bigchaindb/tests/common/test_transaction.py

1054 lines
36 KiB
Python

# Copyright BigchainDB GmbH and BigchainDB contributors
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0
"""These are tests of the API of the Transaction class and associated classes.
Tests for transaction validation are separate.
"""
import json
from copy import deepcopy
from base58 import b58encode, b58decode
from cryptoconditions import Ed25519Sha256
from pytest import mark, raises
from sha3 import sha3_256
pytestmark = mark.bdb
def test_input_serialization(ffill_uri, user_pub):
from bigchaindb.common.transaction import Input
from cryptoconditions import Fulfillment
expected = {
'owners_before': [user_pub],
'fulfillment': ffill_uri,
'fulfills': None,
}
input = Input(Fulfillment.from_uri(ffill_uri), [user_pub])
assert input.to_dict() == expected
def test_input_deserialization_with_uri(ffill_uri, user_pub):
from bigchaindb.common.transaction import Input
from cryptoconditions import Fulfillment
expected = Input(Fulfillment.from_uri(ffill_uri), [user_pub])
ffill = {
'owners_before': [user_pub],
'fulfillment': ffill_uri,
'fulfills': None,
}
input = Input.from_dict(ffill)
assert input == expected
@mark.skip(reason='None is tolerated because it is None before fulfilling.')
def test_input_deserialization_with_invalid_input(user_pub):
from bigchaindb.common.transaction import Input
ffill = {
'owners_before': [user_pub],
'fulfillment': None,
'fulfills': None,
}
with raises(TypeError):
Input.from_dict(ffill)
def test_input_deserialization_with_invalid_fulfillment_uri(user_pub):
from bigchaindb.common.exceptions import InvalidSignature
from bigchaindb.common.transaction import Input
ffill = {
'owners_before': [user_pub],
'fulfillment': 'an invalid fulfillment',
'fulfills': None,
}
with raises(InvalidSignature):
Input.from_dict(ffill)
def test_input_deserialization_with_unsigned_fulfillment(ffill_uri, user_pub):
from bigchaindb.common.transaction import Input
from cryptoconditions import Fulfillment
expected = Input(Fulfillment.from_uri(ffill_uri), [user_pub])
ffill = {
'owners_before': [user_pub],
'fulfillment': Fulfillment.from_uri(ffill_uri),
'fulfills': None,
}
input = Input.from_dict(ffill)
assert input == expected
def test_output_serialization(user_Ed25519, user_pub):
from bigchaindb.common.transaction import Output
expected = {
'condition': {
'uri': user_Ed25519.condition_uri,
'details': {
'type': 'ed25519-sha-256',
'public_key': b58encode(user_Ed25519.public_key).decode(),
},
},
'public_keys': [user_pub],
'amount': '1',
}
cond = Output(user_Ed25519, [user_pub], 1)
assert cond.to_dict() == expected
def test_output_deserialization(user_Ed25519, user_pub):
from bigchaindb.common.transaction import Output
expected = Output(user_Ed25519, [user_pub], 1)
cond = {
'condition': {
'uri': user_Ed25519.condition_uri,
'details': {
'type': 'ed25519-sha-256',
'public_key': b58encode(user_Ed25519.public_key).decode(),
},
},
'public_keys': [user_pub],
'amount': '1',
}
cond = Output.from_dict(cond)
assert cond == expected
def test_output_hashlock_serialization():
from bigchaindb.common.transaction import Output
from cryptoconditions import PreimageSha256
secret = b'wow much secret'
hashlock = PreimageSha256(preimage=secret).condition_uri
expected = {
'condition': {
'uri': hashlock,
},
'public_keys': None,
'amount': '1',
}
cond = Output(hashlock, amount=1)
assert cond.to_dict() == expected
def test_output_hashlock_deserialization():
from bigchaindb.common.transaction import Output
from cryptoconditions import PreimageSha256
secret = b'wow much secret'
hashlock = PreimageSha256(preimage=secret).condition_uri
expected = Output(hashlock, amount=1)
cond = {
'condition': {
'uri': hashlock
},
'public_keys': None,
'amount': '1',
}
cond = Output.from_dict(cond)
assert cond == expected
def test_invalid_output_initialization(cond_uri, user_pub):
from bigchaindb.common.transaction import Output
from bigchaindb.common.exceptions import AmountError
with raises(TypeError):
Output(cond_uri, user_pub)
with raises(TypeError):
Output(cond_uri, [user_pub], 'amount')
with raises(AmountError):
Output(cond_uri, [user_pub], 0)
def test_generate_output_split_half_recursive(user_pub, user2_pub, user3_pub):
from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Sha256, ThresholdSha256
expected_simple1 = Ed25519Sha256(public_key=b58decode(user_pub))
expected_simple2 = Ed25519Sha256(public_key=b58decode(user2_pub))
expected_simple3 = Ed25519Sha256(public_key=b58decode(user3_pub))
expected = ThresholdSha256(threshold=2)
expected.add_subfulfillment(expected_simple1)
expected_threshold = ThresholdSha256(threshold=2)
expected_threshold.add_subfulfillment(expected_simple2)
expected_threshold.add_subfulfillment(expected_simple3)
expected.add_subfulfillment(expected_threshold)
cond = Output.generate([user_pub, [user2_pub, expected_simple3]], 1)
assert cond.fulfillment.to_dict() == expected.to_dict()
def test_generate_outputs_split_half_single_owner(user_pub,
user2_pub, user3_pub):
from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Sha256, ThresholdSha256
expected_simple1 = Ed25519Sha256(public_key=b58decode(user_pub))
expected_simple2 = Ed25519Sha256(public_key=b58decode(user2_pub))
expected_simple3 = Ed25519Sha256(public_key=b58decode(user3_pub))
expected = ThresholdSha256(threshold=2)
expected_threshold = ThresholdSha256(threshold=2)
expected_threshold.add_subfulfillment(expected_simple2)
expected_threshold.add_subfulfillment(expected_simple3)
expected.add_subfulfillment(expected_threshold)
expected.add_subfulfillment(expected_simple1)
cond = Output.generate([[expected_simple2, user3_pub], user_pub], 1)
assert cond.fulfillment.to_dict() == expected.to_dict()
def test_generate_outputs_flat_ownage(user_pub, user2_pub, user3_pub):
from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Sha256, ThresholdSha256
expected_simple1 = Ed25519Sha256(public_key=b58decode(user_pub))
expected_simple2 = Ed25519Sha256(public_key=b58decode(user2_pub))
expected_simple3 = Ed25519Sha256(public_key=b58decode(user3_pub))
expected = ThresholdSha256(threshold=3)
expected.add_subfulfillment(expected_simple1)
expected.add_subfulfillment(expected_simple2)
expected.add_subfulfillment(expected_simple3)
cond = Output.generate([user_pub, user2_pub, expected_simple3], 1)
assert cond.fulfillment.to_dict() == expected.to_dict()
def test_generate_output_single_owner(user_pub):
from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Sha256
expected = Ed25519Sha256(public_key=b58decode(user_pub))
cond = Output.generate([user_pub], 1)
assert cond.fulfillment.to_dict() == expected.to_dict()
def test_generate_output_single_owner_with_output(user_pub):
from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Sha256
expected = Ed25519Sha256(public_key=b58decode(user_pub))
cond = Output.generate([expected], 1)
assert cond.fulfillment.to_dict() == expected.to_dict()
def test_generate_output_invalid_parameters(user_pub, user2_pub, user3_pub):
from bigchaindb.common.transaction import Output
from bigchaindb.common.exceptions import AmountError
with raises(ValueError):
Output.generate([], 1)
with raises(TypeError):
Output.generate('not a list', 1)
with raises(ValueError):
Output.generate([[user_pub, [user2_pub, [user3_pub]]]], 1)
with raises(ValueError):
Output.generate([[user_pub]], 1)
with raises(AmountError):
Output.generate([[user_pub]], -1)
def test_invalid_transaction_initialization(asset_definition):
from bigchaindb.common.transaction import Transaction
with raises(ValueError):
Transaction(operation='invalid operation', asset=asset_definition)
with raises(TypeError):
Transaction(operation='CREATE', asset='invalid asset')
with raises(TypeError):
Transaction(operation='TRANSFER', asset={})
with raises(TypeError):
Transaction(
operation='CREATE',
asset=asset_definition,
outputs='invalid outputs'
)
with raises(TypeError):
Transaction(
operation='CREATE',
asset=asset_definition,
outputs=[],
inputs='invalid inputs'
)
with raises(TypeError):
Transaction(
operation='CREATE',
asset=asset_definition,
outputs=[],
inputs=[],
metadata='invalid metadata'
)
def test_create_default_asset_on_tx_initialization(asset_definition):
from bigchaindb.common.transaction import Transaction
expected = {'data': None}
tx = Transaction(Transaction.CREATE, asset=expected)
asset = tx.asset
assert asset == expected
def test_transaction_serialization(user_input, user_output, data):
from bigchaindb.common.transaction import Transaction
expected = {
'id': None,
'version': Transaction.VERSION,
# NOTE: This test assumes that Inputs and Outputs can
# successfully be serialized
'inputs': [user_input.to_dict()],
'outputs': [user_output.to_dict()],
'operation': Transaction.CREATE,
'metadata': None,
'asset': {
'data': data,
}
}
tx = Transaction(Transaction.CREATE, {'data': data}, [user_input],
[user_output])
tx_dict = tx.to_dict()
assert tx_dict == expected
def test_transaction_deserialization(tri_state_transaction):
from bigchaindb.common.transaction import Transaction
from .utils import validate_transaction_model
tx = Transaction.from_dict(tri_state_transaction)
validate_transaction_model(tx)
def test_invalid_input_initialization(user_input, user_pub):
from bigchaindb.common.transaction import Input
with raises(TypeError):
Input(user_input, user_pub)
with raises(TypeError):
Input(user_input, tx_input='somethingthatiswrong')
def test_transaction_link_serialization():
from bigchaindb.common.transaction import TransactionLink
tx_id = 'a transaction id'
expected = {
'transaction_id': tx_id,
'output_index': 0,
}
tx_link = TransactionLink(tx_id, 0)
assert tx_link.to_dict() == expected
def test_transaction_link_serialization_with_empty_payload():
from bigchaindb.common.transaction import TransactionLink
expected = None
tx_link = TransactionLink()
assert tx_link.to_dict() == expected
def test_transaction_link_deserialization():
from bigchaindb.common.transaction import TransactionLink
tx_id = 'a transaction id'
expected = TransactionLink(tx_id, 0)
tx_link = {
'transaction_id': tx_id,
'output_index': 0,
}
tx_link = TransactionLink.from_dict(tx_link)
assert tx_link == expected
def test_transaction_link_deserialization_with_empty_payload():
from bigchaindb.common.transaction import TransactionLink
expected = TransactionLink()
tx_link = TransactionLink.from_dict(None)
assert tx_link == expected
def test_transaction_link_empty_to_uri():
from bigchaindb.common.transaction import TransactionLink
expected = None
tx_link = TransactionLink().to_uri()
assert expected == tx_link
def test_transaction_link_to_uri():
from bigchaindb.common.transaction import TransactionLink
expected = 'path/transactions/abc/outputs/0'
tx_link = TransactionLink('abc', 0).to_uri('path')
assert expected == tx_link
def test_cast_transaction_link_to_boolean():
from bigchaindb.common.transaction import TransactionLink
assert bool(TransactionLink()) is False
assert bool(TransactionLink('a', None)) is False
assert bool(TransactionLink(None, 'b')) is False
assert bool(TransactionLink('a', 'b')) is True
assert bool(TransactionLink(False, False)) is True
def test_transaction_link_eq():
from bigchaindb.common.transaction import TransactionLink
assert TransactionLink(1, 2) == TransactionLink(1, 2)
assert TransactionLink(2, 2) != TransactionLink(1, 2)
assert TransactionLink(1, 1) != TransactionLink(1, 2)
assert TransactionLink(2, 1) != TransactionLink(1, 2)
def test_add_input_to_tx(user_input, asset_definition):
from bigchaindb.common.transaction import Transaction
from .utils import validate_transaction_model
tx = Transaction(Transaction.CREATE, asset_definition, [], [])
tx.add_input(user_input)
assert len(tx.inputs) == 1
validate_transaction_model(tx)
def test_add_input_to_tx_with_invalid_parameters(asset_definition):
from bigchaindb.common.transaction import Transaction
tx = Transaction(Transaction.CREATE, asset_definition)
with raises(TypeError):
tx.add_input('somewronginput')
def test_add_output_to_tx(user_output, user_input, asset_definition):
from bigchaindb.common.transaction import Transaction
from .utils import validate_transaction_model
tx = Transaction(Transaction.CREATE, asset_definition, [user_input])
tx.add_output(user_output)
assert len(tx.outputs) == 1
validate_transaction_model(tx)
def test_add_output_to_tx_with_invalid_parameters(asset_definition):
from bigchaindb.common.transaction import Transaction
tx = Transaction(Transaction.CREATE, asset_definition, [], [])
with raises(TypeError):
tx.add_output('somewronginput')
def test_sign_with_invalid_parameters(utx, user_priv):
with raises(TypeError):
utx.sign(None)
with raises(TypeError):
utx.sign(user_priv)
def test_validate_tx_simple_create_signature(user_input, user_output, user_priv,
asset_definition):
from bigchaindb.common.transaction import Transaction
from .utils import validate_transaction_model
tx = Transaction(Transaction.CREATE, asset_definition, [user_input], [user_output])
expected = deepcopy(user_output)
tx_dict = tx.to_dict()
tx_dict['inputs'][0]['fulfillment'] = None
serialized_tx = json.dumps(tx_dict, sort_keys=True,
separators=(',', ':'), ensure_ascii=True)
message = sha3_256(serialized_tx.encode()).digest()
expected.fulfillment.sign(message, b58decode(user_priv))
tx.sign([user_priv])
assert tx.inputs[0].to_dict()['fulfillment'] == \
expected.fulfillment.serialize_uri()
assert tx.inputs_valid() is True
validate_transaction_model(tx)
def test_invoke_simple_signature_fulfillment_with_invalid_params(utx,
user_input):
from bigchaindb.common.exceptions import KeypairMismatchException
with raises(KeypairMismatchException):
invalid_key_pair = {'wrong_pub_key': 'wrong_priv_key'}
utx._sign_simple_signature_fulfillment(user_input,
'somemessage',
invalid_key_pair)
def test_sign_threshold_with_invalid_params(utx, user_user2_threshold_input,
user3_pub, user3_priv):
from bigchaindb.common.exceptions import KeypairMismatchException
with raises(KeypairMismatchException):
utx._sign_threshold_signature_fulfillment(user_user2_threshold_input,
'somemessage',
{user3_pub: user3_priv})
with raises(KeypairMismatchException):
user_user2_threshold_input.owners_before = [58 * 'a']
utx._sign_threshold_signature_fulfillment(user_user2_threshold_input,
'somemessage',
None)
def test_validate_input_with_invalid_parameters(utx):
from bigchaindb.common.transaction import Transaction
input_conditions = [out.fulfillment.condition_uri for out in utx.outputs]
tx_dict = utx.to_dict()
tx_serialized = Transaction._to_str(tx_dict)
valid = utx._input_valid(utx.inputs[0], tx_serialized, input_conditions[0])
assert not valid
def test_validate_tx_threshold_create_signature(user_user2_threshold_input,
user_user2_threshold_output,
user_pub,
user2_pub,
user_priv,
user2_priv,
asset_definition):
from bigchaindb.common.transaction import Transaction
from .utils import validate_transaction_model
tx = Transaction(Transaction.CREATE, asset_definition,
[user_user2_threshold_input],
[user_user2_threshold_output])
tx_dict = tx.to_dict()
tx_dict['inputs'][0]['fulfillment'] = None
serialized_tx = json.dumps(tx_dict, sort_keys=True,
separators=(',', ':'), ensure_ascii=True)
message = sha3_256(serialized_tx.encode()).digest()
expected = deepcopy(user_user2_threshold_output)
expected.fulfillment.subconditions[0]['body'].sign(
message, b58decode(user_priv))
expected.fulfillment.subconditions[1]['body'].sign(
message, b58decode(user2_priv))
tx.sign([user_priv, user2_priv])
assert tx.inputs[0].to_dict()['fulfillment'] == \
expected.fulfillment.serialize_uri()
assert tx.inputs_valid() is True
validate_transaction_model(tx)
def test_validate_tx_threshold_duplicated_pk(user_pub, user_priv,
asset_definition):
from cryptoconditions import Ed25519Sha256, ThresholdSha256
from bigchaindb.common.transaction import Input, Output, Transaction
threshold = ThresholdSha256(threshold=2)
threshold.add_subfulfillment(
Ed25519Sha256(public_key=b58decode(user_pub)))
threshold.add_subfulfillment(
Ed25519Sha256(public_key=b58decode(user_pub)))
threshold_input = Input(threshold, [user_pub, user_pub])
threshold_output = Output(threshold, [user_pub, user_pub])
tx = Transaction(Transaction.CREATE, asset_definition,
[threshold_input], [threshold_output])
tx_dict = tx.to_dict()
tx_dict['inputs'][0]['fulfillment'] = None
serialized_tx = json.dumps(tx_dict, sort_keys=True,
separators=(',', ':'), ensure_ascii=True)
message = sha3_256(serialized_tx.encode()).digest()
expected = deepcopy(threshold_input)
expected.fulfillment.subconditions[0]['body'].sign(
message, b58decode(user_priv))
expected.fulfillment.subconditions[1]['body'].sign(
message, b58decode(user_priv))
tx.sign([user_priv, user_priv])
subconditions = tx.inputs[0].fulfillment.subconditions
expected_subconditions = expected.fulfillment.subconditions
assert subconditions[0]['body'].to_dict()['signature'] == \
expected_subconditions[0]['body'].to_dict()['signature']
assert subconditions[1]['body'].to_dict()['signature'] == \
expected_subconditions[1]['body'].to_dict()['signature']
assert tx.inputs[0].to_dict()['fulfillment'] == \
expected.fulfillment.serialize_uri()
assert tx.inputs_valid() is True
def test_multiple_input_validation_of_transfer_tx(user_input, user_output,
user_priv, user2_pub,
user2_priv, user3_pub,
user3_priv,
asset_definition):
from bigchaindb.common.transaction import (Transaction, TransactionLink,
Input, Output)
from cryptoconditions import Ed25519Sha256
from .utils import validate_transaction_model
tx = Transaction(Transaction.CREATE, asset_definition, [user_input],
[user_output, deepcopy(user_output)])
tx.sign([user_priv])
inputs = [Input(cond.fulfillment, cond.public_keys,
TransactionLink(tx.id, index))
for index, cond in enumerate(tx.outputs)]
outputs = [Output(Ed25519Sha256(public_key=b58decode(user3_pub)),
[user3_pub]),
Output(Ed25519Sha256(public_key=b58decode(user3_pub)),
[user3_pub])]
transfer_tx = Transaction('TRANSFER', {'id': tx.id}, inputs, outputs)
transfer_tx = transfer_tx.sign([user_priv])
assert transfer_tx.inputs_valid(tx.outputs) is True
validate_transaction_model(tx)
def test_validate_inputs_of_transfer_tx_with_invalid_params(
transfer_tx, cond_uri, utx, user2_pub, user_priv, ffill_uri):
from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Sha256
invalid_out = Output(Ed25519Sha256.from_uri(ffill_uri), ['invalid'])
assert transfer_tx.inputs_valid([invalid_out]) is False
invalid_out = utx.outputs[0]
invalid_out.public_key = 'invalid'
assert transfer_tx.inputs_valid([invalid_out]) is True
with raises(TypeError):
assert transfer_tx.inputs_valid(None) is False
with raises(AttributeError):
transfer_tx.inputs_valid('not a list')
with raises(ValueError):
transfer_tx.inputs_valid([])
with raises(TypeError):
transfer_tx.operation = "Operation that doesn't exist"
transfer_tx.inputs_valid([utx.outputs[0]])
def test_create_create_transaction_single_io(user_output, user_pub, data):
from bigchaindb.common.transaction import Transaction
from .utils import validate_transaction_model
expected = {
'outputs': [user_output.to_dict()],
'metadata': data,
'asset': {
'data': data,
},
'inputs': [
{
'owners_before': [
user_pub
],
'fulfillment': None,
'fulfills': None
}
],
'operation': 'CREATE',
'version': Transaction.VERSION,
}
tx = Transaction.create([user_pub], [([user_pub], 1)], metadata=data,
asset=data)
tx_dict = tx.to_dict()
tx_dict['inputs'][0]['fulfillment'] = None
tx_dict.pop('id')
assert tx_dict == expected
validate_transaction_model(tx)
def test_validate_single_io_create_transaction(user_pub, user_priv, data,
asset_definition):
from bigchaindb.common.transaction import Transaction
tx = Transaction.create([user_pub], [([user_pub], 1)], metadata=data)
tx = tx.sign([user_priv])
assert tx.inputs_valid() is True
def test_create_create_transaction_multiple_io(user_output, user2_output, user_pub,
user2_pub, asset_definition):
from bigchaindb.common.transaction import Transaction, Input
# a fulfillment for a create transaction with multiple `owners_before`
# is a fulfillment for an implicit threshold condition with
# weight = len(owners_before)
input = Input.generate([user_pub, user2_pub]).to_dict()
expected = {
'outputs': [user_output.to_dict(), user2_output.to_dict()],
'metadata': {
'message': 'hello'
},
'inputs': [input],
'operation': 'CREATE',
'version': Transaction.VERSION
}
tx = Transaction.create([user_pub, user2_pub],
[([user_pub], 1), ([user2_pub], 1)],
metadata={'message': 'hello'}).to_dict()
tx.pop('id')
tx.pop('asset')
assert tx == expected
def test_validate_multiple_io_create_transaction(user_pub, user_priv,
user2_pub, user2_priv,
asset_definition):
from bigchaindb.common.transaction import Transaction
from .utils import validate_transaction_model
tx = Transaction.create([user_pub, user2_pub],
[([user_pub], 1), ([user2_pub], 1)],
metadata={'message': 'hello'})
tx = tx.sign([user_priv, user2_priv])
assert tx.inputs_valid() is True
validate_transaction_model(tx)
def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
user_user2_threshold_output,
user_user2_threshold_input, data):
from bigchaindb.common.transaction import Transaction
expected = {
'outputs': [user_user2_threshold_output.to_dict()],
'metadata': data,
'asset': {
'data': data,
},
'inputs': [
{
'owners_before': [
user_pub,
],
'fulfillment': None,
'fulfills': None,
},
],
'operation': 'CREATE',
'version': Transaction.VERSION
}
tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)],
metadata=data, asset=data)
tx_dict = tx.to_dict()
tx_dict.pop('id')
tx_dict['inputs'][0]['fulfillment'] = None
assert tx_dict == expected
def test_validate_threshold_create_transaction(user_pub, user_priv, user2_pub,
data, asset_definition):
from bigchaindb.common.transaction import Transaction
from .utils import validate_transaction_model
tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)],
metadata=data)
tx = tx.sign([user_priv])
assert tx.inputs_valid() is True
validate_transaction_model(tx)
def test_create_create_transaction_with_invalid_parameters(user_pub):
from bigchaindb.common.transaction import Transaction
with raises(TypeError):
Transaction.create('not a list')
with raises(TypeError):
Transaction.create([], 'not a list')
with raises(ValueError):
Transaction.create([], [user_pub])
with raises(ValueError):
Transaction.create([user_pub], [])
with raises(ValueError):
Transaction.create([user_pub], [user_pub])
with raises(ValueError):
Transaction.create([user_pub], [([user_pub],)])
with raises(TypeError):
Transaction.create([user_pub], [([user_pub], 1)],
metadata='not a dict or none')
with raises(TypeError):
Transaction.create([user_pub],
[([user_pub], 1)],
asset='not a dict or none')
def test_outputs_to_inputs(tx):
inputs = tx.to_inputs([0])
assert len(inputs) == 1
input = inputs.pop()
assert input.owners_before == tx.outputs[0].public_keys
assert input.fulfillment == tx.outputs[0].fulfillment
assert input.fulfills.txid == tx.id
assert input.fulfills.output == 0
def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
user2_output, user_priv):
from bigchaindb.common.transaction import Transaction
from .utils import validate_transaction_model
expected = {
'id': None,
'outputs': [user2_output.to_dict()],
'metadata': None,
'asset': {
'id': tx.id,
},
'inputs': [
{
'owners_before': [
user_pub
],
'fulfillment': None,
'fulfills': {
'transaction_id': tx.id,
'output_index': 0
}
}
],
'operation': 'TRANSFER',
'version': Transaction.VERSION
}
inputs = tx.to_inputs([0])
transfer_tx = Transaction.transfer(inputs, [([user2_pub], 1)],
asset_id=tx.id)
transfer_tx = transfer_tx.sign([user_priv])
transfer_tx = transfer_tx.to_dict()
expected_input = deepcopy(inputs[0])
json_serialized_tx = json.dumps(expected, sort_keys=True,
separators=(',', ':'), ensure_ascii=True)
message = sha3_256(json_serialized_tx.encode())
message.update('{}{}'.format(
expected['inputs'][0]['fulfills']['transaction_id'],
expected['inputs'][0]['fulfills']['output_index'],
).encode())
expected_input.fulfillment.sign(message.digest(), b58decode(user_priv))
expected_ffill = expected_input.fulfillment.serialize_uri()
transfer_ffill = transfer_tx['inputs'][0]['fulfillment']
assert transfer_ffill == expected_ffill
transfer_tx = Transaction.from_dict(transfer_tx)
assert transfer_tx.inputs_valid([tx.outputs[0]]) is True
validate_transaction_model(transfer_tx)
def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
user2_pub, user2_priv,
user3_pub, user2_output,
asset_definition):
from bigchaindb.common.transaction import Transaction
tx = Transaction.create([user_pub], [([user_pub], 1), ([user2_pub], 1)],
metadata={'message': 'hello'})
tx = tx.sign([user_priv])
expected = {
'outputs': [user2_output.to_dict(), user2_output.to_dict()],
'metadata': None,
'inputs': [
{
'owners_before': [
user_pub
],
'fulfillment': None,
'fulfills': {
'transaction_id': tx.id,
'output_index': 0
}
}, {
'owners_before': [
user2_pub
],
'fulfillment': None,
'fulfills': {
'transaction_id': tx.id,
'output_index': 1
}
}
],
'operation': 'TRANSFER',
'version': Transaction.VERSION
}
transfer_tx = Transaction.transfer(tx.to_inputs(),
[([user2_pub], 1), ([user2_pub], 1)],
asset_id=tx.id)
transfer_tx = transfer_tx.sign([user_priv, user2_priv])
assert len(transfer_tx.inputs) == 2
assert len(transfer_tx.outputs) == 2
assert transfer_tx.inputs_valid(tx.outputs) is True
transfer_tx = transfer_tx.to_dict()
transfer_tx['inputs'][0]['fulfillment'] = None
transfer_tx['inputs'][1]['fulfillment'] = None
transfer_tx.pop('asset')
transfer_tx.pop('id')
assert expected == transfer_tx
def test_create_transfer_with_invalid_parameters(tx, user_pub):
from bigchaindb.common.transaction import Transaction
with raises(TypeError):
Transaction.transfer({}, [], tx.id)
with raises(ValueError):
Transaction.transfer([], [], tx.id)
with raises(TypeError):
Transaction.transfer(['fulfillment'], {}, tx.id)
with raises(ValueError):
Transaction.transfer(['fulfillment'], [], tx.id)
with raises(ValueError):
Transaction.transfer(['fulfillment'], [user_pub], tx.id)
with raises(ValueError):
Transaction.transfer(['fulfillment'], [([user_pub],)], tx.id)
with raises(TypeError):
Transaction.transfer(['fulfillment'], [([user_pub], 1)],
tx.id, metadata='not a dict or none')
with raises(TypeError):
Transaction.transfer(['fulfillment'], [([user_pub], 1)],
['not a string'])
def test_cant_add_empty_output():
from bigchaindb.common.transaction import Transaction
tx = Transaction(Transaction.CREATE, None)
with raises(TypeError):
tx.add_output(None)
def test_cant_add_empty_input():
from bigchaindb.common.transaction import Transaction
tx = Transaction(Transaction.CREATE, None)
with raises(TypeError):
tx.add_input(None)
def test_unfulfilled_transaction_serialized(unfulfilled_transaction):
from bigchaindb.common.transaction import Transaction
tx_obj = Transaction.from_dict(unfulfilled_transaction)
expected = json.dumps(unfulfilled_transaction, sort_keys=True,
separators=(',', ':'), ensure_ascii=True)
assert tx_obj.serialized == expected
def test_fulfilled_transaction_serialized(fulfilled_transaction):
from bigchaindb.common.transaction import Transaction
tx_obj = Transaction.from_dict(fulfilled_transaction)
expected = json.dumps(fulfilled_transaction, sort_keys=True,
separators=(',', ':'), ensure_ascii=True)
assert tx_obj.serialized == expected
def test_transaction_hash(fulfilled_transaction):
from bigchaindb.common.transaction import Transaction
tx_obj = Transaction.from_dict(fulfilled_transaction)
assert tx_obj._id is None
assert tx_obj.id is None
thing_to_hash = json.dumps(fulfilled_transaction, sort_keys=True,
separators=(',', ':'), ensure_ascii=True)
expected_hash_id = sha3_256(thing_to_hash.encode()).hexdigest()
tx_obj._hash()
assert tx_obj._id == expected_hash_id
assert tx_obj.id == expected_hash_id
def test_output_from_dict_invalid_amount(user_output):
from bigchaindb.common.transaction import Output
from bigchaindb.common.exceptions import AmountError
out = user_output.to_dict()
out['amount'] = 'a'
with raises(AmountError):
Output.from_dict(out)
def test_unspent_outputs_property(merlin, alice, bob, carol):
from bigchaindb.common.transaction import Transaction
tx = Transaction.create(
[merlin.public_key],
[([alice.public_key], 1),
([bob.public_key], 2),
([carol.public_key], 3)],
asset={'hash': '06e47bcf9084f7ecfd2a2a2ad275444a'},
).sign([merlin.private_key])
unspent_outputs = list(tx.unspent_outputs)
assert len(unspent_outputs) == 3
assert all(utxo.transaction_id == tx.id for utxo in unspent_outputs)
assert all(utxo.asset_id == tx.id for utxo in unspent_outputs)
assert all(
utxo.output_index == i for i, utxo in enumerate(unspent_outputs))
unspent_output_0 = unspent_outputs[0]
assert unspent_output_0.amount == 1
assert unspent_output_0.condition_uri == Ed25519Sha256(
public_key=b58decode(alice.public_key)).condition_uri
unspent_output_1 = unspent_outputs[1]
assert unspent_output_1.amount == 2
assert unspent_output_1.condition_uri == Ed25519Sha256(
public_key=b58decode(bob.public_key)).condition_uri
unspent_output_2 = unspent_outputs[2]
assert unspent_output_2.amount == 3
assert unspent_output_2.condition_uri == Ed25519Sha256(
public_key=b58decode(carol.public_key)).condition_uri
def test_spent_outputs_property(signed_transfer_tx):
spent_outputs = list(signed_transfer_tx.spent_outputs)
tx = signed_transfer_tx.to_dict()
assert len(spent_outputs) == 1
spent_output = spent_outputs[0]
assert spent_output['transaction_id'] == tx['inputs'][0]['fulfills']['transaction_id']
assert spent_output['output_index'] == tx['inputs'][0]['fulfills']['output_index']
# assert spent_output._asdict() == tx['inputs'][0]['fulfills']