def check_block_place( cls, block: Block, active_chain: BlockChain, utxo_set: BaseUTXO_Set, mempool: BaseMemPool, side_branches: Iterable[BlockChain], ) -> int: if Block.locate_block(block.id, active_chain, side_branches)[0]: logger.debug( f"[p2p] ignore block that already be seen: {block.id}") return None # already seen block try: chain_idx = block.validate_block(active_chain, utxo_set, mempool, side_branches) except BlockValidationError as e: if e.to_orphan: logger.info( f"[p2p] block {block.id} failed validation as an orphan block" ) return -1 # orphan block else: logger.exception( f"[p2p] block {block.id} failed validation due to internal error in this block" ) return -2 # Internal error in this block if chain_idx != Params.ACTIVE_CHAIN_IDX and len( side_branches) < chain_idx: logger.info( f"[p2p] creating a new side branch (idx {chain_idx}) for block {block.id}" ) side_branches.append(BlockChain(idx=chain_idx, chain=[])) prev_block, prev_block_height, prev_block_chain_idx = Block.locate_block( block.prev_block_hash, active_chain, side_branches) if prev_block_chain_idx != Params.ACTIVE_CHAIN_IDX: # branch of a branch logger.info( f"[p2p] branch (idx {chain_idx}) of an existing side branch (idx {prev_block_chain_idx}) for block {block.id}" ) branch_fork_height = Block.locate_block( block.prev_block_hash, side_branches[prev_block_chain_idx - 1])[1] side_branches[chain_idx - 1].chain = list( side_branches[prev_block_chain_idx - 1].chain[:branch_fork_height]) return chain_idx
def handleBlockSyncReq(self, blockid: str, peer: Peer): logger.info(f"[p2p] receive BlockSyncReq from peer {peer}") #with self.chain_lock: height = Block.locate_block(blockid, self.active_chain)[1] if height is None: logger.info( f'[p2p] cannot find blockid {blockid}, and do nothing for this BlockSyncReq from peer {peer}' ) with self.chain_lock: block = copy.deepcopy(self.active_chain.chain[-1]) message = Message(Actions.BlockRev, block, Params.PORT_CURRENT) self.request.sendall(Utils.encode_socket_data(message)) return else: logger.info( f"[p2p] receive BlockSyncReq at height {height} from peer {peer}" ) with self.chain_lock: blocks = copy.deepcopy( self.active_chain.chain[height:(height + Params.CHUNK_SIZE)]) logger.info(f"[p2p] sending {len(blocks)} blocks to {peer}") message = Message(Actions.BlocksSyncGet, blocks, Params.PORT_CURRENT) self.request.sendall(Utils.encode_socket_data(message)) if (peer not in self.peerManager.getAllPeers()) and not (peer == Peer('127.0.0.1', Params.PORT_CURRENT) or \ peer == Peer('localhost', Params.PORT_CURRENT) or \ peer.ip == '0.0.0.0' or \ peer == Peer(Params.PUBLIC_IP, Params.PORT_CURRENT)): if Utils.is_peer_valid(peer): with self.peers_lock: self.peerManager.add(peer) self.sendPeerExtend() else: self.peerManager.block(peer)
def _reorg_and_succeed(active_chain: BaseBlockChain, side_branches: Iterable[BaseBlockChain], \ mempool: MemPool, utxo_set:UTXO_Set, \ mine_interrupt: threading.Event) -> bool: def _do_reorg(branch_idx: int, side_branches: Iterable[BaseBlockChain], active_chain: BaseBlockChain, \ fork_height: int, mempool: MemPool, utxo_set:UTXO_Set, \ mine_interrupt: threading.Event) -> bool: branch_chain = side_branches[branch_idx - 1] fork_block = active_chain.chain[fork_height - 1] def disconnect_to_fork(active_chain: BaseBlockChain = active_chain, fork_block: Block = fork_block): while active_chain.chain[-1].id != fork_block.id: yield active_chain.disconnect_block(mempool, utxo_set) old_active = list(disconnect_to_fork(active_chain, fork_block))[::-1] assert branch_chain.chain[0].prev_block_hash == active_chain.chain[-1].id def rollback_reorg(): list(disconnect_to_fork(active_chain, fork_block)) for block in old_active: assert active_chain.connect_block(block, active_chain, side_branches, mempool, utxo_set, \ mine_interrupt, \ doing_reorg=True) == True for block in branch_chain.chain: if not active_chain.connect_block(block, active_chain, side_branches, mempool, utxo_set, \ mine_interrupt, doing_reorg=True): logger.info(f'[ds] reorg of branch {branch_idx} to active_chain failed, decide to rollback') rollback_reorg() return False branch_chain.chain = list(old_active) logger.info(f'[ds] chain reorg successful with new active_chain height {active_chain.height} and top block id {active_chain.chain[-1].id}') return True reorged = False frozen_side_branches = list(side_branches) for _, branch_chain in enumerate(frozen_side_branches): branch_idx = branch_chain.idx fork_block, fork_height, _ = Block.locate_block(branch_chain.chain[0].prev_block_hash, active_chain) active_height = active_chain.height branch_height_real = branch_chain.height + fork_height if branch_height_real > active_height: logger.info(f'[ds] decide to reorg branch {branch_idx} with height {branch_height_real} to active_chain with real height {active_height}') reorged |= _do_reorg(branch_idx, side_branches, active_chain, fork_height, mempool, \ utxo_set, mine_interrupt) if reorged is True: return reorged return reorged
def do_connect_block_and_after(cls, block: Block, chain_idx, active_chain: BlockChain, side_branches: Iterable[BlockChain], \ mempool: BaseMemPool, utxo_set: BaseUTXO_Set, mine_interrupt: threading.Event) -> bool: if int(chain_idx) == int(Params.ACTIVE_CHAIN_IDX): if block.block_subsidy_fees != Block.get_block_subsidy( active_chain) + block.calculate_fees(utxo_set): #logger.info(f'{block.block_subsidy_fees} != {Block.get_block_subsidy(active_chain)} + {block.calculate_fees(utxo_set)}') logger.info( f'[p2p] subsidy and fees of this block are not right, so discard this block and return.' ) #logger.info(f'after check subsid_fees, and give out a logger.exception') return False else: #logger.info(f'[p2p] subsidy and fees of this block are right.') pass connect_block_success = active_chain.connect_block(block, active_chain, \ side_branches, \ mempool, utxo_set, mine_interrupt) else: connect_block_success = side_branches[chain_idx-1].connect_block(block, \ active_chain, side_branches, \ mempool, utxo_set, mine_interrupt) if connect_block_success is not False: if connect_block_success is not True: # -1, success and reorg #logger.info(f'[p2p] a successful reorg is found, begin to deal with {len(side_branches)} side branches') for branch_chain in side_branches: #logger.info(f'[p2p] number of blocks before slim side branch: {len(branch_chain.chain)}') #TCPHandler.printBlockchainIDs(branch_chain, '[p2p] side branch removed from active chain ') fork_height_from_end = 0 for block in branch_chain.chain[::-1]: if not Block.locate_block(block.id, active_chain)[0]: if not Block.locate_block(block.prev_block_hash, active_chain)[0]: fork_height_from_end += 1 else: break else: branch_chain.chain = [] logger.info( f'[p2p] the whole body of this branch chain is in active chain' ) break if fork_height_from_end >= branch_chain.height and branch_chain.height != 0: branch_chain.chain = [] logger.info( f'[p2p] all blocks are orphans to the current active chain' ) else: for num_to_pop in range( 1, branch_chain.height - fork_height_from_end): branch_chain.chain.pop(0) #logger.info(f'[p2p] number of blocks after slim side branch: {len(branch_chain.chain)}') side_branches_to_discard = [] for branch_chain in side_branches: if branch_chain.chain == []: side_branches_to_discard.append(branch_chain) continue fork_block, fork_height, _ = Block.locate_block( branch_chain.chain[0].prev_block_hash, active_chain) if fork_block is None: side_branches_to_discard.append(branch_chain) branch_height_real = branch_chain.height + fork_height if active_chain.height - branch_height_real > Params.MAXIMUM_ALLOWABLE_HEIGHT_DIFF: side_branches_to_discard.append(branch_chain) if len(side_branches_to_discard) > 0: logger.info( f'[p2p] ## delete {len(side_branches_to_discard)} side branches beyond MAXIMUM_ALLOWABLE_HEIGHT_DIFF' ) for branch_chain in side_branches_to_discard: side_branches.remove(branch_chain) for index, branch_chain in enumerate(side_branches, 1): branch_chain.index = index else: logger.exception(f'[p2p] connect_block returned a False value') return True
def handleBlockRev(self, block: Block, peer: Peer): if not isinstance(block, Block): logger.info(f'[p2p] {block} is not a Block') return else: if peer != Peer('127.0.0.1', Params.PORT_CURRENT): logger.info( f"[p2p] received block {block.id} from peer {peer}") with self.chain_lock: chain_idx = TCPHandler.check_block_place(block, self.active_chain, self.utxo_set, self.mempool, \ self.side_branches) ret_outside_lock = False if chain_idx is not None and chain_idx >= 0: ret_outside_lock = TCPHandler.do_connect_block_and_after(block, chain_idx, self.active_chain, self.side_branches, \ self.mempool, self.utxo_set, self.mine_interrupt) if not ret_outside_lock: #logger.info(f'####### out of chain_lock: {chain_use_id} of handleBlockRev') return if not Block.locate_block(block.id, self.active_chain, self.side_branches)[0]: return #logger.info(f'####### out of chain_lock: {chain_use_id} of handleBlockRev') if ret_outside_lock is True: if len(self.active_chain.chain ) % Params.SAVE_PER_SIZE == 0 or len( self.active_chain.chain) <= 5: Persistence.save_to_disk(self.active_chain) if chain_idx is not None and chain_idx >= 0: peers = self.peerManager.getPeers() if len(peers) > 0: for _peer in random.sample(peers, min(len(peers), 5)): if _peer != peer: ret = Utils.send_to_peer( Message(Actions.BlockRev, block, Params.PORT_CURRENT), _peer) if ret == 1: if _peer in peers: try: with self.peers_lock: self.peerManager.block(_peer) except: pass elif ret != 0: with self.peers_lock: self.peerManager.addLog(_peer, 1) else: with self.peers_lock: self.peerManager.addLog(_peer, 0) self.sendPeerExtend() elif chain_idx is None: logger.info( f'[p2p] already seen block {block.id}, and do nothing') elif chain_idx == -1: # case of orphan block message = Message(Actions.TopBlocksSyncReq, 50, Params.PORT_CURRENT) if peer == Peer('127.0.0.1', Params.PORT_CURRENT): getpeers = self.peerManager.getPeers(2) if len(getpeers) > 0: peer = random.sample(getpeers, 1)[0] else: return try: with socket.socket( socket.AF_INET, socket.SOCK_STREAM ) as s: #socket.create_connection(peer(), timeout=25) as s: s.connect(peer()) s.sendall(Utils.encode_socket_data(message)) logger.info( f'[p2p] succeed to send TopBlocksSyncReq to {peer}' ) msg_len = int(binascii.hexlify(s.recv(4) or b'\x00'), 16) data = b'' while msg_len > 0: tdat = s.recv(1024) data += tdat msg_len -= len(tdat) s.close() except ConnectionRefusedError: self.peerManager.block(peer) except: self.peerManager.addLog(peer, 1) else: self.peerManager.addLog(peer, 0) message = Utils.deserialize(data.decode(), self.gs) if data else None if message: blocks = message.data if blocks[0].prev_block_hash: if not Block.locate_block( blocks[0].prev_block_hash, self.active_chain, self.side_branches)[0]: logger.info( f"received sync blocks for the orphan block in handleBlockRev, but the first blocks's pre_block_hash cannot be seen on the chains" ) self.peerManager.block(peer) return else: blocks.pop(0) if not Block.locate_block( blocks[0].prev_block_hash, self.active_chain, self.side_branches)[0]: logger.info( f"received sync blocks for the orphan block in handleBlockRev, but the first blocks's pre_block_hash cannot be seen on the chains" ) self.peerManager.block(peer) return message = Message(message.action, message.data, Params.PORT_CURRENT, peer) ret = Utils.send_to_peer(message, Peer('127.0.0.1', Params.PORT_CURRENT), itself=True) if ret != 0: logger.info( f'[p2p] cannot send data to itself, and its current port is {Params.PORT_CURRENT}' ) else: #logger.info(f'[p2p] send BlocksSyncGet to itself') pass else: logger.info(f'[p2p] recv nothing from peer {peer}')
def handleBlockSyncGet(self, blocks: Iterable[Block], peer: Peer): if peer != Peer('127.0.0.1', Params.PORT_CURRENT): logger.info( f"[p2p] receive {len(blocks)} blocks for BlockSyncGet from {peer}" ) new_blocks = [ block for block in blocks if not Block.locate_block( block.id, self.active_chain, self.side_branches)[0] ] logger.info( f'[p2p] {len(new_blocks)} of {len(blocks)} blocks from {peer} is new' ) #if not new_blocks: # logger.info('[p2p] initial block download complete') # self.ibd_done.set() # return #else: # self.ibd_done.clear() if not TCPHandler.check_blocks_headers(new_blocks): return with self.chain_lock: for block in new_blocks: if Block.locate_block(block.id, self.active_chain, self.side_branches)[0]: new_blocks.pop(0) else: break if not new_blocks: return chain_idx = TCPHandler.check_block_place(new_blocks[0], self.active_chain, self.utxo_set, \ self.mempool, self.side_branches) if chain_idx is None: logger.info( f'received blocks have been seen in BlockSyncGet, do nothing and return' ) return if chain_idx <= -1: logger.info(f'[p2p] orphan or wrong blocks') if peer != Peer('127.0.0.1', Params.PORT_CURRENT): with self.peers_lock: self.peerManager.block(peer) return if chain_idx >= 1: # if is side branches, append the blocks (one block left) to the side branches directly logger.info( f'[p2p] just append {len(new_blocks)-1} blocks to side branch {chain_idx}, leaving one block to ' f'be coped with method do_connect_block_and_after') while len(new_blocks) >= 2: self.side_branches[chain_idx - 1].chain.append( new_blocks.pop(0)) for block in new_blocks: if not TCPHandler.do_connect_block_and_after(block, chain_idx, self.active_chain, self.side_branches, \ self.mempool, self.utxo_set, self.mine_interrupt): return if chain_idx == Params.ACTIVE_CHAIN_IDX: if len(self.active_chain.chain) % Params.SAVE_PER_SIZE == 0 or len( self.active_chain.chain) <= 5: Persistence.save_to_disk(self.active_chain) logger.info( f'[p2p] current chain height {self.active_chain.height}, and continue initial block download ... ' ) if peer not in self.peerManager.getAllPeers(): if peer== Peer('127.0.0.1', Params.PORT_CURRENT) or \ peer == Peer('localhost', Params.PORT_CURRENT) or \ peer.ip == '0.0.0.0' or \ peer == Peer(Params.PUBLIC_IP, Params.PORT_CURRENT): return if Utils.is_peer_valid(peer): with self.peers_lock: self.peerManager.add(peer) #self.peers.append(peer) self.sendPeerExtend() else: self.peerManager.block(peer) if peer == Peer('127.0.0.1', Params.PORT_CURRENT): logger.info(f'peer in handleBlockSyncGet cannot be itself') return top_id_chain_idx = new_blocks[-1].id message = Message(Actions.BlocksSyncReq, top_id_chain_idx, Params.PORT_CURRENT) try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect(peer()) s.sendall(Utils.encode_socket_data(message)) logger.info(f'[p2p] succeed to send BlocksSyncReq to {peer}') msg_len = int(binascii.hexlify(s.recv(4) or b'\x00'), 16) data = b'' while msg_len > 0: tdat = s.recv(1024) data += tdat msg_len -= len(tdat) s.close() except ConnectionRefusedError: self.peerManager.block(peer) except: self.peerManager.addLog(peer, 1) else: self.peerManager.addLog(peer, 0) message = Utils.deserialize(data.decode(), self.gs) if data else None if message: logger.info( f'[p2p] received blocks for sync blocks from peer {peer}') message = Message(message.action, message.data, Params.PORT_CURRENT, peer) ret = Utils.send_to_peer(message, Peer('127.0.0.1', Params.PORT_CURRENT), itself=True) if ret != 0: logger.info( f'[p2p] cannot send data to itself, and its current port is {Params.PORT_CURRENT}' ) else: logger.info(f'[p2p] recv nothing from peer {peer}')