Exemple #1
0
from blockchain import BlockChain

_blockchain = BlockChain().create()

for i in range(20):
    hash = _blockchain.add(i)
    print(hash)
    print(_blockchain.retrieve_data_by_block_hash(hash))
Exemple #2
0
class Miner():
    def __init__(self, blockchain: BlockChain=None):
        self.blockchain = BlockChain()
        if(blockchain != None):
            # This refers to the same blockchain object, but we just want a copy of it. 
            self.blockchain = blockchain
        self.transactions = []
        # self.balances: List[Dict[bytes, float]] = [{}]
        # self.balances: Dict[bytes, float] = {}
        # It's a dictionary of a dictionary.
        # First keys are blocks => headers, second keys are sender/receiver's keys
        self.balances: Dict[bytes, Dict[bytes, float]] = {}
        self.wallet = Wallet()
        initial_transaction = Transaction(b'', self.wallet.get_public_key(), 100., "first transaction", signature=b'')
        self.transactions.append(initial_transaction)

        # Done:
        # initial_transaction needs to be verified 
        # I think we have to verify every transaction before mining..?
        # Need to implement first miner coinbase money
        # Need to have list of transactions to be put into merkle tree mined
        # Miners have to mine
        # Handle transactions so as to not repeat them in previously mined blocks or such, handle it in .add()?
        # Since searching for the longest chain is relatively cheap, especially if there are little forks, we 
        #   can search for the longest chain every time instead of storing the current longest chain.
        # We're using a on-the-fly calculation of balances, with some memory storage done every 5 blocks.
        # Miner needs to have data of all the wallets and how much money they contain to verify transactions.


        # Not done:
        # When we reject transactions/blocks from forks, should we pick up the transactions to be resubmitted again?
        # Transactions should to be checked after and not rejected straight away, deposit might come later than withdrawal.
        # We should be able to implement only certain transactions that are good and reject the rest that are not.
        # Need to respond to other miners for queries about orphan's parents
        # Need to implement nakamoto consensus
        # Need to query for parents if too many orphans
        # Need to implement resolving other miner's longer blockchains (miner broadcasts block)
        # Good to make custom exceptions for failed verifications and the likes
        # Need to handle sending the same transactions: miners should check if the transaction has been sent before.
        # Need to reject transactions made from shorter chains, and verify transactions from longer chains.
        # not required: miners joining halfway

        # Miners have to send through the network:-
        # transactions, blocks, difficulty raises, requests for orphans, 
        # Miner has to be coded to be interruptable by new incoming blocks
    

    def change_difficulty(self, difficulty: bytes):
        self.blockchain.difficulty = difficulty

    # Adds transactions to self.transactions
    # This is used for single transactions while validate_transactions is used for a list of transactions.
    # This does not yet update the balance, only when a block is mined the balance is updated.
    # If the transaction is rejected, the transaction is thrown away instead of being put into an orphan pool.
    def add_transaction(self, transaction: Transaction):
        if(transaction.validate()):
            current_balance = self.get_current_balance()
            if(self.check_balances([transaction], current_balance)):
                self.transactions.append(transaction)
                return True
        return False
                

    # Runs .validate() on all transactions, handles the first transaction differently.
    # Used in .receive_block(), when a block is received.
    # Does not check for signature of the first transaction as there isn't any. 
    # Verify only validates transactions to see if they are able to go through, the adding is done later.
    # Does not tell which transactions are wrong, just whether the set is right or wrong.
    def validate_transactions(self, inp_transactions: List, balance_to: Dict[bytes,float]) -> bool:
        if(type(inp_transactions[0]) == bytes):
            transactions: List[Transaction] = [Transaction.from_json(x.decode()) for x in inp_transactions]
        else:
            transactions: List[Transaction] = inp_transactions
        if(transactions[0].amount == 100.0 and transactions[0].sender == b''):
            if not (self.check_balances(transactions, balance_to)):
                return False
            for transaction in transactions[1:]:
                if (not transaction.validate()):
                    return False
            return True
        else:
            raise ValueError("First transaction: amount not 100 or sender not b''")

    # First updates all the transactions into a copy of current balances, 
    # Then checks if any balance is negative.
    # Returns False if any balance is negative, True otherwise.
    # Does not check b''
    def check_balances(self, transactions: List[Transaction], balance: Dict[bytes, float]) -> bool:
        testbalances = copy.deepcopy(balance)
        for i in transactions:
            self._update_balances(i, testbalances)
        for i in testbalances:
            if(i == b''):
                continue
            if(testbalances[i] < 0):
                return False
        return True

    # Updates the provided balances with inp_transaction
    # Mutates the passed-in dictionary
    # Does not check if amount would be negative.
    # Should be used after/in self.check_balances() as this does not check.
    def _update_balances(self, inp_transaction: Transaction, balances: Dict[bytes, float]):
        sender = inp_transaction.sender if inp_transaction.sender == b'' else inp_transaction.sender.to_string()
        receiver = inp_transaction.receiver.to_string()
        amount: float = inp_transaction.amount
        if not (sender in balances):
            balances[sender] = 0
        if not (receiver in balances):
            balances[receiver] = 0
        balances[sender] -= amount
        balances[receiver] += amount

    # Gets the root of transactions after verifying them
    # Currently unused
    # def get_root(self) -> bytes:
        # if(self.validate_transactions(self.transactions)):
        #     transactions_bytes = [x.to_json().encode() for x in self.transactions]
        #     return Block.get_transaction_root(transactions_bytes)

    # Receives block from other miners
    # Validates the new block, then add it into the blockchain.
    # I check the received block, then if it's good, 
    #   I'll add the block and update my balances and update my wallet and remove repeated transactions
    def receive_block(self, block: Block) -> bool: 
        # We need to handle errors for prev_header not being there.
        balance = self.compute_balance(block.prev_header)
        if(balance is None):
            # It's an orphan
            self.blockchain.add(block)
            return False

        verified = self.validate_transactions(block.transactions.get_entries(), balance)
        if (verified):
            if(block.validate(self.blockchain.difficulty)):
                for i in block.transactions.get_entries():
                    self._update_balances(Transaction.from_json(i), balance)
                self.blockchain.add(block)
                self._update_self_wallet(block.transactions.get_entries())
                self.remove_repeated_transactions(block.transactions.get_entries())
                # After 5(arbitrary number) blocks, stores computed balance in self.balances for efficiency
                if (self.blockchain.blocks[-1].length % 5 == 0):
                    self.balances[block.get_header()] = self.compute_balance(block.get_header())
                return True
            else:
                print("Failed to validate block")
                return False
                # raise ValueError("Failed to validate block")
        # raise ValueError("failed to verify transactions")
        return False

    # removes all transactions that appear in both the input and self.transactions
    # To be used before adding a new block (self._add_block())
    def remove_repeated_transactions(self, input_transaction: List):
        if(type(input_transaction[0]) == bytes):
            transactions = [Transaction.from_json(x.decode()) for x in input_transaction]
            # I'm converting from json to json again, it's an inefficiency
        else:
            transactions: List[Transaction] = input_transaction
        leftovers = self.transactions[:]
        to_be_popped: List[int] = []
        for i, self_transaction in enumerate(self.transactions[1:]):
            for other_transaction in transactions:
                if(self_transaction.to_json() == other_transaction.to_json()):
                    # Since we've implemented __eq__, we can just do 
                    # self_transaction == other_transaction
                    print("Removed a transaction")
                    to_be_popped.append(i+1)
        for i in to_be_popped[::-1]:
            # We're popping from the back
            leftovers.pop(i)
        self.transactions = leftovers

    # Broadcasts block to other miners
    def send_block(self):
        pass

    # This mines and adds the block, returns the new block mined to be broadcasted.
    # To verify transactions, we used .receive_block(). 
    # If it errors the transactions have an issue, which shouldn't happen as self.transactions are already validated
    def mine(self) -> Block:
        newblock = self.blockchain.mine(self.transactions)
        if not (self.receive_block(newblock)): # This shouldn't raise an error...
            raise ValueError("Block not received properly after mining, probably some transactions are wrong")
        return newblock

    # Attempts to mine once, so we are able to interrupt the mining.
    # Returns None if it fails to get a valid block.
    def mine_once(self) -> Block:
        newblock = self.blockchain.mine_once(self.transactions)
        if (newblock is not None):
            if not (self.receive_block(newblock)): # This shouldn't raise an error...
                raise ValueError("Block not received properly after mining, probably some transactions are wrong")
            return newblock
        return None
    

    # Private method used in mine() and receive_block(), for doing some actions to add a block.
    # update transactions should be here.
    # Not used, implementation is put in receive_block
    # def _add_block(self, newblock: Block):
    #     self.blockchain.add(newblock)
    #     self._update_self_wallet(newblock.transactions.get_entries())
    #     self.remove_repeated_transactions(newblock.transactions.get_entries())

    # To update self.wallet with transactions
    def _update_self_wallet(self, transactions: List):
        for trans in transactions:
            transaction = Transaction.from_json(trans.decode())
            if (transaction.receiver.to_string() == self.wallet.get_public_key().to_string()):
                self.wallet.deposit(transaction.amount)
            if (transaction.sender == b''):
                continue
            elif (transaction.sender.to_string() == self.wallet.get_public_key().to_string()):
                if not (self.wallet.withdraw(transaction.amount)):
                    # Not enough cash from wallet, shouldn't happen.
                    # Transactions should have been validated in .receive_blocks()
                    raise ValueError("Not enough money, shouldn't happen")
    
    # This function verifies that the transaction exists in the blockchain
    # This function will return the proof.
    # Should be used for clients.
    def verify_transaction(self, transaction):
        transaction: bytes = transaction.to_json().encode()
        current_block = self.blockchain.get_longest_chain()
        while current_block != None:
            proof = current_block.block.transactions.get_proof(transaction)
            if proof != None:
                root = current_block.block.transaction_root
                return proof, root
            current_block = current_block.previous
        return None

    # Computes the balance from the stated header to the genesis block.
    # Computes the balance on the fly, can be used in forking or verification.
    # Assumes the header provided is good.
    # Makes use of self.balances to reduce computation. (kinda like checkpoints)
    def compute_balance(self, header: bytes) -> Dict[bytes,float]:
        current_node = self.blockchain.get_matching_header(header)
        if (current_node == None):
            return None
        balance = {}
        while (current_node != None):
            if(current_node.block.get_header() in self.balances):
                return self.combine_balances(self.balances[current_node.block.get_header()], balance)
            current_transactions = current_node.block.transactions.get_entries()
            for transaction in current_transactions:
                trans = Transaction.from_json(transaction)
                self._update_balances(trans, balance)
            current_node = current_node.previous
        return balance

    # Gets the longest chain's balance.
    def get_current_balance(self) -> Dict[bytes, float]:
        current_longest_chain_header = self.blockchain.get_longest_chain().block.get_header()
        return self.compute_balance(current_longest_chain_header)


    # Given 2 balances, adds them together.
    def combine_balances(self, inp_balances1, inp_balances2):
        balances1 = copy.deepcopy(inp_balances1)
        balances2 = copy.deepcopy(inp_balances2)
        for i in balances1:
            if(i in balances2):
                balances2[i] += balances1[i]
            else:
                balances2[i] = balances1[i]
        return balances2

    # Checks for orphans in self.blockchain, and attempts to add all of them in a for loop.
    def add_orphans(self):
        for orphan in self.blockchain.orphans[:]:
            if(self.receive_block(orphan)):
                self.blockchain.orphans.remove(orphan)
                print("Orphan added to blockchain!")