Beispiel #1
0
    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
Beispiel #2
0
    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
Beispiel #4
0
    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"],
        )
Beispiel #5
0
    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']
Beispiel #6
0
    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
Beispiel #7
0
    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