def register_node_to_ring(self, wallet_dict: dict): '''Handle request to enter the network. The bootstrap node should register the node in `ring` and respond with index and current blockchain. Arguments: * `wallet_dict`: `dict` directly from `.to_dict()`. Returns: * `dict` with ['id', 'blockchain'].''' node_wallet = Wallet.from_dict(wallet_dict) # if node has already contacted before to register # do not produce new index, ... index = self.pubk2ind.get(pubk_to_key(node_wallet.public_key), len(self.pubk2ind)) self.pubk2ind[pubk_to_key(node_wallet.public_key)] = index self.ring[index] = node_wallet self.ring_bak[index] = node_wallet.deepcopy() info = dict(blockchain=self.blockchain.to_dict(), id=index) return info
def view_blockchain(): '''Return human readable blockchain''' blockchain_list = [] for block in NODE.blockchain.chain: human_readable = dict() for transaction in block.list_of_transactions: if isinstance(transaction.sender_pubk, int): # genesis block human_readable.setdefault('transactions', []).append( dict(sender='Genesis', receiver='0', amount=sum([ to.amount for to in transaction.transaction_outputs ]))) else: human_readable.setdefault('transactions', []).append( dict(transaction_id=transaction.transaction_id, transaction_inputs=transaction.transaction_inputs, sender=NODE.pubk2ind[pubk_to_key(transaction.sender_pubk)], receiver=NODE.pubk2ind[pubk_to_key(transaction.receiver_pubk)], amounts=[transaction_output.amount \ for transaction_output in transaction.transaction_outputs]) ) blockchain_list.append(human_readable) return jsonify(blockchain_list), 200
def receive_wallets(self, wallet_dict: dict): '''Receive all wallets from the bootstrap and copy to ring and backup. Also, process all transactions received before receiving the wallets. Arguments: * `wallet_dict`: `dict` with indices as key values and `Wallet` `dicts` as values.''' self.ring = { int(idx): Wallet.from_dict(wallet_dict[idx]) \ if int(idx) != self.my_id else self.my_wallet() \ for idx in wallet_dict } self.pubk2ind = { pubk_to_key(self.ring[idx].public_key): idx for idx in self.ring } self.ring_bak = object_dict_deepcopy(self.ring) while True: new_ring = self.valid_chain(self.blockchain) if new_ring is not None: self.ring = object_dict_deepcopy(new_ring) self.ring_bak = object_dict_deepcopy(new_ring) break else: self.my_id, self.blockchain = first_contact_data( self.bootstrap_address, self.my_wallet()) # process transactions received before wallets self.process_transactions()
def add_utxos(self, transaction_outputs: list, ring: dict): '''Add unspent transactions to respective wallets. Arguments: * `transaction_outputs`: iterable of `TransactionOutput` objects. * `ring`: `Wallet` of nodes.''' for tro in transaction_outputs: ring[self.pubk2ind[pubk_to_key( tro.receiver_public_key)]].add_utxo(tro)
def view_transactions(): '''Return human readable format (`sender`, `receiver`, `amount`) of every transacion in the last block of the blockchain.''' last_block = NODE.blockchain.chain[-1] human_readable = dict() for transaction in last_block.list_of_transactions: if isinstance(transaction.sender_pubk, int): # genesis block human_readable.setdefault('transactions', []).append( dict(sender='Genesis', receiver='0', amount=sum([ to.amount for to in transaction.transaction_outputs ]))) else: human_readable.setdefault('transactions', []).append( dict(sender=NODE.pubk2ind[pubk_to_key( transaction.sender_pubk)], receiver=NODE.pubk2ind[pubk_to_key( transaction.receiver_pubk)], amount=transaction.transaction_outputs[0].amount)) return jsonify(human_readable), 200
def check_my_mined_block(self, block_dict: dict): '''Check block returned from miner and its coherence with the current blockchain. Append if everything is proper. Renew miner. NOTE: block is not broadcasted. Arguments: * `block_dict`: `dict` directly from `to_dict()`. Returns: * The mined block or `None` if not appended.''' block = Block.from_dict(block_dict) if block.previous_hash == self.blockchain.get_block_hash(-1): block_transactions, _ = self.transaction_queue.split(self.capacity, assign=1) # allow miner to be recalled now that the transaction queue is up-to-date self.miner_pid = None for tra in block_transactions: self.add_utxos(tra.transaction_outputs, ring=self.ring_bak) self.ring_bak[self.pubk2ind[pubk_to_key(tra.sender_pubk)]]\ .remove_utxos(tra.transaction_inputs) # NOTE: broadcast block from API so not inside lock # self.broadcast_block(block) self.blockchain.append_block(block) else: # new blockchain/block received and miner was not killed in time # enable to recall, but transaction queue is new so dont meddle self.miner_pid = None block = None if len(self.transaction_queue) >= self.capacity: self.mine_block() return block
def validate_transaction(self, transaction: Transaction, ring: dict): '''Validate received transaction. Arguments: * `transaction`: [Reconstructed from `dict`] `Transaction`. * `ring`: ring of `Wallet`s the validation is based upon. Returns: * `True` if valid.''' # signature if not PKCS1_v1_5.new(transaction.sender_pubk).\ verify(transaction.make_hash(as_str=False), transaction.signature): return False # double spending if len(set(transaction.transaction_inputs)) < len( transaction.transaction_inputs): return False # check ids are the same, note that utxos can be 1 or 2 if not all([utxo.transaction_id == transaction.transaction_id \ for utxo in transaction.transaction_outputs]): return False # check if transaction inputs exist amount = 0 for utxo in transaction.transaction_outputs: if utxo.amount < 0: return False amount += utxo.amount return ring[self.pubk2ind[pubk_to_key(transaction.sender_pubk)]]\ .check_and_remove_utxos(transaction.transaction_inputs, amount)
def __init__(self, bootstrap_address: str, capacity: int, difficulty: int, port: int, nodes=0, is_bootstrap=False): '''Initialize `Node` object. Arguments: * `bootstrap_address`: ip+port of bootstrap (considered known a priori). * `capacity`: capacity of a block in blockchain. * `difficulty`: difficulty of mining a block. * `port`: port listening to. * `nodes`: number of nodes in the network (considered known a priori). * `is_bootstrap`: if this node is the bootstrap.''' wallet = generate_wallet(port) # validated transactions self.transaction_queue = TransactionQueue() # transactions not reflected in wallets of ring self.unprocessed_transaction_queue = TransactionQueue() self.miner_pid = None self.capacity = capacity self.difficulty = difficulty self.nodes = nodes if is_bootstrap: self.my_id = 0 # information for every node (its address (ip:port), # its public key, its balance, its utxos) # should be modified with TRANSACTION_LOCK self.ring = {self.my_id: wallet} # backup containing info as it appears in the blockchain # Should be modified with BLOCK_LOCK self.ring_bak = object_dict_deepcopy(self.ring) # public key to index correspondence self.pubk2ind = { pubk_to_key(self.my_wallet().public_key): self.my_id } self.blockchain = self.init_bootstrap_blockchain() else: self.bootstrap_address = bootstrap_address self.my_id, self.blockchain = first_contact_data( self.bootstrap_address, wallet) # information for every node (its address (ip:port), # its public key, its balance, its utxos) self.ring = {self.my_id: wallet} # public key to index correspondence self.pubk2ind = { pubk_to_key(self.my_wallet().public_key): self.my_id }