Пример #1
0
def _rebuild_tx_pool_unlocked(r,
                              tx_pool: Mapping[bytes, Transaction],
                              b: Block) -> Dict[bytes, Transaction]:
    """Rebuild the tx_pool (ie keep only the valid transactions)
    using utxo-block of b to check for transaction validity. Return
    the updated transaction pool. Should be called with the
    utxo-block and tx_pool locks held."""
    utxo_block = {TransactionInput.loadb(i): TransactionOutput.loadb(o) for i, o \
        in r.hgetall("blockchain:utxo-block:".encode() + b.current_hash).items()}
    def is_unspent(txin: TransactionInput) -> bool:
        if txin in utxo_block:
            return True
        prev_tx = tx_pool.get(txin.transaction_id)
        if prev_tx is None:
            return False
        return all(is_unspent(i) for i in prev_tx.inputs)

    tx_to_remove: Set[Transaction] = set()
    for t in tx_pool.values():
        # A transaction is valid only if all its inputs are either in UTXO-block
        # or are outputs of other valid transactions in the pool
        if not all(is_unspent(i) for i in t.inputs):
            tx_to_remove.add(t)
    tx_pool = {tid: t for tid, t in tx_pool.items() if t not in tx_to_remove}
    if tx_to_remove:
        r.hdel("blockchain:tx_pool", *(t.id for t in tx_to_remove))

    return tx_pool
Пример #2
0
def test_transaction():
    gt = Transaction.genesis()
    assert gt.is_genesis()
    assert gt.verify()
    assert Transaction.loadb(gt.dumpb()) == gt
    assert Transaction.loads(gt.dumps()) == gt

    # TODO: Create transactions with only sender specified in the data (change_id())
    for td in TRANSACTION_DATA:
        sender_id = td.get("sender")
        sender = PUBKEYSB[sender_id] if sender_id is not None else None
        tod = td.get("outputs")
        outputs = [
            TransactionOutput(index=tod[0]["index"],
                              recipient=PUBKEYSB[tod[0]["recipient"]],
                              amount=tod[0]["amount"]),
            TransactionOutput(index=tod[1]["index"],
                              recipient=PUBKEYSB[tod[1]["recipient"]],
                              amount=tod[1]["amount"])
        ] if tod is not None else None
        if sender is not None and outputs is None:
            # If only sender is specified (apart from the necessary parameters),
            # temporarily pretend to be sender
            change_id(sender_id)
        t = Transaction(recipient=PUBKEYSB[td["recipient"]],
                        amount=td["amount"],
                        inputs=[TransactionInput(
                            transaction_id=transactions[tid["transaction_id"]].id \
                                if tid["transaction_id"] >= 0 else gt.id,
                            index=tid["index"]) for tid in td["inputs"]],
                        input_amount=td.get("input_amount"),
                        sender=sender if outputs is not None else None,
                        outputs=outputs,
                        id_=td.get("id_"),
                        signature=td.get("signature"))
        if sender is not None and outputs is None:
            # Change back
            change_id(NODE_ID)
        transactions.append(t)

        assert not t.is_genesis()
        assert t.verify()
        assert ("input_amount" not in td) or (t.outputs[1].amount
                                              == td["input_amount"] - t.amount)
        assert Transaction.loadb(t.dumpb()) == t
        assert Transaction.loads(t.dumps()) == t

    t = deepcopy(transactions[0])
    t.recipient = PUBKEYSB[0]
    assert not t.verify()
    t = deepcopy(transactions[0])
    t.amount += 100
    assert not t.verify()
    t = deepcopy(transactions[0])
    t.inputs[0].index = 2
    assert not t.verify()
    t = deepcopy(transactions[0])
    t.outputs[1].amount = -12
    assert not t.verify()
Пример #3
0
def _rebuild_utxo_tx_unlocked(r, b: Block, tx_pool: Mapping[bytes, Transaction]) -> None:
    """Reinitialize UTXO-tx as a copy of UTXO-block[b] and simulate adding all
    transactions in tx_pool. Should be called with the utxo-block and the
    utxo-tx locks held."""
    r.delete("blockchain:utxo-tx")
    utxo_tx = {TransactionInput.loadb(i): TransactionOutput.loadb(o) for i, o \
        in r.hgetall("blockchain:utxo-block:".encode() + b.current_hash).items()}
    while tx_pool:
        tx_to_remove: Set[Transaction] = set()
        for t in tx_pool.values():
            if all(i in utxo_tx for i in t.inputs):
                for i in t.inputs:
                    del utxo_tx[i]
                for o in t.outputs:
                    utxo_tx[TransactionInput(t.id, o.index)] = o
                tx_to_remove.add(t)
        tx_pool = {tid: t for tid, t in tx_pool.items() if t not in tx_to_remove}
    # NOTE: utxo_tx is not empty because UTXO-block[recv_block] is not empty
    r.hmset("blockchain:utxo-tx", {i.dumpb(): o.dumpb() for i, o in utxo_tx.items()})
Пример #4
0
def generate_transaction(recipient_id: int, amount: float, mute: bool = False) -> bool:
    """If possible (there are enough UTXOs) generate a new transaction giving
    amount NBC to recipient and the change back to us. If mute is True don't
    broadcast it."""
    logging.debug("Transaction requested: %f NBC to node %d", amount, recipient_id)
    sender = wallet.get_public_key().dumpb()
    recipient = wallet.get_public_key(recipient_id).dumpb()
    r = util.get_db()
    inputs: List[TransactionInput] = []
    input_amount = 0.0
    with r.lock("blockchain:tx_pool:lock"), \
         r.lock("blockchain:utxo-tx:lock"):
        for ib, ob in r.hgetall("blockchain:utxo-tx").items():
            o = TransactionOutput.loadb(ob)
            if o.recipient == sender:
                inputs.append(TransactionInput.loadb(ib))
                input_amount += o.amount
                if input_amount >= amount:
                    t = Transaction(recipient=recipient,
                                    amount=amount,
                                    inputs=inputs,
                                    input_amount=input_amount)
                    # Add to transaction pool
                    r.hset("blockchain:tx_pool", t.id, t.dumpb())
                    # "Add to wallet if mine"
                    r.hdel("blockchain:utxo-tx", *(i.dumpb() for i in t.inputs))
                    r.hmset("blockchain:utxo-tx", {TransactionInput(t.id, o.index).dumpb(): \
                                                       o.dumpb() for o in t.outputs})
                    break
        else:
            # Not enough UTXOs
            logging.error("Cannot send %f NBC to node %d (not enough coins)", amount, recipient_id)
            return False

    logging.debug("Generated transaction %s", util.bintos(t.id))
    _check_for_new_block()
    if not mute:
        logging.debug("Broadcasting transaction %s", util.bintos(t.id))
        chatter.broadcast_transaction(t, util.get_peer_ids())
    return True
Пример #5
0
def _check_for_new_block() -> None:
    """Check if there are at least CAPACITY transactions that can go in a new
    block (ie transactions in the pool with all their inputs in
    UTXO-block[last_block]). If so and if a miner is not already running, start
    mining for a new block. Should be called with NO locks held."""
    logging.debug("Checking for new block")
    CAPACITY = block.get_capacity()

    r = util.get_db()
    with r.lock("blockchain:last_block:lock"), \
         r.lock("blockchain:miner_pid:lock"), \
         r.lock("blockchain:tx_pool:lock"), \
         r.lock("blockchain:utxo-block:lock"):
        # NOTE: If a miner is running, we expect it to add a new block, so we
        # abort. If mining succeeds, this function will be called again by
        # new_recv_block(). If it fails (another valid block is received) this
        # will again be called by new_recv_block()
        miner_pidb = r.get("blockchain:miner_pid")
        if miner_pidb is not None:
            logging.debug("Miner already running with PID %d", util.btoui(miner_pidb))
            return

        tx_pool = {Transaction.loadb(tb) for tb in r.hvals("blockchain:tx_pool")}
        if len(tx_pool) < CAPACITY:
            logging.debug("Cannot create new block yet (not enough transactions)")
            return

        last_block = get_block()
        utxo_block = {TransactionInput.loadb(i): TransactionOutput.loadb(o) for i, o \
            in r.hgetall("blockchain:utxo-block:".encode() + last_block.current_hash).items()}
        new_block_tx: List[Transaction] = []
        # NOTE: Since there are >= CAPACITY transactions in the pool, and we
        # don't mind transaction inter-dependence in the same block, a new
        # block can be created, so this loop will terminate
        while True:
            for t in tx_pool:
                # Search for t.inputs in UTXO-block[last_block] as well as in new_block_tx
                if all(i in utxo_block or \
                       any(nt.id == i.transaction_id for nt in new_block_tx) for i in t.inputs):
                    new_block_tx.append(t)
                    if len(new_block_tx) == CAPACITY:
                        new_block = Block(index=last_block.index + 1,
                                          previous_hash=last_block.current_hash,
                                          transactions=new_block_tx)
                        # NOTE: We don't delete the new block_tx from the pool, because
                        # mining might fail. They will be deleted eventually when they
                        # enter the main branch.
                        miner_pid = new_block.finalize()
                        r.set("blockchain:miner_pid", util.uitob(miner_pid))
                        logging.debug("Miner started with PID %d", miner_pid)
                        return
            tx_pool.difference_update(new_block_tx)
Пример #6
0
def new_recv_transaction(t: Transaction) -> bool:
    logging.debug("Received transaction %s", util.bintos(t.id))
    if not t.verify():
        logging.debug("Transaction %s rejected (failed verification)", util.bintos(t.id))
        return False

    r = util.get_db()
    # OK 8  Reject if we already have matching tx in the pool, or in a block in the main branch
    referenced_txos = [i.dumpb() for i in t.inputs] # not empty because t.inputs != []
    with r.lock("blockchain:tx_pool:lock"), \
         r.lock("blockchain:utxo-tx:lock"):
        # NOTE: When receiving a new transaction, both queries and updates are
        # done against UTXO-transaction
        prev_outb = r.hmget("blockchain:utxo-tx", *referenced_txos)
        if any(i is None for i in prev_outb):
            logging.debug("Transaction %s rejected (UTXO not found)", util.bintos(t.id))
            return False
        prev_outputs = [TransactionOutput.loadb(outb) for outb in prev_outb]

        # OK 14 Reject if the sum of input values < sum of output values
        input_amount = sum(i.amount for i in prev_outputs)
        output_amount = sum(o.amount for o in t.outputs)
        if input_amount != output_amount:
            logging.debug("Transaction %s rejected (input amount != output amount)",
                          util.bintos(t.id))
            return False

        # OK 16 Verify the scriptPubKey accepts for each input; reject if any are bad
        if any(o.recipient != t.sender for o in prev_outputs):
            logging.debug("Transaction %s rejected (spending another's UTXO)", util.bintos(t.id))
            return False

        # OK 17 Add to transaction pool[7]
        r.hset("blockchain:tx_pool", t.id, t.dumpb())

        # OK 18 "Add to wallet if mine"
        r.hdel("blockchain:utxo-tx", *referenced_txos)
        # NOTE: Not empty because t.outputs != []
        new_utxos = {TransactionInput(t.id, o.index).dumpb(): o.dumpb() for o in t.outputs}
        r.hmset("blockchain:utxo-tx", new_utxos)

    logging.debug("Transaction %s accepted", util.bintos(t.id))
    _check_for_new_block()
    return True
Пример #7
0
def _validate_block_unlocked(r, b: Block) -> Optional[Tuple[Set[bytes], Dict[bytes, bytes]]]:
    """Should be called with the utxo-block lock held"""
    referenced_txos: Set[bytes] = set()  # the utxos from UTXO-block spent in block
    new_utxos: Dict[bytes, bytes] = {}
    for t in b.transactions:
        input_amount = 0.0
        for i in t.inputs:
            # Search for i in UTXO-block
            ib = i.dumpb()
            ob = r.hget("blockchain:utxo-block:".encode() + b.previous_hash, ib)
            if ob is None:
                # Not found in UTXO-block, search in new_utxos
                ob = new_utxos.get(ib)
                if ob is None:
                    logging.debug("Block %s rejected (UTXO not found)", util.bintos(b.current_hash))
                    return None
                del new_utxos[ib]
            else:
                # Avoid double-spending of a utxo from UTXO-block in the block
                if ib in referenced_txos:
                    logging.debug("Block %s rejected (double spending in the block)",
                                  util.bintos(b.current_hash))
                    return None
                referenced_txos.add(ib)
            o = TransactionOutput.loadb(ob)

            if o.recipient != t.sender:
                logging.debug("Block %s rejected (spending another's UTXO)",
                              util.bintos(b.current_hash))
                return None
            input_amount += o.amount

        if input_amount != sum(o.amount for o in t.outputs):
            logging.debug("Block %s rejected (input amount != output amount)",
                          util.bintos(b.current_hash))
            return None

        new_utxos.update({TransactionInput(t.id, o.index).dumpb(): o.dumpb() \
                for o in t.outputs})

    return (referenced_txos, new_utxos)
Пример #8
0
def new_recv_block(recv_block: Block, sender_id: Optional[int] = None, mute: bool = False) -> bool:
    """Handle a received block. Based on the bitcoin protocol rules for block
    handling at https://en.bitcoin.it/wiki/Protocol_rules. Should be called
    with NO locks held (except if sender_id is our node's id, in which case we
    expect to have been called by the miner, which holds the miner_pid lock)."""
    logging.debug("Received block %s", util.bintos(recv_block.current_hash))
    if not recv_block.verify():
        logging.debug("Block %s rejected (failed verification)",
                      util.bintos(recv_block.current_hash))
        return False

    r = util.get_db()
    with r.lock("blockchain:blocks:lock"), \
         r.lock("blockchain:last_block:lock"), \
         r.lock("blockchain:main_branch:lock"), \
         r.lock("blockchain:orphan_blocks:lock"), \
         r.lock("blockchain:tx_pool:lock"), \
         r.lock("blockchain:utxo-block:lock"), \
         r.lock("blockchain:utxo-tx:lock"):

        # NOTE: Comments like the one below are references to the bitcoin
        # protocol rules
        # OK 2  Reject if duplicate of block we have in any of the three categories
        if r.hexists("blockchain:blocks", recv_block.current_hash) or \
           r.sismember("blockchain:orphan_blocks:".encode() + recv_block.previous_hash,
                       recv_block.dumpb()):
            logging.debug("Block %s rejected (already exists)",
                          util.bintos(recv_block.current_hash))
            return False

        # Handle the genesis block
        if recv_block.is_genesis():
            r.hset("blockchain:blocks", recv_block.current_hash, recv_block.dumpb())
            t = recv_block.transactions[0]
            o = t.outputs[0]
            ib = TransactionInput(t.id, o.index).dumpb()
            ob = o.dumpb()
            r.hset("blockchain:utxo-block:".encode() + recv_block.current_hash, ib, ob)
            r.hset("blockchain:utxo-tx", ib, ob)
            r.sadd("blockchain:main_branch", recv_block.current_hash)
            _set_last_block_unlocked(r, recv_block)
            logging.debug("Genesis block accepted")
            return True

        # OK 11 Check if prev block (matching prev hash) is in main branch or side branches. If not,
        #       add this to orphan blocks, then query peer we got this from for 1st missing orphan
        #       block in prev chain; done with block
        prev_blockb = r.hget("blockchain:blocks", recv_block.previous_hash)
        if prev_blockb is None:
            logging.debug("Block %s is orphan", util.bintos(recv_block.current_hash))
            r.sadd("blockchain:orphan_blocks:".encode() + recv_block.previous_hash,
                   recv_block.dumpb())
            # TODO OPT: Unlock before requesting the block (it could take some time, although
            # the response is asynchronous of course
            if not mute:
                logging.debug("Requesting block %s", util.bintos(recv_block.previous_hash))
                # TODO OPT: Only ask the node we got this from, not everyone, to
                # avoid the flood of incoming blocks later
                chatter.get_blockid(recv_block.previous_hash,
                                    [sender_id] if sender_id is not None else util.get_peer_ids())
            return False

        prev_block = Block.loadb(prev_blockb)
        logging.debug("Previous block %s", util.bintos(prev_block.current_hash))
        if recv_block.index != prev_block.index + 1:
            logging.debug("Block %s rejected (wrong index)", util.bintos(recv_block.current_hash))
            return False

        # OK 15 Add block into the tree. There are three cases: 1. block further extends the main
        #       branch; 2. block extends a side branch but does not add enough difficulty to make
        #       it become the new main branch; 3. block extends a side branch and makes it the new
        #       main branch.
        last_block = get_block()
        if recv_block.previous_hash == last_block.current_hash:
            # OK Case 1 (b.previous_hash == last_block):
            logging.debug("Block %s extends the main branch", util.bintos(recv_block.current_hash))
            txos = _validate_block_unlocked(r, recv_block)
            if txos is None:
                return False
            referenced_txos, new_utxos = txos
            """
            # NOTE: This is the body of _validate_block_unlocked, annotated, for reference
            referenced_txos: Set[bytes] = set()  # the utxos from UTXO-block spent in recv_block
            new_utxos: Dict[bytes, bytes] = {}
            # OK 1  For all but the coinbase transaction, apply the following:
            for t in recv_block.transactions:
                # OK 1  For each input, look in the main branch to find the referenced output
                #       transaction. Reject if the output transaction is missing for any input.
                input_amount = 0.0
                for i in t.inputs:
                    # Search for i in UTXO-block
                    ib = i.dumpb()
                    ob = r.hget("blockchain:utxo-block:".encode() + recv_block.previous_hash, ib)
                    if ob is None:
                        # Not found in UTXO-block, search in new_utxos
                        ob = new_utxos.get(ib)
                        if ob is None:
                            return False
                        del new_utxos[ib]
                    else:
                        # Avoid double-spending of a utxo from UTXO-block in the block
                        if ib in referenced_txos:
                            return False
                        referenced_txos.add(ib)
                    o = TransactionOutput.loadb(ob)
                # OK 2  For each input, if we are using the nth output of the earlier transaction,
                #       but it has fewer than n+1 outputs, reject.
                # OK 4  Verify crypto signatures for each input; reject if any are bad
                    if o.recipient != t.sender:
                        return False
                # OK 5  For each input, if the referenced output has already been spent by a
                #       transaction in the main branch, reject
                # OK 7  Reject if the sum of input values < sum of output values
                    input_amount += o.amount
                if input_amount != sum(o.amount for o in t.outputs):
                    return False

                new_utxos.update({TransactionInput(t.id, o.index).dumpb(): o.dumpb() \
                        for o in t.outputs})
            """

            # OK 4  For each transaction, "Add to wallet if mine"
            # NOTE: referenced_txos and new_utxos are not empty since we got here
            _create_utxo_block_unlocked(r, recv_block, referenced_txos, new_utxos)

            # OK 5  For each transaction in the block, delete any matching transaction from the pool
            #       : of the transactions in the pool, keep only the ones that are valid using the
            #       new utxo-block to check for validity
            tx_pool = {t.id: t for t in \
                          [Transaction.loadb(tb) for tb in r.hvals("blockchain:tx_pool")]}
            # NOTE: There can't be double spending in the tx pool as it is now
            tx_pool = _rebuild_tx_pool_unlocked(r, tx_pool, recv_block)

            _rebuild_utxo_tx_unlocked(r, recv_block, tx_pool)

            # Add block to main branch
            r.hset("blockchain:blocks", recv_block.current_hash, recv_block.dumpb())
            r.sadd("blockchain:main_branch", recv_block.current_hash)

            _set_last_block_unlocked(r, recv_block)
            logging.debug("Block %s accepted", util.bintos(recv_block.current_hash))
        elif recv_block.index <= last_block.index:
            # OK Case 2 (b.previous_hash != last_block && b.index <= last_block.index)
            # : Add it without doing any validation because validating this now would require a lot
            # of work (actually simulating adding this to its prev as if extending the main branch).
            logging.debug("Block %s extends a side branch (not changing main)",
                          util.bintos(recv_block.current_hash))
            r.hset("blockchain:blocks", recv_block.current_hash, recv_block.dumpb())
        else:
            # OK Case 3 (b.previous_hash != last_block && b.index > last_block.index)
            # OK 1  Find the fork block on the main branch which this side branch forks off of
            #       : Ascend the side branch, the fork block is the first to be in the main branch
            logging.debug("Block %s extends a side branch (changing main)",
                          util.bintos(recv_block.current_hash))
            old_side_branch = [recv_block]    # the Blocks in the old side branch
            fork_block = Block.loadb(r.hget("blockchain:blocks", recv_block.previous_hash))
            while not r.sismember("blockchain:main_branch", fork_block.current_hash):
                old_side_branch.append(fork_block)
                fork_block = Block.loadb(r.hget("blockchain:blocks", fork_block.previous_hash))
            old_side_branch.reverse()   # starting from the child of the fork block
            # OK 2  Redefine the main branch to only go up to this fork block
            #       : Ascend from last_block up to the fork block
            old_main_branch: List[Block] = []    # the Blocks in the old main branch
            b = Block.loadb(r.hget("blockchain:blocks", last_block.current_hash))
            while b != fork_block:
                old_main_branch.append(b)
                b = Block.loadb(r.hget("blockchain:blocks", b.previous_hash))
            old_main_branch.reverse()   # starting from the child of the fork block
            logging.debug("Fork block %s", util.bintos(fork_block.current_hash))
            # OK 3  For each block on the side branch, from the child of the fork block to the leaf,
            #       add to the main branch:
            for osbi, b in enumerate(old_side_branch):
                # OK 1  Do "branch" checks 3-11
                #       : Why? we did them when first receiving the block. What could have changed?
                # OK 2  For all the transactions:
                txos = _validate_block_unlocked(r, b)
                if txos is None:
                    # Delete invalid blocks and abort
                    invalid_ids = [invalid.current_hash for invalid in old_side_branch[osbi:]]
                    r.hdel("blockchain:blocks", *invalid_ids)
                    return False
                referenced_txos, new_utxos = txos
                """
                # NOTE: This is the body of _validate_block_unlocked, annotated, for reference
                referenced_txos: Set[bytes] = set()  # the utxos from UTXO-block spent in b
                new_utxos: Dict[bytes, bytes] = {}
                for t in b.transactions:
                    # WP 1  For each input, look in the main branch to find the referenced output
                    #       transaction. Reject if the output transaction is missing for any input.
                    #       : Search for the referenced outputs in UTXO-block[previous_hash]
                    input_amount = 0.0
                    for i in t.inputs:
                        # Search for i in UTXO-block
                        ib = i.dumpb()
                        ob = r.hget("blockchain:utxo-block:".encode() + b.previous_hash, ib)
                        if ob is None:
                            # Not found in UTXO-block, search in new_utxos
                            ob = new_utxos.get(ib)
                            if ob is None:
                                # TODO: Undo any changes, delete invalid blocks and reject
                                raise NotImplementedError
                            del new_utxos[ib]
                        else:
                            # Avoid double-spending in the block
                            if ib in referenced_txos:
                                # TODO: Undo any changes, delete invalid blocks and reject
                                raise NotImplementedError
                            referenced_txos.add(ib)
                        o = TransactionOutput.loadb(ob)
                    # OK 2  For each input, if we are using the nth output of the earlier
                    #       transaction, but it has fewer than n+1 outputs, reject.
                    # WP 4  Verify crypto signatures for each input; reject if any are bad
                    #       : Check that t.sender == o.recipient for each utxo referenced
                        if o.recipient != t.sender:
                            # TODO: Undo any changes, delete invalid blocks and reject
                            raise NotImplementedError
                    # OK 5  For each input, if the referenced output has already been spent by a
                    #       transaction in the main branch, reject
                    # WP 7  Reject if the sum of input values < sum of output values
                    #       : Check that sum(inputs) == sum(outputs)
                        input_amount += o.amount
                    if input_amount != sum(o.amount for o in t.outputs):
                        # TODO: Undo any changes, delete invalid blocks and reject
                        raise NotImplementedError

                    new_utxos.update({TransactionInput(t.id, o.index).dumpb(): o.dumpb() for o \
                            in t.outputs})
                """

                # OK 5  For each transaction, "Add to wallet if mine"
                # NOTE: referenced_txos and new_utxos are not empty since we got here
                _create_utxo_block_unlocked(r, b, referenced_txos, new_utxos)

            # OK 5  For each block in the old main branch, from the leaf down to the child of the
            #       fork block:
            tx_pool = {t.id: t for t in \
                          [Transaction.loadb(tb) for tb in r.hvals("blockchain:tx_pool")]}
            for b in reversed(old_main_branch):
                # OK 1  For each non-coinbase transaction in the block:
                for t in b.transactions:
                    # OK 1  Apply "tx" checks 2-9, except in step 8, only look in the transaction
                    #       pool for duplicates, not the main branch
                    #       : Why? these have been checked already. There can't be double spending
                    #       transactions in the pool as it is at this point (current as of the old
                    #       main branch) + the old main branch, because they wouldn't have gotten
                    #       there in the first place.
                    # OK 2  Add to transaction pool if accepted, else go on to next transaction
                    tx_pool[t.id] = t

            # OK 6  For each block in the new main branch, from the child of the fork node to the
            #       leaf:
                # OK 1  For each transaction in the block, delete any matching transaction from the
                #       transaction pool
            #       : Of the transactions in the pool, keep only the ones that are valid using the
            #       new utxo-block to check for validity
            # NOTE: There can't be double spending in the tx pool as it is now,
            # because it consists of the tx in the previous tx pool and all the
            # tx in the old main branch, and all of these have already been
            # checked for double spending
            tx_pool = _rebuild_tx_pool_unlocked(r, tx_pool, recv_block)

            _rebuild_utxo_tx_unlocked(r, recv_block, tx_pool)

            # Update main_branch
            for b in old_main_branch:
                r.srem("blockchain:main_branch", b.current_hash)
            for b in old_side_branch:
                r.sadd("blockchain:main_branch", b.current_hash)

            r.hset("blockchain:blocks", recv_block.current_hash, recv_block.dumpb())
            _set_last_block_unlocked(r, recv_block)
            logging.debug("Block %s accepted", util.bintos(recv_block.current_hash))

        orphans = [Block.loadb(orphanb) for orphanb in \
                       r.smembers("blockchain:orphan_blocks:".encode() + recv_block.current_hash)]
        r.delete("blockchain:orphan_blocks:".encode() + recv_block.current_hash)

    logging.debug("Block time for %s %f", util.bintos(recv_block.current_hash),
                      time.time() - recv_block.timestamp)

    # OK 19 For each orphan block for which this block is its prev, run all these steps (including
    #       this one) recursively on that orphan
    for orphan in orphans:
        new_recv_block(orphan, sender_id)

    _check_for_new_block()
    return True