def test_block_to_json(self): wallet = Wallet() transaction = Transaction(wallet.pubkey, "CityU", 1) signature = wallet.sign_transaction(transaction) transaction.add_signature(signature) block = Block(0, [transaction.to_json()], datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S"), "0") content = block.to_json() self.assertIsInstance(content, str) self.assertGreater(len(content), 0)
def test_block_compute_hash(self): wallet = Wallet() transaction = Transaction(wallet.pubkey, "CityU", 1) signature = wallet.sign_transaction(transaction) transaction.add_signature(signature) block = Block(0, [transaction.to_json()], datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S"), "0") hash_val = block.compute_hash() self.assertIsInstance(hash_val, str) self.assertGreater(len(hash_val), 0)
def proof_of_work(self, block: Block) -> str: '''Proof-of-Work to verify a valid block and find the corresponding nonce value''' block.nonce = 0 computed_hash = block.compute_hash() # Keep trying and increasing the nonce value # until the new hash value meets the difficulty level restriction (Solve the hash puzzle) while not computed_hash.startswith('0' * block.difficulty): block.nonce += 1 computed_hash = block.compute_hash() return computed_hash
def test_block_to_dict(self): wallet = Wallet() transaction = Transaction(wallet.pubkey, "CityU", 1) signature = wallet.sign_transaction(transaction) transaction.add_signature(signature) block = Block(0, [transaction.to_json()], datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S"), "0") content = block.to_dict() self.assertIsInstance(content, dict) self.assertEqual(list(content.keys()), ["index", "timestamp", "previous_hash", "merkle_root", "nonce", "difficulty"])
def create_genesis_block(self, wallet: Wallet): '''method to create and puts the genesis block into the blockchain''' block_reward = Transaction("Block_Reward", wallet.pubkey, "5.0").to_json() genesis_block = Block( 0, [block_reward], datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S"), "0") # Hash of genesis block cannot be computed directly, proof of work is needed genesis_block.hash = self.proof_of_work(genesis_block) self.chain.append(genesis_block.to_json())
def add_block(self, block: Block, proof: str) -> bool: ''' method to check if a new block could be added at the end of the blockchain 1. by checking that the new block from parameter is the next block from our last block 2. by checking the new block and the given hash (from proof-of-work) is legit ''' previous_hash = self.last_block['hash'] if previous_hash != block.previous_hash: return False if not self.is_valid_proof(block, proof): return False block.hash = proof self.chain.append(block.to_json()) return True
def test_block_initialize(self): wallet = Wallet() transaction = Transaction(wallet.pubkey, "CityU", 1) signature = wallet.sign_transaction(transaction) transaction.add_signature(signature) block = Block(0, [transaction], datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S"), "0") self.assertIsInstance(block, Block)
def is_valid_proof(self, block: Block, block_hash: str) -> bool: ''' we might receive blocks from another node, a validation method is also needed. a valid proof should have a valid hash starting with the corresponding difficulty number of 0 (e.g. difficulty = 2; hash = 00abcd...), and the testing block's hash should match with the computed hash. ''' return (block_hash.startswith('0' * block.difficulty) and (block_hash == block.compute_hash()))
def mine(self, wallet: Wallet) -> Union[Block, bool]: ''' method to generate new blocks and claim the block reward and all the transaction fees It confirms all unconfirmed transactions into blocks by using the proof-of-work method. convert to JSON to store transaction in the blockchain ''' # get all available wallet addresses in the network addresses = self.get_addresses_from_transactions() # create Transaction objects for interest interest_txs = self.create_interest_transactions(addresses) for interest_tx in interest_txs: self.unconfirmed_transactions.insert(0, interest_tx.to_json()) # add up all the transaction fee from all unconfirmed transactions tx_fees = [ float(json.loads(transaction)['fee']) for transaction in self.unconfirmed_transactions ] total_tx_fee = sum(tx_fees) # create and add a transaction into the list of unconfirmed transactions # for sending out a block reward, which also include all of their transaction fees block_reward = Transaction("Block_Reward", wallet.pubkey, str(5.0 + total_tx_fee)) self.unconfirmed_transactions.insert(0, block_reward.to_json()) # if there are no unconfirmed transactions, return False if not self.unconfirmed_transactions: return False new_block = Block( index=self.last_block['index'] + 1, transaction=self.unconfirmed_transactions, timestamp=datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S"), previous_hash=self.last_block['hash']) new_block.difficulty = self.next_difficulty(self.last_block) proof = self.proof_of_work(new_block) if self.add_block(new_block, proof): self.unconfirmed_transactions = [] return new_block else: return False
def test_blockchain_add_block_wrong_previous_hash(self): wallet = Wallet() blockchain = Blockchain(wallet) transaction = Transaction(wallet.pubkey, "CityU", 1) signature = wallet.sign_transaction(transaction) transaction.add_signature(signature) block = Block(0, [transaction.to_json()], datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S"), "Wrong") computed_hash = blockchain.proof_of_work(block) result = blockchain.add_block(block, computed_hash) self.assertFalse(result)
def test_blockchain_proof_of_work_bad_block(self): wallet = Wallet() blockchain = Blockchain(wallet) transaction = Transaction(wallet.pubkey, "CityU", 1) signature = wallet.sign_transaction(transaction) transaction.add_signature(signature) previous_hash = blockchain.last_block["hash"] block = Block(0, [transaction.to_json()], datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S"), previous_hash) delattr(block, "transaction") self.assertRaises(AttributeError, blockchain.proof_of_work, block)
def test_blockchain_proof_of_work(self): wallet = Wallet() blockchain = Blockchain(wallet) transaction = Transaction(wallet.pubkey, "CityU", 1) signature = wallet.sign_transaction(transaction) transaction.add_signature(signature) previous_hash = blockchain.last_block["hash"] block = Block(0, [transaction.to_json()], datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S"), previous_hash) computed_hash = blockchain.proof_of_work(block) self.assertGreater(len(computed_hash), 0)
def lightweight(): fullchain = [json.loads(block) for block in blockchain.chain] lightweight = [] for block in fullchain: block_object = Block(block['index'], block['transaction'], block['timestamp'], block['previous_hash']) block_object.merkle_root = block['merkle_root'] block_object.nonce = block['nonce'] block_object.difficulty = block['difficulty'] lightweight.append(block_object.to_dict()) response = { 'chain': json.dumps(lightweight), 'length': len(lightweight) } return jsonify(response), 200
def valid_chain(self, chain: List[str]) -> bool: '''check if a blockchain (all blocks) is valid''' current_index = 0 chain = json.loads(chain) # load and check every block from the blockchain while current_index < len(chain): block = json.loads(chain[current_index]) current_block = Block(block['index'], block['transaction'], block['timestamp'], block['previous_hash']) current_block.merkle_root = block['merkle_root'] current_block.nonce = block['nonce'] current_block.difficulty = block['difficulty'] # if the current block is NOT the last block if current_index + 1 < len(chain): # if the hash value of the current block != previous block's hash value of the next block, then reject if current_block.compute_hash() != json.loads( chain[current_index + 1])['previous_hash']: return False # check if the current block is an instance from the list of chain if isinstance(current_block.transaction, list): for transaction in current_block.transaction: transaction = json.loads(transaction) # Skip block reward because it does not have signature if transaction['sender'] == 'Block_Reward': continue current_transaction = Transaction(transaction['sender'], transaction['recipient'], transaction['value']) current_transaction.signature = transaction['signature'] # check if digital signature of each transaction is valid, if not then reject if not current_transaction.verify_transaction_signature(): return False # check if hash value of the current block is not valid, if yes then reject if not self.is_valid_proof(current_block, block['hash']): return False current_index += 1 return True