def validate_chain(self, chain=''): ''' Checks the chain for validity. Returns True on validation. ''' num_of_indexes_at_0 = 0 if not chain: chain = self.chainfile with open(chain, 'r') as f: for line in f: #print("VALIDATION OF NEXT BLOCK : ") #print() block_to_validate = json.loads(line) number_of_zeros = block_to_validate['num_zeros'] nonce = block_to_validate['nonce'] index = block_to_validate['index'] previous_hash = block_to_validate['previous_hash'] block_hash = block_to_validate['hash'] timestamp = block_to_validate['timestamp'] block_data = block_to_validate['data'] #print("Validation : ",block_to_validate['data']) x = Block(index, timestamp, block_data, previous_hash, nonce, number_of_zeros) calculated_hash = x.hash_block() ''' sha = hashlib.sha256() sha.update( str(index).encode('utf-8') + str(timestamp).encode('utf-8') + str(block_data).encode('utf-8') + str(previous_hash).encode('utf-8') + str(number_of_zeros).encode('utf-8') + str(nonce).encode('utf-8') ) calculated_hash = sha.hexdigest() ''' if index == 0: num_of_indexes_at_0 += 1 else: if block_hash != calculated_hash: # print(block_hash," ",calculated_hash) msg = 'Invalid Chain. Message has been changed' raise ValueError(msg) if not _hash == previous_hash: msg = 'Incorrect hashes. Broken chain.' raise ValueError(msg) _hash = block_to_validate['hash'] _hash_to_validate = self._return_hash(previous_hash, nonce) self._validate_hash(_hash_to_validate, number_of_zeros) if num_of_indexes_at_0 > 1: msg = 'Multiple genesis blocks.' raise ValueError(msg) return True
def test_block_updateBlockchain(self): nodeA_chains = [] # has two blocks nodeB_chains = [] # has three blocks genesis_block = Block.create_genesis_block() second_block = Block.create_next_block(genesis_block) third_block = Block.create_next_block(second_block) genesis_block_dict = { "index": str(genesis_block.index), "timestamp": str(genesis_block.timestamp), "data": genesis_block.data, "hash": str(genesis_block.hash) } second_block_dict = { "index": str(second_block.index), "timestamp": str(second_block.timestamp), "data": second_block.data, "hash": str(second_block.hash) } third_block_dict = { "index": str(third_block.index), "timestamp": str(third_block.timestamp), "data": third_block.data, "hash": str(third_block.hash) } nodeA_chains.extend((genesis_block_dict, second_block_dict)) nodeB_chains.extend( (genesis_block_dict, second_block_dict, third_block_dict)) test_my_block = [genesis_block, second_block] test_peer_block = nodeB_chains test2_my_block = [genesis_block, second_block, third_block] test2_peer_block = nodeA_chains ret_chain = test2_my_block # expected return from update_blockchain # assertion tests self.assertTrue(Test_compare.comp_list_of_block_obj( \ self.block_helper._update_blockchain(test_my_block, test_peer_block), \ ret_chain)) self.assertTrue(Test_compare.comp_list_of_block_obj( \ self.block_helper._update_blockchain(test2_my_block, test2_peer_block), \ ret_chain)) self.assertTrue(Test_compare.comp_list_of_block_obj( \ self.block_helper._update_blockchain(test_peer_block, test_my_block, auto_update=True), \ test_my_block))
def _create_genesis_block(self): ''' Creates the genesis block. Seperate routine due to the genesis block creation being a one-off event. ''' b = Block(index=0, timestamp=str(datetime.datetime.now()), data='genesis block', previous_hash='0', nonce=1, num_zeros=0) self._write_to_chain(b.get_block_data()) return
def test_syncBlocksHandler(self): # test data genesis_block = Block.create_genesis_block() second_block = Block.create_next_block(genesis_block) third_block = Block.create_next_block(second_block) mock_blockchain = [genesis_block, second_block, third_block] with patch("server.Server.block_helper.consensus", return_value=mock_blockchain), \ patch("server.Server.blockchain", []) as mock_bc: # test method call Server.sync_blocks_handler() self.assertEqual(mock_bc, mock_blockchain)
def test_block_createBlock(self): with patch("resources.block.datetime") as mock_datetime: # test data mock_datetime.datetime.now.return_value = "NOW" genesis_block = Block.create_genesis_block() expected_result = Block(genesis_block.index + 1, "NOW", genesis_block.data, prev_hash=genesis_block.hash) # test test_result = self.block_helper.create_block( genesis_block.index + 1, "NOW", genesis_block.data, genesis_block.hash) boo_true = Test_compare.comp_list_of_block_obj([test_result], [expected_result]) self.assertTrue(boo_true)
def test_block_findOtherChains(self): test_peer_nodes = ['192.168.1.86:5000', '192.168.1.87:5000'] test_get_ret_chain = [] # has two blocks genesis_block = Block.create_genesis_block() genesis_block_dict = { "index": str(genesis_block.index), "timestamp": str(genesis_block.timestamp), "data": str(genesis_block.data), "hash": genesis_block.hash } # need to create test dictionary data of the block object test_get_ret_chain.append(genesis_block_dict) second_block = Block.create_next_block(genesis_block) second_block_dict = { "index": str(second_block.index), "timestamp": str(second_block.timestamp), "data": str(second_block.data), "hash": second_block.hash } test_get_ret_chain.append(second_block_dict) test_get_ret_chain = json.dumps(test_get_ret_chain) chains = [ json.loads(test_get_ret_chain), json.loads(test_get_ret_chain) ] # technically using our previous dictionaries would work with patch('requests.get') as mock_get: mock_get.return_value.status_code = 200 mock_get.return_value.content = test_get_ret_chain self.assertEqual( self.block_helper._find_other_chains(test_peer_nodes), chains)
def create_initial_block(): b = Block(index=0, timestamp=str(datetime.datetime.now()), data='genesis block', previous_hash='0', nonce=1) return {'index':b.index, 'timestamp':b.timestamp, 'data':b.data, 'nonce':b.nonce, 'previous_hash':b.previous_hash, 'hash':b.hash}
def test_block_createGenBlock(self): with patch("resources.block.datetime") as mock_datetime: # test data mock_datetime.datetime.now.return_value = "NOW" expected_result = Block.create_genesis_block() test_result = self.block_helper.create_gen_block() # test boo_true = Test_compare.comp_list_of_block_obj([test_result], [expected_result]) self.assertTrue(boo_true)
def mine(): """Performs work. Becomes swoll. Miner gets rewarded. Raises ValueError : when `mine()` is called on an empty `blockchain` """ MINER_REWARD = 1 global this_nodes_transactions # verifies non-empty blockchain if not blockchain: msg = "Empty blockchain." raise ValueError(msg) # if not this_nodes_transactions: # msg = "Empty transaction list." # raise ValueError(msg) last_block = blockchain[len(blockchain) - 1] # perform proof of work function _previous_hash = last_block['hash'] _nonce = proof_of_work(_previous_hash) # reward miner this_nodes_transactions.append({ 'from': 'network', 'to': miner_address, 'amount': MINER_REWARD }) # generate new block's data, empty local transaction list _data = {'transactions': this_nodes_transactions} _index = int(last_block['index']) + 1 _timestamp = str(datetime.datetime.now()) mined_block = Block(index=_index, timestamp=_timestamp, data=_data, previous_hash=_previous_hash, nonce=_nonce) this_nodes_transactions = [] mined_block_data = { 'index': mined_block.index, 'timestamp': mined_block.timestamp, 'data': mined_block.data, 'nonce': mined_block.nonce, 'previous_hash': mined_block.previous_hash, 'hash': mined_block.hash } blockchain.append(mined_block_data) # inform client of mining's completion mined = json.dumps(mined_block_data) print(mined + '\n') return mined
def create_new_block(self): ''' Creates a block using the data in `self.data`. ''' with open(self.chainfile, 'r') as f: previous_block = f.readlines()[-1] previous_block = json.loads(previous_block) f.close() index = previous_block['index'] + 1 previous_hash = previous_block['hash'] timestamp = str(datetime.datetime.now()) nonce, number_of_leading_zeros = proof_of_work(previous_hash) self.block = Block(index=index, timestamp=timestamp, data=self.data, previous_hash=previous_hash, nonce=nonce, num_zeros=number_of_leading_zeros) self._write_to_chain(self.block.get_block_data()) self.data = " " return
def test_getBlocksHandler(self): # test data genesis_block = Block.create_genesis_block() genesis_block_dict = { "index": genesis_block.index, "timestamp": genesis_block.timestamp, "data": genesis_block.data, "hash": genesis_block.hash } mock_blockchain = [genesis_block] expected_result = json.dumps([genesis_block_dict]) with patch("server.Server.blockchain", mock_blockchain): # test method call test_call_ret = Server.get_blocks_handler # comp_list_of_nodes will also work here self.assertEqual(json.loads(test_call_ret()), json.loads(expected_result))
class Blockchain: def __init__(self): # path to chain dir_path = os.path.dirname(os.path.realpath(__file__)) self.chainfile = os.path.join(dir_path, 'chain', CHAIN_NAME) self._create_chain_if_not_exists() # create genesis block if chainfile is empty if os.stat(self.chainfile).st_size == 0: self._create_genesis_block() self.validate_chain() self.data = [] def _create_chain_if_not_exists(self): if not os.path.isfile(self.chainfile): f = open(self.chainfile,'w') f.close() return def _create_genesis_block(self): ''' Creates the genesis block. Seperate routine due to the genesis block creation being a one-off event. ''' b = Block(index=0, timestamp=str(datetime.datetime.now()), data='genesis block', previous_hash='0', nonce=1, num_zeros=0) self._write_to_chain(b.get_block_data()) return def _write_to_chain(self, block_dictionary): ''' Writes a dictionary to json, appends the json to the blockchain text file. ''' with open(self.chainfile, 'a') as f: f.write(json.dumps(block_dictionary) + '\n') f.close() return def create_new_block(self): ''' Creates a block using the data in `self.data`. ''' with open(self.chainfile, 'r') as f: previous_block = f.readlines()[-1] previous_block = json.loads(previous_block) f.close() index = previous_block['index'] + 1 previous_hash = previous_block['hash'] timestamp = str(datetime.datetime.now()) nonce, number_of_leading_zeros = proof_of_work(previous_hash) self.block = Block(index=index, timestamp=timestamp, data=self.data, previous_hash=previous_hash, nonce=nonce, num_zeros=number_of_leading_zeros) self._write_to_chain(self.block.get_block_data()) self.data = [] return def add_data_to_block(self, new_data): ''' Appends data to the newest block. ''' self.data.append(str(new_data)) def _return_hash(self, previous_hash, nonce): sha = hashlib.sha256() sha.update( str(previous_hash).encode('utf-8') + str(nonce).encode('utf-8') ) return sha.hexdigest() def _validate_hash(self, _hash, num_zeros): if str(_hash[:num_zeros]) != "0" * num_zeros: # checks for leading zeros msg = 'Invalid chain.' raise ValueError(msg) else: return True def validate_chain(self, chain=''): ''' Checks the chain for validity. Returns True on validation. ''' num_of_indexes_at_0 = 0 if not chain: chain = self.chainfile with open(chain, 'r') as f: for line in f: block_to_validate = json.loads(line) number_of_zeros = block_to_validate['num_zeros'] nonce = block_to_validate['nonce'] index = block_to_validate['index'] previous_hash = block_to_validate['previous_hash'] if index == 0: num_of_indexes_at_0 += 1 else: if not _hash == previous_hash: msg = 'Incorrect hashes. Broken chain.' raise ValueError(msg) _hash = block_to_validate['hash'] _hash_to_validate = self._return_hash(previous_hash, nonce) self._validate_hash(_hash_to_validate, number_of_zeros) if num_of_indexes_at_0 > 1: msg = 'Multiple genesis blocks.' raise ValueError(msg) return True
def test_block_consensus(self): test_peer_nodes = ['192.168.1.86:5000', '192.168.1.87:5000'] ret = [] nodeA_chains_dict = [] # has two blocks format: [{dict}] nodeB_chains_dict = [] # has three blocks nodeC_chains_dict = [ ] # has one block this is the same format returned from _find_other_chains genesis_block = Block.create_genesis_block() second_block = Block.create_next_block(genesis_block) third_block = Block.create_next_block(second_block) genesis_block_dict = { "index": str(genesis_block.index), "timestamp": str(genesis_block.timestamp), "data": genesis_block.data, "hash": str(genesis_block.hash) } second_block_dict = { "index": str(second_block.index), "timestamp": str(second_block.timestamp), "data": second_block.data, "hash": str(second_block.hash) } third_block_dict = { "index": str(third_block.index), "timestamp": str(third_block.timestamp), "data": third_block.data, "hash": str(third_block.hash) } nodeA_chains_dict.extend((genesis_block_dict, second_block_dict)) nodeB_chains_dict.extend( (genesis_block_dict, second_block_dict, third_block_dict)) nodeC_chains_dict.append(genesis_block_dict) ret.extend( (nodeA_chains_dict, nodeB_chains_dict)) # what is returned from _find_other_chains ret_chain = [genesis_block, second_block, third_block ] # expended returned chain output from consensus # need to create test data for when two coins are mined almost simoutaneously, in which case, we take the earlier stamped coin time_early = datetime.datetime.now() time_late = datetime.datetime.now() trans_network = { "timestamp": "NOW", "from": "network", "to": "random", "amount": 1 } trans_no_network = { "timestamp": "NOW", "from": "random", "to": "random", "amount": 1 } data_mine = {"proof-of-work": 36, "transactions": [trans_network]} data_not_mine = { "proof-of-work": 36, "transactions": [trans_no_network] } data_no_trans = {"proof-of-work": None, "transactions": None} # testing blocks block_early = { "index": 1, "timestamp": time_early, "data": dict(data_mine), "hash": "random-hash-bc-it-doesnt-matter-for-this-test" } block_late = { "index": 1, "timestamp": time_late, "data": dict(data_mine), "hash": "random-hash-bc-it-doesnt-matter-for-this-test" } block_early_obj = Block(block_early['index'], block_early['timestamp'], block_early['data'], current_hash=block_late['hash']) ret2 = [[genesis_block_dict, block_early]] # tests for when my chain is either shorter or longer than peer's, in which we return the longest with patch( 'resources.helper.Helper.Blockchain_helper._find_other_chains' ) as mock_find_other_chains: mock_find_other_chains.return_value = ret self.assertTrue(Test_compare.comp_list_of_block_obj( \ self.block_helper.consensus([genesis_block], test_peer_nodes), \ ret_chain)) self.assertTrue(Test_compare.comp_list_of_block_obj( \ self.block_helper.consensus([genesis_block, second_block, third_block], test_peer_nodes), \ ret_chain)) # tests when my chain is the same length, in which we take the earlier one, if both last blocks are mine blocks with patch( 'resources.helper.Helper.Blockchain_helper._find_other_chains' ) as mock_find_other_chains: mock_find_other_chains.return_value = ret2 test_result = self.block_helper.consensus( [genesis_block, block_late], test_peer_nodes) expected_result = [genesis_block, block_early_obj] boo_true = Test_compare.comp_list_of_block_obj( test_result, expected_result) self.assertTrue(boo_true)
def test_mineHandler(self): with patch("server.datetime") as mock_server_datetime, \ patch("resources.block.datetime") as mock_datetime, \ patch("server.requests") as mock_requests: mock_requests.post.return_value = 1 mock_datetime.datetime.now.return_value = 'NOW' mock_server_datetime.datetime.now.return_value = 'NOW' # test data # mock_server.miner_address = Server.miner_address host1 = "192.168.1.86:5000" host2 = "192.168.1.87:5000" trans1 = { "timestamp": str(mock_server_datetime.datetime.now()), "from": "asdf-random-public-key-asdf", "to": "qwer-random-public-key-qwer", "amount": 1 } trans2 = { "timestamp": str(mock_server_datetime.datetime.now()), "from": "zxcv-random-public-key-zxcv", "to": "hjkl-random-public-key-hjkl", "amount": 3 } data = { "proof-of-work": 18, "transactions": list([trans1, trans2]) } genesis_block = Block.create_genesis_block() second_block = Block.create_next_block(genesis_block) third_block = Block(second_block.index + 1, mock_server_datetime.datetime.now(), data, prev_hash=second_block.hash) mine_trans = { "timestamp": str(mock_server_datetime.datetime.now()), "from": "network", "to": Server.miner_address, "amount": 1 } mined_data = { "proof-of-work": 36, "transactions": list([trans1, trans2, mine_trans]) } mined_block = Block(third_block.index + 1, mock_server_datetime.datetime.now(), mined_data, prev_hash=third_block.hash) expected_output = json.dumps({ "index": mined_block.index, "timestamp": str(mined_block.timestamp), "data": mined_block.data, "hash": str(mined_block.hash) }) with patch("server.Server.node_helper.consensus", return_value=[host1, host2]), \ patch("server.Server.block_helper.consensus", return_value=[genesis_block, second_block, third_block]), \ patch("server.Server.trans_helper.consensus", return_value=[trans1, trans2]), \ patch("server.Server.helper.proof_of_work", return_value=36), \ patch("server.Server.this_node_transactions", [trans1, trans2]) as mock_tnt, \ patch("server.Server.blockchain", []) as mock_bc, \ patch("server.Server.peer_nodes", []) as mock_pn: # test method call test_ret = Server.mine_handler() # test for node transactions (method sideeffect) # this tests the second sync function, usually will result in [] # but this time mock sync returns [trans1, trans2] self.assertEqual(mock_tnt, [trans1, trans2]) # test for block mined (method output) boo = Test_compare.comp_block_dict(json.loads(test_ret), json.loads(expected_output)) self.assertTrue(boo) # test for blockchain congruency (network effect) # NOTE: mined_block is not added here because of the second sync of mine method, and it is resuing the block consensus return boo = Test_compare.comp_list_of_block_obj( mock_bc, [genesis_block, second_block, third_block]) self.assertTrue(boo) # below is to test if the current chain is empty, genesis block must be created, and returned is the second (mined) block of chain with patch("server.Server.node_helper.consensus", return_value=[host1, host2]), \ patch("server.Server.block_helper.consensus", return_value=[]), \ patch("server.Server.trans_helper.consensus", return_value=[trans1, trans2]), \ patch("server.Server.helper.proof_of_work", return_value=36), \ patch("server.Server.this_node_transactions", data["transactions"]) as mock_tnt, \ patch("server.Server.blockchain", []) as mock_bc, \ patch("server.Server.peer_nodes", []) as mock_pn: mine_trans = { "timestamp": str(mock_server_datetime.datetime.now()), "from": "network", "to": Server.miner_address, "amount": 1 } mined_data = { "proof-of-work": 36, "transactions": list([trans1, trans2, mine_trans]) } mined_block = Block(genesis_block.index + 1, mock_server_datetime.datetime.now(), mined_data, prev_hash=genesis_block.hash) expected_output = json.dumps({ "index": mined_block.index, "timestamp": str(mined_block.timestamp), "data": mined_block.data, "hash": str(mined_block.hash) }) # test method call test_ret = Server.mine_handler() # test for block mined (method output) boo = Test_compare.comp_block_dict(json.loads(test_ret), json.loads(expected_output)) self.assertTrue(boo)
def test_updateList(self): # test data host1 = "192.168.1.86:5000" host2 = "192.168.1.87:5000" trans1 = { "timestamp": str(datetime.datetime.now()), "from": "asdf-random-public-key-asdf", "to": "qwer-random-public-key-qwer", "amount": 1 } trans2 = { "timestamp": str(datetime.datetime.now()), "from": "zxcv-random-public-key-zxcv", "to": "hjkl-random-public-key-hjkl", "amount": 3 } genesis_block = Block.create_genesis_block() block2 = Block.create_next_block(genesis_block) # test 1, ensure transaction with patch("server.Server.this_node_transactions", []) as mock_tnt, \ patch("server.Server.trans_helper.ensure", return_value=[trans1, trans2]): Server._updateList(ensure_transactions=True, delete_data=None) self.assertEqual(mock_tnt, [trans1, trans2]) # test 2, sync this node's transactions with patch("server.Server.this_node_transactions", []) as mock_tnt, \ patch("server.Server.trans_helper.consensus", return_value=[trans1, trans2]): Server._updateList(node_transactions=True) self.assertEqual(mock_tnt, [trans1, trans2]) # test 3, add transactions with patch("server.Server.this_node_transactions", []) as mock_tnt: Server._updateList(add_trans=True, trans=trans1) self.assertEqual(mock_tnt, [trans1]) # test 4, sync this node's blockchain with patch("server.Server.blockchain", []) as mock_bc, \ patch("server.Server.block_helper.consensus", return_value=[genesis_block, block2]): Server._updateList(blockchain=True) self.assertEqual(mock_bc, [genesis_block, block2]) # test 5, sync this node's peer_nodes with patch("server.Server.peer_nodes", []) as mock_pn, \ patch("server.Server.node_helper.consensus", return_value=[host1, host2]): Server._updateList(peer_nodes=True) self.assertEqual(mock_pn, [host1, host2]) # test 6, sync this node's peer_nodes with patch("server.Server.peer_nodes", []) as mock_pn: Server._updateList(add_peer=True, peer=host1) self.assertEqual(mock_pn, [host1]) pass