Exemple #1
0
def test_uto_apply_transaction_on_non_coinbase_transaction():
    public_key = SECP256k1PublicKey(b'x' * 64)

    output_0 = Output(40, public_key)
    output_1 = Output(34, public_key)
    output_2 = Output(30, public_key)

    previous_transaction_hash = b'a' * 32

    unspent_transaction_outs = immutables.Map({
        OutputReference(previous_transaction_hash, 0):
        output_0,
        OutputReference(previous_transaction_hash, 1):
        output_1,
    })

    transaction = Transaction(inputs=[
        Input(
            OutputReference(previous_transaction_hash, 1),
            SECP256k1Signature(b'y' * 64),
        )
    ],
                              outputs=[output_2])

    result = uto_apply_transaction(unspent_transaction_outs,
                                   transaction,
                                   is_coinbase=False)

    assert OutputReference(previous_transaction_hash, 0) in result
    assert OutputReference(previous_transaction_hash, 1) not in result  # spent
    assert OutputReference(transaction.hash(), 0) in result

    assert result[OutputReference(previous_transaction_hash, 0)] == output_0
    assert result[OutputReference(transaction.hash(), 0)] == output_2
Exemple #2
0
def test_uto_apply_transaction_on_coinbase():
    public_key = SECP256k1PublicKey(b'x' * 64)

    output_0 = Output(40, public_key)
    output_1 = Output(34, public_key)

    unspent_transaction_outs = immutables.Map()

    transaction = Transaction(inputs=[
        Input(
            construct_reference_to_thin_air(),
            CoinbaseData(0, b'coinbase of the first block'),
        )
    ],
                              outputs=[output_0, output_1])

    result = uto_apply_transaction(unspent_transaction_outs,
                                   transaction,
                                   is_coinbase=True)

    assert OutputReference(transaction.hash(), 0) in result
    assert OutputReference(transaction.hash(), 1) in result

    assert result[OutputReference(transaction.hash(), 0)] == output_0
    assert result[OutputReference(transaction.hash(), 1)] == output_1
Exemple #3
0
def test_pkb_apply_transaction_on_non_coinbase_transaction():
    public_key_0 = SECP256k1PublicKey(b'\x00' * 64)
    public_key_1 = SECP256k1PublicKey(b'\x01' * 64)
    public_key_2 = SECP256k1PublicKey(b'\x02' * 64)

    output_0 = Output(40, public_key_0)
    output_1 = Output(34, public_key_1)
    output_3 = Output(66, public_key_1)
    final_output = Output(30, public_key_2)

    previous_transaction_hash = b'a' * 32

    unspent_transaction_outs = immutables.Map({
        OutputReference(previous_transaction_hash, 0):
        output_0,
        OutputReference(previous_transaction_hash, 1):
        output_1,
        OutputReference(previous_transaction_hash, 2):
        output_3,
    })

    public_key_balances = immutables.Map({
        public_key_0:
        PKBalance(0, []),
        public_key_1:
        PKBalance(100, [
            OutputReference(previous_transaction_hash, 1),
            OutputReference(previous_transaction_hash, 2),
        ]),
    })

    transaction = Transaction(inputs=[
        Input(
            OutputReference(previous_transaction_hash, 1),
            SECP256k1Signature(b'y' * 64),
        )
    ],
                              outputs=[final_output])

    result = pkb_apply_transaction(unspent_transaction_outs,
                                   public_key_balances,
                                   transaction,
                                   is_coinbase=False)

    assert result[
        public_key_0].value == 0  # not referenced in the transaction under consideration
    assert result[public_key_0].output_references == []

    assert result[public_key_1].value == 100 - 34
    assert result[public_key_1].output_references == [
        OutputReference(previous_transaction_hash, 2)
    ]

    assert result[
        public_key_2].value == 30  # the value of the transaction output
    assert result[public_key_2].output_references == [
        OutputReference(transaction.hash(), 0)
    ]
Exemple #4
0
def test_construct_block_for_mining_with_non_coinbase_transactions():
    coinstate = _read_chain_from_disk(5)

    transactions = [
        Transaction(
            inputs=[
                Input(
                    OutputReference(
                        coinstate.at_head.block_by_height[0].transactions[0].
                        hash(), 0),
                    SECP256k1Signature(b'y' * 64),
                )
            ],
            outputs=[Output(9 * SASHIMI_PER_COIN, example_public_key)],
        )
    ]

    block = construct_block_for_mining(coinstate, transactions,
                                       example_public_key, 1231006505,
                                       b'skepticoin is digital bitcoin', 1234)

    assert 6 == block.height
    assert 2 == len(block.transactions)
    assert 11 * SASHIMI_PER_COIN == block.transactions[0].outputs[
        0].value  # 10 mined + 1 fee
    assert example_public_key == block.transactions[0].outputs[0].public_key
    assert 1231006505 == block.timestamp
    assert b'skepticoin is digital bitcoin' == block.transactions[0].inputs[
        0].signature.signature
    assert 1234 == block.nonce
Exemple #5
0
def test_pkb_apply_transaction_on_coinbase():
    public_key_0 = SECP256k1PublicKey(b'0' * 64)
    public_key_1 = SECP256k1PublicKey(b'1' * 64)

    output_0 = Output(40, public_key_0)
    output_1 = Output(34, public_key_1)

    unspent_transaction_outs = immutables.Map()
    public_key_balances = immutables.Map()

    transaction = Transaction(inputs=[
        Input(
            construct_reference_to_thin_air(),
            CoinbaseData(0, b'coinbase of the first block'),
        )
    ],
                              outputs=[output_0, output_1])

    result = pkb_apply_transaction(unspent_transaction_outs,
                                   public_key_balances,
                                   transaction,
                                   is_coinbase=True)

    assert public_key_0 in result
    assert public_key_1 in result

    assert result[public_key_0].value == 40
    assert result[public_key_1].value == 34
Exemple #6
0
    def add_transaction_to_pool(self, transaction: Transaction) -> bool:
        with self.lock:
            self.local_peer.logger.info(
                "%15s ChainManager.add_transaction_to_pool(%s)" %
                ("", human(transaction.hash())))

            try:
                validate_non_coinbase_transaction_by_itself(transaction)

                assert self.coinstate.current_chain_hash

                validate_non_coinbase_transaction_in_coinstate(
                    transaction, self.coinstate.current_chain_hash,
                    self.coinstate)

                # Horribly inefficiently implemented (AKA 'room for improvement)
                validate_no_duplicate_output_references_in_transactions(
                    self.transaction_pool + [transaction])

                #  we don't do validate_no_duplicate_transactions here (assuming it's just been done before
                #  add_transaction_to_pool).

            except ValidateTransactionError as e:
                # TODO: dirty hack at this particular point... to allow for e.g. out-of-order transactions to not take
                # down the whole peer, but this should more specifically match for a short list of OK problems.
                self.local_peer.logger.warning("%15s INVALID transaction %s" %
                                               ("", str(e)))
                self.local_peer.disk_interface.save_transaction_for_debugging(
                    transaction)

                return False  # not successful

            self.transaction_pool.append(transaction)

        return True  # successfully added
Exemple #7
0
def test_validate_non_coinbase_transaction_by_itself_no_inputs():
    transaction = Transaction(
        inputs=[],
        outputs=[Output(30, example_public_key)],
    )

    with pytest.raises(ValidateTransactionError, match=".*No inputs.*"):
        validate_non_coinbase_transaction_by_itself(transaction)
Exemple #8
0
def test_transaction_serialization():
    trans = Transaction(
        inputs=[
            Input(output_reference=OutputReference(b"b" * 32, 1234),
                  signature=SECP256k1Signature(b"b" * 64))
        ],
        outputs=[Output(value=1582, public_key=SECP256k1PublicKey(b"g" * 64))],
    )
    serialize_and_deserialize(trans)
Exemple #9
0
def test_validate_non_coinbase_transaction_by_itself():
    transaction = Transaction(
        inputs=[Input(
            OutputReference(b'a' * 32, 1),
            SignableEquivalent(),
        )],
        outputs=[Output(30, example_public_key)])

    with pytest.raises(ValidateTransactionError,
                       match=".*Non-signature Signature class used.*"):
        validate_non_coinbase_transaction_by_itself(transaction)
Exemple #10
0
def test_validate_non_coinbase_transaction_by_itself_max_size():
    transaction = Transaction(inputs=[
        Input(
            OutputReference(b'a' * 32, 1),
            SECP256k1Signature(b'y' * 64),
        )
    ] * 30_000,
                              outputs=[Output(30, example_public_key)])

    with pytest.raises(ValidateTransactionError, match=".*MAX_BLOCK_SIZE.*"):
        validate_non_coinbase_transaction_by_itself(transaction)
Exemple #11
0
def test_transaction_repr():
    trans = Transaction(
        inputs=[
            Input(output_reference=OutputReference(b"b" * 32, 1234),
                  signature=SECP256k1Signature(b"b" * 64))
        ],
        outputs=[Output(value=1582, public_key=SECP256k1PublicKey(b"g" * 64))],
    )

    assert repr(
        trans
    ) == "Transaction #4025f3f13790dc96d857562dabcdd257ee9dfd95ce126e11d8cbbe64ac1bbec4"
Exemple #12
0
def test_validate_non_coinbase_transaction_by_itself_is_not_coinbase():
    transaction = Transaction(inputs=[
        Input(
            OutputReference(b'\x00' * 32, 0),
            SECP256k1Signature(b'y' * 64),
        )
    ],
                              outputs=[Output(30, example_public_key)])

    with pytest.raises(ValidateTransactionError,
                       match=".*null-reference in non-coinbase transaction.*"):
        validate_non_coinbase_transaction_by_itself(transaction)
Exemple #13
0
def test_validate_non_coinbase_transaction_by_itself_max_total_output():
    transaction = Transaction(
        inputs=[
            Input(
                OutputReference(b'a' * 32, 1),
                SECP256k1Signature(b'y' * 64),
            )
        ],
        outputs=[Output(21_000_000 * SASHIMI_PER_COIN, example_public_key)])

    with pytest.raises(ValidationError, match=".out of range.*"):
        validate_non_coinbase_transaction_by_itself(transaction)
Exemple #14
0
def pkb2_apply_transaction(
    unspent_transaction_outs: immutables.Map[OutputReference, Output],
    public_key_balances: immutables.Map[PublicKey, PKBalance2],
    transaction: Transaction,
    is_coinbase: bool,
) -> immutables.Map[PublicKey, PKBalance2]:
    with public_key_balances.mutate() as mutable_public_key_balances:
        # for coinbase we must skip the input-removal because the input references "thin air" rather than an output.
        if not is_coinbase:
            for input in transaction.inputs:
                previously_unspent_output: Output = unspent_transaction_outs[
                    input.output_reference]

                public_key = previously_unspent_output.public_key
                mutable_public_key_balances[public_key] = PKBalance2(
                    value=mutable_public_key_balances[public_key].value -
                    previously_unspent_output.value,
                    all_output_references=mutable_public_key_balances[
                        public_key].all_output_references,
                    unspent_output_references=([
                        to for to in mutable_public_key_balances[public_key].
                        unspent_output_references
                        if to != input.output_reference
                    ]),

                    # in principle a single public_key could be spent more than once in a single transaction, we
                    # could change the below into a set (or alternatively, note an input index)
                    spent_in_transactions=mutable_public_key_balances[
                        public_key].spent_in_transactions + [transaction],
                )

        for i, output in enumerate(transaction.outputs):
            output_reference = OutputReference(transaction.hash(), i)

            if output.public_key not in mutable_public_key_balances:
                mutable_public_key_balances[output.public_key] = PKBalance2(
                    0, [], [], [])

            mutable_public_key_balances[output.public_key] = PKBalance2(
                value=mutable_public_key_balances[output.public_key].value +
                output.value,
                all_output_references=(mutable_public_key_balances[
                    output.public_key].all_output_references +
                                       [output_reference]),
                unspent_output_references=(mutable_public_key_balances[
                    output.public_key].unspent_output_references +
                                           [output_reference]),
                spent_in_transactions=mutable_public_key_balances[
                    output.public_key].spent_in_transactions,
            )

        return mutable_public_key_balances.finish()
Exemple #15
0
def test_validate_non_coinbase_transaction_by_itself_no_duplicate_output_references(
):
    transaction = Transaction(inputs=[
        Input(
            OutputReference(b'a' * 32, 1),
            SECP256k1Signature(b'y' * 64),
        )
    ] * 2,
                              outputs=[Output(30, example_public_key)])

    with pytest.raises(ValidateTransactionError,
                       match=".*output_reference referenced more than once.*"):
        validate_non_coinbase_transaction_by_itself(transaction)
Exemple #16
0
def test_validate_non_coinbase_transaction_by_itself_no_outputs():
    transaction = Transaction(
        inputs=[
            Input(
                OutputReference(b'a' * 32, 1),
                SECP256k1Signature(b'y' * 64),
            )
        ],
        outputs=[],
    )

    with pytest.raises(ValidateTransactionError, match=".*No outputs.*"):
        validate_non_coinbase_transaction_by_itself(transaction)
def test_get_transaction_fee():
    public_key = SECP256k1PublicKey(b'x' * 64)

    previous_transaction_hash = b'a' * 32

    unspent_transaction_outs = immutables.Map({
        OutputReference(previous_transaction_hash, 0): Output(40, public_key),
        OutputReference(previous_transaction_hash, 1): Output(34, public_key),
    })

    transaction = Transaction(
        inputs=[Input(
            OutputReference(previous_transaction_hash, 1),
            SECP256k1Signature(b'y' * 64),
        )],
        outputs=[Output(30, public_key)]
    )

    assert get_transaction_fee(transaction, unspent_transaction_outs) == 34 - 30  # 4
Exemple #18
0
def test_validate_block_by_itself_invalid_coinbase_transaction():
    coinstate = CoinState.empty()

    transactions = [
        Transaction(inputs=[
            Input(
                OutputReference(b'x' * 32, 1),
                SECP256k1Signature(b'y' * 64),
            )
        ],
                    outputs=[Output(1, example_public_key)])
    ]

    summary = construct_minable_summary(coinstate, transactions, 1615209942,
                                        39)
    evidence = construct_pow_evidence(coinstate, summary, 0, transactions)
    block = Block(BlockHeader(summary, evidence), transactions)

    with pytest.raises(ValidateTransactionError, match=".*thin air.*"):
        validate_block_by_itself(block, 1615209942)
Exemple #19
0
def test_validate_block_by_itself_max_block_size(mocker):
    # work around get_block_fees... because we would need a coinstate w/ 50_000 unspent outputs to make it work.
    mocker.patch("skepticoin.consensus.get_block_fees", return_value=0)

    transactions = [
        Transaction(inputs=[
            Input(
                OutputReference(i.to_bytes(32, 'big'), 1),
                SECP256k1Signature(b'y' * 64),
            )
        ],
                    outputs=[Output(1, example_public_key)])
        for i in range(50_000)
    ]

    block = construct_block_for_mining_genesis(transactions,
                                               example_public_key, 1231006505,
                                               b'', 22)

    with pytest.raises(ValidateBlockError, match=".*MAX_BLOCK_SIZE.*"):
        validate_block_by_itself(block, 1615209942)
Exemple #20
0
def test_block_serialization():
    block = Block(
        header=BlockHeader(
            summary=example_block_summary,
            pow_evidence=example_pow_evidence,
        ),
        transactions=[
            Transaction(
                inputs=[
                    Input(output_reference=OutputReference(b"b" * 32, 1234),
                          signature=SECP256k1Signature(b"b" * 64))
                ],
                outputs=[
                    Output(value=1582,
                           public_key=SECP256k1PublicKey(b"g" * 64))
                ],
            )
        ] * 2,
    )

    serialize_and_deserialize(block)
Exemple #21
0
def test_broadcast_transaction(caplog, mocker):
    # just testing the basics: is a broadcast transaction stored in the transaction pool on the other side?

    # By turning off transaction-validation, we can use an invalid transaction in this test.
    mocker.patch(
        "skepticoin.networking.peer.validate_non_coinbase_transaction_by_itself"
    )
    mocker.patch(
        "skepticoin.networking.peer.validate_non_coinbase_transaction_in_coinstate"
    )

    caplog.set_level(logging.INFO)

    coinstate = _read_chain_from_disk(5)

    thread_a = NetworkingThread(coinstate, 12412, FakeDiskInterface())
    thread_a.start()

    _try_to_connect('127.0.0.1', 12412)

    thread_b = NetworkingThread(coinstate, 12413, FakeDiskInterface())
    thread_b.local_peer.network_manager.disconnected_peers = load_peers_from_list(
        [('127.0.0.1', 12412, "OUTGOING")])
    thread_b.start()

    previous_hash = coinstate.at_head.block_by_height[0].transactions[0].hash()

    # Not actually a valid transaction (not signed)
    transaction = Transaction(
        inputs=[
            Input(OutputReference(previous_hash, 0), SignableEquivalent())
        ],
        outputs=[Output(10, SECP256k1PublicKey(b'x' * 64))],
    )

    try:
        # give both peers some time to find each other
        start_time = time()
        while True:
            if (len(thread_a.local_peer.network_manager.get_active_peers()) > 0
                    and
                    len(thread_b.local_peer.network_manager.get_active_peers())
                    > 0):
                break

            if time() > start_time + 5:
                print("\n".join(str(r) for r in caplog.records))
                raise Exception("Peers can't connect")

            sleep(0.01)

        # broadcast_transaction... the part that we're testing
        thread_a.local_peer.network_manager.broadcast_transaction(transaction)

        # wait until it's picked up on the other side
        start_time = time()
        while True:
            if len(thread_b.local_peer.chain_manager.transaction_pool) > 0:
                break

            if time() > start_time + 5:
                print("\n".join(str(r) for r in caplog.records))
                raise Exception("Transaction broadcast failed")

            sleep(0.01)

    finally:
        thread_a.stop()
        thread_a.join()

        thread_b.stop()
        thread_b.join()
Exemple #22
0
 def save_transaction_for_debugging(self, transaction: Transaction) -> None:
     with open("/tmp/%s.transaction" % human(transaction.hash()),
               'wb') as f:
         f.write(transaction.serialize())