def _connect_block(block: Block, active_chain: BlockChain, utxo_set: UTXO_Set) -> bool: """ Add block after current chain. Return True if block added successfully, else False. """ if block.validate_block(active_chain, utxo_set) is None: return False logger.info( f"connecting the {len(active_chain.chain)+1}th block {block.id} to chain {active_chain.idx}" ) active_chain.chain.append(block) # Minipulate transactions in this block. # Remove txin from utxo_set, add txout to utxo_set. for tx in block.txns: if not tx.is_coinbase: for txin in tx.txins: utxo_set.rm_from_utxo(*txin.to_spend) for i, txout in enumerate(tx.txouts): utxo_set.add_to_utxo( txout, tx, i, tx.is_coinbase, len(active_chain.chain) ) return True
def disconnect_block(self, mempool: MemPool, utxo_set: UTXO_Set) -> Block: block = self.chain[-1] for tx in block.txns: if tx.txins[0].to_spend is not None: mempool.mempool[tx.id] = tx # Restore UTXO set to what it was before this block. for txin in tx.txins: if txin.to_spend: # Account for degenerate coinbase txins. utxo_set.add_to_utxo(*self.find_txout_for_txin(txin)) for i in range(len(tx.txouts)): utxo_set.rm_from_utxo(tx.id, i) logger.info(f'[ds] block {block.id} disconnected, recover transactions and UTXOs by it') return self.chain.pop()
def _connect_block(block: Block, active_chain: BlockChain, utxo_set: UTXO_Set) -> bool: if block.validate_block(active_chain, utxo_set) is None: return False logger.info( f'connecting the {len(active_chain.chain)+1}th block {block.id} to chain {active_chain.idx}' ) active_chain.chain.append(block) for tx in block.txns: if not tx.is_coinbase: for txin in tx.txins: utxo_set.rm_from_utxo(*txin.to_spend) for i, txout in enumerate(tx.txouts): utxo_set.add_to_utxo(txout, tx, i, tx.is_coinbase, len(active_chain.chain)) return True
def connect_block(self, block: Block, active_chain: BaseBlockChain, side_branches: Iterable[BaseBlockChain],\ mempool: MemPool, utxo_set: UTXO_Set, mine_interrupt: threading.Event,\ doing_reorg=False) -> bool: 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 if self.idx == Params.ACTIVE_CHAIN_IDX: logger.info(f'[ds] ##### connecting block at height #{len(self.chain)+1} chain with index #{self.idx}: {block.id} ') else: logger.info(f'[ds] ## connecting block to chain with index #{self.idx}: {block.id} ') self.chain.append(block) # If we added to the active chain, perform upkeep on utxo_set and mempool. if self.idx == Params.ACTIVE_CHAIN_IDX: for tx in block.txns: mempool.mempool.pop(tx.id, None) if not tx.is_coinbase: for txin in tx.txins: utxo_set.rm_from_utxo(*txin.to_spend) for i, txout in enumerate(tx.txouts): # print(txout) utxo_set.add_to_utxo(txout, tx, i, tx.is_coinbase, self.height) reorg_and_succeed = False if doing_reorg == False: reorg_and_succeed = _reorg_and_succeed(active_chain, side_branches, mempool, utxo_set, mine_interrupt) if reorg_and_succeed or self.idx == Params.ACTIVE_CHAIN_IDX: mine_interrupt.set() return -1 if reorg_and_succeed else True