def __init__(self, blockchain_db=None): # block db has [ block_hash - block | block_height - block_hash | BlockChain.LAST_BLOCK_KEY - block_hash ] self.__confirmed_block_db = blockchain_db if self.__confirmed_block_db is None: try: self.__confirmed_block_db = leveldb.LevelDB(conf.DEFAULT_LEVEL_DB_PATH) except leveldb.LevelDBError: raise leveldb.LevelDBError("Fail To Create Level DB(path): " + conf.DEFAULT_LEVEL_DB_PATH) # level DB에서 블럭을 읽어 들이며, 만약 levelDB에 블럭이 없을 경우 제네시스 블럭을 만든다 try: last_block_key = self.__confirmed_block_db.Get(BlockChain.LAST_BLOCK_KEY, True) except KeyError: last_block_key = None logging.debug("LAST BLOCK KEY : %s", last_block_key) if last_block_key: # DB에서 마지막 블럭을 가져와서 last_block 에 바인딩 self.last_block = Block() block_dump = self.__confirmed_block_db.Get(last_block_key) self.last_block.deserialize_block(block_dump) logging.debug("restore from last block hash(" + str(self.last_block.block_hash) + ")") logging.debug("restore from last block height(" + str(self.last_block.height) + ")") else: # 제네시스 블럭 생성 self.__add_genesisblock() # 블럭의 높이는 마지막 블럭의 높이와 같음 self.block_height = self.last_block.height # made block count as a leader self.__made_block_count = 0
def confirm_block(self, confirmed_block_hash): """인증완료후 Block을 Confirm해 줍니다. :param confirmed_block_hash: 인증된 블럭의 hash :return: Block 에 포함된 tx 의 갯수를 리턴한다. """ logging.debug("BlockChain::confirm_block") try: unconfirmed_block_byte = self.__confirmed_block_db.Get( BlockChain.UNCONFIRM_BLOCK_KEY) except KeyError: except_msg = "there is no unconfirmed block in this peer by: " + confirmed_block_hash logging.warning(except_msg) raise BlockchainError(except_msg) unconfirmed_block = Block() unconfirmed_block.deserialize_block(unconfirmed_block_byte) if unconfirmed_block.block_hash != confirmed_block_hash: logging.warning( "It's not possible to add block while check block hash is fail-" ) raise BlockchainError('확인하는 블럭 해쉬 값이 다릅니다.') logging.debug("unconfirmed_block.block_hash: " + unconfirmed_block.block_hash) logging.debug("confirmed_block_hash: " + confirmed_block_hash) logging.debug("unconfirmed_block.prev_block_hash: " + unconfirmed_block.prev_block_hash) unconfirmed_block.block_type = BlockType.confirmed # Block Validate and save Block self.add_block(unconfirmed_block) self.__confirmed_block_db.Delete(BlockChain.UNCONFIRM_BLOCK_KEY) return unconfirmed_block.confirmed_transaction_list.__len__()
def init_block_chain(self, is_leader=False): # level DB에서 블럭을 읽어 들이며, 만약 levelDB에 블럭이 없을 경우 제네시스 블럭을 만든다 try: last_block_key = self.__confirmed_block_db.Get( BlockChain.LAST_BLOCK_KEY, True) except KeyError: last_block_key = None logging.debug("LAST BLOCK KEY : %s", last_block_key) if last_block_key: # DB에서 마지막 블럭을 가져와서 last_block 에 바인딩 self.__last_block = Block(channel_name=self.__channel_name) block_dump = self.__confirmed_block_db.Get(last_block_key) self.__last_block.deserialize_block(block_dump) logging.debug("restore from last block hash(" + str(self.__last_block.block_hash) + ")") logging.debug("restore from last block height(" + str(self.__last_block.height) + ")") # 블럭의 높이는 마지막 블럭의 높이와 같음 if self.__last_block is None: self.__block_height = -1 else: self.__block_height = self.__last_block.height logging.debug(f"ENGINE-303 init_block_chain: {self.__block_height}")
def rebuild_blocks(self): # Genesis block 까지 순회하며 Block 정보를 복원한다. logging.info("re-build blocks from DB....") block = Block() prev_block_hash = self.last_block.block_hash total_tx = 0 while prev_block_hash != "": block_dump = self.__confirmed_block_db.Get( prev_block_hash.encode(encoding='UTF-8')) block.deserialize_block(block_dump) # Rebuild Block 코드 구간. 현재는 total_tx 만 구하고 있음 # TODO 향후에도 rebuild_blocks가 total_tx만 구하는 경우 이 로직은 제거 하고 total_tx 는 다른 방식으로 구하도록 한다. # logging.debug("re-build block: " + str(block.block_hash)) total_tx += block.confirmed_transaction_list.__len__() prev_block_hash = block.prev_block_hash logging.info("rebuilt blocks, total_tx: " + str(total_tx)) logging.info("block hash(" + self.last_block.block_hash + ") and height(" + str(self.last_block.height) + ")") return total_tx
def __find_block_by_key(self, key): block = Block() try: block_bytes = self.__confirmed_block_db.Get(key) block.deserialize_block(block_bytes) except KeyError: block = None return block
def __find_block_by_key(self, key): try: block_bytes = self.__confirmed_block_db.Get(key) block = Block(channel_name=self.__channel_name) block.deserialize_block(block_bytes) except KeyError as e: block = None logging.error(f"__find_block_by_key::KeyError block_hash({key}) error({e})") return block
def __add_genesisblock(self): """제네시스 블럭을 추가 합니다. :return: """ logging.info("Make Genesis Block....") block = Block(channel_name=self.__channel_name) block.block_status = BlockStatus.confirmed block.generate_block() # 제네시스 블럭을 추가 합니다. self.add_block(block)
def __add_genesisblock(self): """ 제네시스 블럭을 추가 합니다. :return: """ logging.info("Make Genesis Block....") block = Block() block.block_type = BlockType.confirmed block.generate_block() # 제네시스 블럭을 추가 합니다. self.add_block(block)
def generate_test_block(self): """ 임시 블럭 생성하는 메소드 :return: 임시 블럭 """ block = Block(channel_name=conf.LOOPCHAIN_DEFAULT_CHANNEL) for x in range(0, 10): tx = test_util.create_basic_tx(self.__peer_id, self.peer_auth) self.assertNotEqual(tx.tx_hash, "", "트랜잭션 생성 실패") self.assertTrue(block.put_transaction(tx), "Block에 트랜잭션 추가 실패") return block
def test_serialize_and_deserialize(self): """ 블럭 serialize and deserialize 테스트 """ block = self.__generate_block() test_dmp = block.serialize_block() block2 = Block(channel_name=conf.LOOPCHAIN_DEFAULT_CHANNEL) block2.deserialize_block(test_dmp) logging.debug("serialize block hash : %s , deserialize block hash %s", block.merkle_tree_root_hash, block2.merkle_tree_root_hash) self.assertEqual(block.merkle_tree_root_hash, block2.merkle_tree_root_hash, "블럭이 같지 않습니다 ")
def test_serialize_and_deserialize(self): """ 블럭 serialize and deserialize 테스트 """ block = self.generate_block() test_dmp = block.serialize_block() block2 = Block() block2.deserialize_block(test_dmp) logging.debug("serialize block hash : %s , deserialize block hash %s", block.merkle_tree_root_hash, block2.merkle_tree_root_hash) self.assertEqual(block.merkle_tree_root_hash, block2.merkle_tree_root_hash, "블럭이 같지 않습니다 ")
def generate_test_block(self): """ 임시 블럭 생성하는 메소드 :return: 임시 블럭 """ block = Block() for x in range(0, 10): tx = Transaction() hashed_value = tx.put_data("{args:[]}") self.assertNotEqual(hashed_value, "", "트랜잭션 생성 실패") self.assertTrue(block.put_transaction(tx), "Block에 트랜잭션 추가 실패") return block
def _rebuild_transaction_count_from_blocks(self): total_tx = 0 block_hash = self.__last_block.block_hash while block_hash != "": block_dump = self.__confirmed_block_db.Get(block_hash.encode(encoding='UTF-8')) block = Block(channel_name=self.__channel_name) block.deserialize_block(block_dump) # Count only normal block`s tx count, not genesis block`s if block.height > 0: total_tx += block.confirmed_tx_len block_hash = block.prev_block_hash return total_tx
def __init__(self, blockchain_db=None, channel_name=None): if channel_name is None: channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL self.__block_height = 0 self.__last_block = None self.__channel_name = channel_name self.__peer_id = None if ObjectManager().peer_service is not None: self.__peer_id = ObjectManager().peer_service.peer_id # block db has [ block_hash - block | block_height - block_hash | BlockChain.LAST_BLOCK_KEY - block_hash ] self.__confirmed_block_db = blockchain_db # logging.debug(f"BlockChain::init confirmed_block_db({self.__confirmed_block_db})") if self.__confirmed_block_db is None: try: self.__confirmed_block_db = leveldb.LevelDB( conf.DEFAULT_LEVEL_DB_PATH) except leveldb.LevelDBError: raise leveldb.LevelDBError("Fail To Create Level DB(path): " + conf.DEFAULT_LEVEL_DB_PATH) # level DB에서 블럭을 읽어 들이며, 만약 levelDB에 블럭이 없을 경우 제네시스 블럭을 만든다 try: last_block_key = self.__confirmed_block_db.Get( BlockChain.LAST_BLOCK_KEY, True) except KeyError: last_block_key = None logging.debug("LAST BLOCK KEY : %s", last_block_key) if last_block_key: # DB에서 마지막 블럭을 가져와서 last_block 에 바인딩 self.__last_block = Block(channel_name=self.__channel_name) block_dump = self.__confirmed_block_db.Get(last_block_key) self.__last_block.deserialize_block(block_dump) logging.debug("restore from last block hash(" + str(self.__last_block.block_hash) + ")") logging.debug("restore from last block height(" + str(self.__last_block.height) + ")") else: # 제네시스 블럭 생성 self.__add_genesisblock() # 블럭의 높이는 마지막 블럭의 높이와 같음 self.__block_height = self.__last_block.height # made block count as a leader self.__made_block_count = 0
def test_put_transaction(self): """ Block 에 여러 개 transaction 들을 넣는 것을 test. """ block = Block(channel_name=conf.LOOPCHAIN_DEFAULT_CHANNEL) tx_list = [] tx_size = 10 for x in range(0, tx_size): tx = test_util.create_basic_tx(self.__peer_id, self.peer_auth) tx2 = test_util.create_basic_tx(self.__peer_id, self.peer_auth) tx_list.append(tx2) self.assertNotEqual(tx.tx_hash, "", "트랜잭션 생성 실패") self.assertTrue(block.put_transaction(tx), "Block에 트랜잭션 추가 실패") self.assertTrue(block.put_transaction(tx_list), "Block에 여러 트랜잭션 추가 실패") self.assertEqual(block.confirmed_tx_len, tx_size * 2, "트랜잭션 사이즈 확인 실패")
def put_precommit_block(self, precommit_block: Block): # write precommit block to DB logging.debug( f"blockchain:put_precommit_block ({self.__channel_name}), hash ({precommit_block.block_hash})" ) if self.__last_block.height < precommit_block.height: self.__precommit_tx(precommit_block) util.logger.spam( f"blockchain:put_precommit_block:confirmed_transaction_list") # for tx in precommit_block.confirmed_transaction_list: # tx_hash = tx.tx_hash # util.logger.spam(f"blockchain.py:put_precommit_block:{tx_hash}") results = self.__confirmed_block_db.Put( BlockChain.PRECOMMIT_BLOCK_KEY, precommit_block.serialize_block()) util.logger.spam(f"result of to write to db ({results})") logging.info( f"ADD BLOCK PRECOMMIT HEIGHT : {precommit_block.height} , " f"HASH : {precommit_block.block_hash}, CHANNEL : {self.__channel_name}" ) else: results = None logging.debug( f"blockchain:put_precommit_block::this precommit block is not validate. " f"the height of precommit block must be bigger than the last block." f"(last block:{self.__last_block.height}/precommit block:{precommit_block.height})" ) return results
def score_invoke(self, _block: Block) -> dict or None: if conf.USE_EXTERNAL_SCORE: method = "icx_sendTransaction" transactions = [] for tx in _block.confirmed_transaction_list: data = tx.icx_origin_data transaction = {"method": method, "params": data} transactions.append(transaction) request = { 'block': { 'blockHeight': _block.height, 'blockHash': _block.block_hash, 'prevBlockHash': _block.prev_block_hash, 'timestamp': _block.time_stamp }, 'transactions': transactions } request = convert_params(request, ParamType.invoke) stub = StubCollection().icon_score_stubs[ChannelProperty().name] response = stub.sync_task().invoke(request) response_to_json_query(response) _block.commit_state[ ChannelProperty().name] = response['stateRootHash'] return response["txResults"] else: stub = StubCollection().score_stubs[ChannelProperty().name] response = stub.sync_task().score_invoke(_block) if response.code == message_code.Response.success: commit_state = pickle.loads(response.object) _block.commit_state = commit_state return json.loads(response.meta) return None
def __generate_block_data(self) -> Block: """ block data generate :return: unsigned block """ genesis = Block(channel_name="test_channel") genesis.generate_block() # Block 생성 block = Block(channel_name=conf.LOOPCHAIN_DEFAULT_CHANNEL) # Transaction(s) 추가 for x in range(0, 10): block.put_transaction( test_util.create_basic_tx(self.__peer_id, self.__peer_auth)) # Hash 생성 이 작업까지 끝내고 나서 Block을 peer에 보낸다 block.generate_block(genesis) return block
def __add_genesisblock(self, tx_info: dict = None): """제네시스 블럭을 추가 합니다. :param tx_info: Transaction data for making genesis block from an initial file :return: """ logging.info("Make Genesis Block....") block = Block(channel_name=self.__channel_name) block.block_status = BlockStatus.confirmed if tx_info: keys = tx_info.keys() if "accounts" in keys and "message" in keys: genesis_validator = get_genesis_tx_validator( self.__channel_name) is_valid, tx = genesis_validator.init_genesis_tx(tx_info) if is_valid: block.put_genesis_transaction(tx) else: raise TransactionInvalidError( message=f"The Genesis block data is invalid." f"That's why Genesis block couldn't be generated.") block.generate_block() # 제네시스 블럭을 추가 합니다. self.add_block(block)
def generate_block(self): """ 블럭 생성기 :return: 임의생성블럭 """ genesis = Block() genesis.generate_block() # Block 생성 block = Block() # Transaction(s) 추가 for x in range(0, 10): tx = Transaction() tx.put_data("{args:[]}") block.put_transaction(tx) # Hash 생성 이 작업까지 끝내고 나서 Block을 peer에 보낸다 block.generate_block(genesis) return block
def test_validate_block(self): """ GIVEN correct block and invalid signature block WHEN validate two block THEN correct block validate return True, invalid block raise exception """ # GIVEN # create correct block block = self.__generate_block() # WHEN THEN self.assertTrue(Block.validate(block), "Fail to validate block!") # GIVEN # create invalid block invalid_signature_block = self.__generate_invalid_block() # WHEN THEN with self.assertRaises(BlockInValidError): Block.validate(invalid_signature_block)
def test_put_transaction(self): """ Block 에 여러 개 transaction 들을 넣는 것을 test. """ block = Block() tx_list = [] tx_size = 10 for x in range(0, tx_size): tx = Transaction() tx2 = Transaction() hashed_value = tx.put_data("{args:[]}") tx2.put_data("{args:[]}") tx_list.append(tx2) self.assertNotEqual(hashed_value, "", "트랜잭션 생성 실패") self.assertTrue(block.put_transaction(tx), "Block에 트랜잭션 추가 실패") self.assertTrue(block.put_transaction(tx_list), "Block에 여러 트랜잭션 추가 실패") self.assertEqual(len(block.confirmed_transaction_list), tx_size * 2, "트랜잭션 사이즈 확인 실패")
def test_block_hash_must_be_the_same_regardless_of_the_commit_state(self): # ENGINE-302 # GIVEN block1 = Block(channel_name=conf.LOOPCHAIN_DEFAULT_CHANNEL) block2 = Block(channel_name=conf.LOOPCHAIN_DEFAULT_CHANNEL) # WHEN block1.commit_state = {"TEST": "TEST_VALUE1234"} block1.generate_block() block2.generate_block() util.logger.spam(f"block1 hash({block1.block_hash})") util.logger.spam(f"block1 hash({block2.block_hash})") # THEN self.assertEqual(block1.block_hash, block2.block_hash)
def test_block_prevent_tx_duplication(self): origin_send_tx_type = conf.CHANNEL_OPTION[ conf.LOOPCHAIN_DEFAULT_CHANNEL]["send_tx_type"] conf.CHANNEL_OPTION[conf.LOOPCHAIN_DEFAULT_CHANNEL][ "send_tx_type"] = conf.SendTxType.icx tx_validator.refresh_tx_validators() try: block = Block(channel_name=conf.LOOPCHAIN_DEFAULT_CHANNEL) client = IcxWallet() client.to_address = 'hxebf3a409845cd09dcb5af31ed5be5e34e2af9433' client.value = 1 hash_generator = get_tx_hash_generator( conf.LOOPCHAIN_DEFAULT_CHANNEL) validator = IconValidateStrategy(hash_generator) icx_origin = client.create_icx_origin() for i in range(10): tx = validator.restore(json.dumps(icx_origin), 'icx/score') block.put_transaction(tx) block.generate_block() self.assertEqual(block.confirmed_tx_len, 1) finally: conf.CHANNEL_OPTION[conf.LOOPCHAIN_DEFAULT_CHANNEL][ "send_tx_type"] = origin_send_tx_type tx_validator.refresh_tx_validators()
def test_block_rebuild(self): """ GIVEN 1Block with 3tx, and conf remove failed tx when in block WHEN Block call verify_through_score_invoke THEN all order 3tx must removed in block """ block = Block(conf.LOOPCHAIN_DEFAULT_CHANNEL) fail_tx_hash = None for i in range(3): tx = Transaction() tx.put_meta(Transaction.CHANNEL_KEY, conf.LOOPCHAIN_DEFAULT_CHANNEL) tx.put_data("aaaaa") tx.sign_hash(self.peer_auth) block.put_transaction(tx) if i == 2: fail_tx_hash = tx.tx_hash verify, need_rebuild, invoke_results = block.verify_through_score_invoke( True) self.assertTrue(need_rebuild) logging.debug(f"fail tx hash : {fail_tx_hash}") self.assertEqual(block.confirmed_tx_len, 2) for i, tx in enumerate(block.confirmed_transaction_list): self.assertNotEqual(i, 2, "index 2 must be deleted") self.assertNotEqual(tx.tx_hash, fail_tx_hash)
def test_transaction_merkle_tree_validate_block(self): """ 머클트리 검증 """ # 블럭을 검증 - 모든 머클트리의 내용 검증 block = self.__generate_block() mk_result = True for tx in block.confirmed_transaction_list: # FIND tx index idx = block.confirmed_transaction_list.index(tx) sm_result = Block.merkle_path(block, idx) mk_result &= sm_result # logging.debug("Transaction %i th is %s (%s)", idx, sm_result, tx.get_tx_hash()) # logging.debug("block mekletree : %s ", block.merkle_tree) self.assertTrue(mk_result, "머클트리검증 실패")
async def announce_confirmed_block(self, serialized_block, commit_state="{}"): try: confirmed_block = Block(channel_name=ChannelProperty().name) confirmed_block.deserialize_block(serialized_block) util.logger.spam( f"channel_inner_service:announce_confirmed_block\n " f"hash({confirmed_block.block_hash}), block_type({confirmed_block.block_type}), " f"block height({confirmed_block.height}), " f"commit_state({commit_state})") try: confirmed_block.commit_state = ast.literal_eval(commit_state) except Exception as e: logging.warning( f"channel_inner_service:announce_confirmed_block FAIL get commit_state_dict, " f"error by : {e}") if self._channel_service.block_manager.get_blockchain( ).block_height < confirmed_block.height: self._channel_service.block_manager.add_confirmed_block( confirmed_block) else: logging.debug( f"channel_inner_service:announce_confirmed_block " f"already synced block height({confirmed_block.height})") response_code = message_code.Response.success # stop subscribe timer if TimerService.TIMER_KEY_SUBSCRIBE in self._channel_service.timer_service.timer_list.keys( ): self._channel_service.timer_service.stop_timer( TimerService.TIMER_KEY_SUBSCRIBE) except Exception as e: logging.error(f"announce confirmed block error : {e}") response_code = message_code.Response.fail return response_code
def generate_block(self): """임시 블럭 생성하는 메소드 :return: 임시 블럭 """ block = Block(channel_name=conf.LOOPCHAIN_DEFAULT_CHANNEL) for x in range(10): tx = test_util.create_basic_tx(self.__peer_id, self.__peer_auth) block.put_transaction(tx) block.generate_block(self.chain.last_block) return block
def __add_single_tx_block_blockchain_return_tx(self): last_block = self.chain.last_block block = Block(channel_name=conf.LOOPCHAIN_DEFAULT_CHANNEL) tx = test_util.create_basic_tx(self.__peer_id, self.peer_auth) block.put_transaction(tx) logging.debug("tx_hash: " + tx.tx_hash) block.generate_block(last_block) block.block_status = BlockStatus.confirmed # add_block to blockchain self.assertTrue(self.chain.add_block(block), "Fail Add Block to BlockChain") return tx
def generate_block(self): """ 임시 블럭 생성하는 메소드 :return: 임시 블럭 """ block = Block() for x in range(10): tx = Transaction() tx.put_data("{args:[]}") block.put_transaction(tx) block.generate_block(self.chain.last_block) return block