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 __init__(self): # active_chain is initialized with a chain index and a list with genesis block in it self.active_chain: BlockChain = BlockChain( idx=Params.ACTIVE_CHAIN_IDX, chain=[Block.genesis_block()]) self.side_branches: Iterable[BlockChain] = [] self.orphan_blocks: Iterable[Block] = [] self.utxo_set: UTXO_Set = UTXO_Set() self.mempool: MemPool = MemPool() self.wallet: Wallet = Wallet.init_wallet(Params.WALLET_FILE) # PeerManager use Peer.init_peer() to read peer from PEERS_FILE or peer list in Params.py self.peerManager: PeerManager = PeerManager( Peer.init_peers(Params.PEERS_FILE)) self.mine_interrupt: threading.Event = threading.Event() self.ibd_done: threading.Event = threading.Event() self.chain_lock: threading.RLock = threading.RLock() self.peers_lock: threading.RLock = threading.RLock() self.gs = dict() ( self.gs["Block"], self.gs["Transaction"], self.gs["UnspentTxOut"], self.gs["Message"], self.gs["TxIn"], self.gs["TxOut"], self.gs["Peer"], self.gs["OutPoint"], ) = ( globals()["Block"], globals()["Transaction"], globals()["UnspentTxOut"], globals()["Message"], globals()["TxIn"], globals()["TxOut"], globals()["Peer"], globals()["OutPoint"], )
def __init__(self): self.active_chain: BlockChain = BlockChain( idx=Params.ACTIVE_CHAIN_IDX, chain=[Block.genesis_block()]) self.side_branches: Iterable[BlockChain] = [] self.orphan_blocks: Iterable[Block] = [] self.utxo_set: UTXO_Set = UTXO_Set() self.mempool: MemPool = MemPool() self.wallet: Wallet = Wallet.init_wallet(Params.WALLET_FILE) #self.peers: Iterable[Peer] = Peer.init_peers(Params.PEERS_FILE) self.peerManager: PeerManager = PeerManager( Peer.init_peers(Params.PEERS_FILE)) self.mine_interrupt: threading.Event = threading.Event() self.ibd_done: threading.Event = threading.Event() self.chain_lock: _thread.RLock = threading.RLock() self.peers_lock: _thread.RLock = threading.RLock() self.gs = dict() self.gs['Block'], self.gs['Transaction'], self.gs['UnspentTxOut'], self.gs['Message'], self.gs['TxIn'], self.gs['TxOut'], self.gs['Peer'], self.gs['OutPoint']= \ globals()['Block'], globals()['Transaction'], globals()['UnspentTxOut'], globals()['Message'], \ globals()['TxIn'], globals()['TxOut'], globals()['Peer'], globals()['OutPoint']
def validate_txn( self, utxo_set: BaseUTXO_Set, mempool: BaseMemPool = None, active_chain: BaseBlockChain = None, as_coinbase: bool = False, siblings_in_block: Iterable[NamedTuple] = None, #object allow_utxo_from_mempool: bool = True, ) -> bool: """ Validate a single transaction. Used in various contexts, so the parameters facilitate different uses. """ def validate_signature_for_spend(txin, utxo: UnspentTxOut): def build_spend_message(to_spend, pk, sequence, txouts) -> bytes: """This should be ~roughly~ equivalent to SIGHASH_ALL.""" return Utils.sha256d( Utils.serialize(to_spend) + str(sequence) + binascii.hexlify(pk).decode() + Utils.serialize(txouts)).encode() pubkey_as_addr = Wallet.pubkey_to_address(txin.unlock_pk) verifying_key = ecdsa.VerifyingKey.from_string( txin.unlock_pk, curve=ecdsa.SECP256k1) if pubkey_as_addr != utxo.to_address: raise TxUnlockError("Pubkey doesn't match") try: spend_msg = build_spend_message(txin.to_spend, txin.unlock_pk, txin.sequence, self.txouts) verifying_key.verify(txin.unlock_sig, spend_msg) except Exception: logger.exception(f'[ds] Key verification failed') raise TxUnlockError("Signature doesn't match") return True self.validate_basics(as_coinbase=as_coinbase) available_to_spend = 0 for idx, txin in enumerate(self.txins): utxo = utxo_set.get().get(txin.to_spend) if siblings_in_block: from ds.UTXO_Set import UTXO_Set utxo = utxo or UTXO_Set.find_utxo_in_list( txin, siblings_in_block) if allow_utxo_from_mempool: utxo = utxo or mempool.find_utxo_in_mempool(txin) if not utxo: raise TxnValidationError( f'Could find no UTXO for TxIn [{idx}] for txn: {self.id}', to_orphan=self) if active_chain is not None: if utxo.is_coinbase and \ (active_chain.height - utxo.height) < \ Params.COINBASE_MATURITY: raise TxnValidationError( f'Coinbase UTXO not ready for spend') try: validate_signature_for_spend(txin, utxo) except TxUnlockError: raise TxnValidationError( f'{txin} is not a valid spend of {utxo}') available_to_spend += utxo.value if available_to_spend < sum(o.value for o in self.txouts): raise TxnValidationError('Spend value is more than available') 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