Beispiel #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
Beispiel #2
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)
Beispiel #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()})
Beispiel #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