mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Fixes #1891
This commit is contained in:
parent
e377fb57af
commit
9dd2e026b0
@ -13,7 +13,9 @@ required:
|
||||
- version
|
||||
properties:
|
||||
id:
|
||||
"$ref": "#/definitions/sha3_hexdigest"
|
||||
anyOf:
|
||||
- "$ref": "#/definitions/sha3_hexdigest"
|
||||
- type: 'null'
|
||||
operation:
|
||||
"$ref": "#/definitions/operation"
|
||||
asset:
|
||||
|
@ -112,7 +112,7 @@ class Input(object):
|
||||
InvalidSignature: If an Input's URI couldn't be parsed.
|
||||
"""
|
||||
fulfillment = data['fulfillment']
|
||||
if not isinstance(fulfillment, Fulfillment):
|
||||
if not isinstance(fulfillment, (Fulfillment, type(None))):
|
||||
try:
|
||||
fulfillment = Fulfillment.from_uri(data['fulfillment'])
|
||||
except ASN1DecodeError:
|
||||
@ -477,7 +477,7 @@ class Transaction(object):
|
||||
VERSION = '1.0'
|
||||
|
||||
def __init__(self, operation, asset, inputs=None, outputs=None,
|
||||
metadata=None, version=None):
|
||||
metadata=None, version=None, hash_id=None):
|
||||
"""The constructor allows to create a customizable Transaction.
|
||||
|
||||
Note:
|
||||
@ -495,6 +495,7 @@ class Transaction(object):
|
||||
metadata (dict): Metadata to be stored along with the
|
||||
Transaction.
|
||||
version (string): Defines the version number of a Transaction.
|
||||
hash_id (string): Hash id of the transaction.
|
||||
"""
|
||||
if operation not in Transaction.ALLOWED_OPERATIONS:
|
||||
allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS)
|
||||
@ -528,6 +529,14 @@ class Transaction(object):
|
||||
self.inputs = inputs or []
|
||||
self.outputs = outputs or []
|
||||
self.metadata = metadata
|
||||
self._id = hash_id
|
||||
|
||||
@property
|
||||
def serialized(self):
|
||||
return Transaction._to_str(self.to_dict())
|
||||
|
||||
def _hash(self):
|
||||
self._id = hash_data(self.serialized)
|
||||
|
||||
@classmethod
|
||||
def create(cls, tx_signers, recipients, metadata=None, asset=None):
|
||||
@ -756,6 +765,9 @@ class Transaction(object):
|
||||
tx_serialized = Transaction._to_str(tx_dict)
|
||||
for i, input_ in enumerate(self.inputs):
|
||||
self.inputs[i] = self._sign_input(input_, tx_serialized, key_pairs)
|
||||
|
||||
self._hash()
|
||||
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
@ -907,6 +919,7 @@ class Transaction(object):
|
||||
|
||||
tx_dict = self.to_dict()
|
||||
tx_dict = Transaction._remove_signatures(tx_dict)
|
||||
tx_dict['id'] = None
|
||||
tx_serialized = Transaction._to_str(tx_dict)
|
||||
|
||||
def validate(i, output_condition_uri=None):
|
||||
@ -965,22 +978,16 @@ class Transaction(object):
|
||||
Returns:
|
||||
dict: The Transaction as an alternative serialization format.
|
||||
"""
|
||||
tx = {
|
||||
return {
|
||||
'inputs': [input_.to_dict() for input_ in self.inputs],
|
||||
'outputs': [output.to_dict() for output in self.outputs],
|
||||
'operation': str(self.operation),
|
||||
'metadata': self.metadata,
|
||||
'asset': self.asset,
|
||||
'version': self.version,
|
||||
'id': self._id,
|
||||
}
|
||||
|
||||
tx_no_signatures = Transaction._remove_signatures(tx)
|
||||
tx_serialized = Transaction._to_str(tx_no_signatures)
|
||||
tx_id = Transaction._to_hash(tx_serialized)
|
||||
|
||||
tx['id'] = tx_id
|
||||
return tx
|
||||
|
||||
@staticmethod
|
||||
# TODO: Remove `_dict` prefix of variable.
|
||||
def _remove_signatures(tx_dict):
|
||||
@ -1010,7 +1017,7 @@ class Transaction(object):
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.to_hash()
|
||||
return self._id
|
||||
|
||||
def to_hash(self):
|
||||
return self.to_dict()['id']
|
||||
@ -1069,12 +1076,13 @@ class Transaction(object):
|
||||
# NOTE: Remove reference to avoid side effects
|
||||
tx_body = deepcopy(tx_body)
|
||||
try:
|
||||
proposed_tx_id = tx_body.pop('id')
|
||||
proposed_tx_id = tx_body['id']
|
||||
except KeyError:
|
||||
raise InvalidHash('No transaction id found!')
|
||||
|
||||
tx_body_no_signatures = Transaction._remove_signatures(tx_body)
|
||||
tx_body_serialized = Transaction._to_str(tx_body_no_signatures)
|
||||
tx_body['id'] = None
|
||||
|
||||
tx_body_serialized = Transaction._to_str(tx_body)
|
||||
valid_tx_id = Transaction._to_hash(tx_body_serialized)
|
||||
|
||||
if proposed_tx_id != valid_tx_id:
|
||||
@ -1092,8 +1100,7 @@ class Transaction(object):
|
||||
Returns:
|
||||
:class:`~bigchaindb.common.transaction.Transaction`
|
||||
"""
|
||||
cls.validate_id(tx)
|
||||
inputs = [Input.from_dict(input_) for input_ in tx['inputs']]
|
||||
outputs = [Output.from_dict(output) for output in tx['outputs']]
|
||||
return cls(tx['operation'], tx['asset'], inputs, outputs,
|
||||
tx['metadata'], tx['version'])
|
||||
tx['metadata'], tx['version'], hash_id=tx['id'])
|
||||
|
@ -84,6 +84,7 @@ class Transaction(Transaction):
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, tx_body):
|
||||
super().validate_id(tx_body)
|
||||
validate_transaction_schema(tx_body)
|
||||
validate_txn_obj('asset', tx_body['asset'], 'data', validate_key)
|
||||
validate_txn_obj('metadata', tx_body, 'metadata', validate_key)
|
||||
|
@ -71,8 +71,10 @@ def test_asset_id_mismatch(b, user_pk):
|
||||
|
||||
tx1 = Transaction.create([b.me], [([user_pk], 1)],
|
||||
metadata={'msg': random.random()})
|
||||
tx1.sign([b.me_private])
|
||||
tx2 = Transaction.create([b.me], [([user_pk], 1)],
|
||||
metadata={'msg': random.random()})
|
||||
tx2.sign([b.me_private])
|
||||
|
||||
with pytest.raises(AssetIdMismatch):
|
||||
Transaction.get_asset_id([tx1, tx2])
|
||||
|
@ -308,14 +308,13 @@ def test_count_blocks(signed_create_tx):
|
||||
assert query.count_blocks(conn) == 1
|
||||
|
||||
|
||||
def test_count_backlog(signed_create_tx):
|
||||
def test_count_backlog(signed_create_tx, signed_transfer_tx):
|
||||
from bigchaindb.backend import connect, query
|
||||
conn = connect()
|
||||
|
||||
# create and insert some transations
|
||||
conn.db.backlog.insert_one(signed_create_tx.to_dict())
|
||||
signed_create_tx.metadata = {'msg': 'aaa'}
|
||||
conn.db.backlog.insert_one(signed_create_tx.to_dict())
|
||||
conn.db.backlog.insert_one(signed_transfer_tx.to_dict())
|
||||
|
||||
assert query.count_backlog(conn) == 2
|
||||
|
||||
@ -437,17 +436,21 @@ def test_get_new_blocks_feed(b, create_tx):
|
||||
assert list(feed) == [b3]
|
||||
|
||||
|
||||
def test_get_spending_transactions(user_pk):
|
||||
def test_get_spending_transactions(user_pk, user_sk):
|
||||
from bigchaindb.backend import connect, query
|
||||
from bigchaindb.models import Block, Transaction
|
||||
conn = connect()
|
||||
|
||||
out = [([user_pk], 1)]
|
||||
tx1 = Transaction.create([user_pk], out * 3)
|
||||
tx1.sign([user_sk])
|
||||
inputs = tx1.to_inputs()
|
||||
tx2 = Transaction.transfer([inputs[0]], out, tx1.id)
|
||||
tx2.sign([user_sk])
|
||||
tx3 = Transaction.transfer([inputs[1]], out, tx1.id)
|
||||
tx3.sign([user_sk])
|
||||
tx4 = Transaction.transfer([inputs[2]], out, tx1.id)
|
||||
tx4.sign([user_sk])
|
||||
block = Block([tx1, tx2, tx3, tx4])
|
||||
conn.db.bigchain.insert_one(block.to_dict())
|
||||
|
||||
|
@ -202,3 +202,149 @@ def dummy_transaction():
|
||||
}],
|
||||
'version': '1.0'
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def unfulfilled_transaction():
|
||||
return {
|
||||
'asset': {
|
||||
'data': {
|
||||
'msg': 'Hello BigchainDB!',
|
||||
}
|
||||
},
|
||||
'id': None,
|
||||
'inputs': [{
|
||||
# XXX This could be None, see #1925
|
||||
# https://github.com/bigchaindb/bigchaindb/issues/1925
|
||||
'fulfillment': {
|
||||
'public_key': 'JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE',
|
||||
'type': 'ed25519-sha-256'
|
||||
},
|
||||
'fulfills': None,
|
||||
'owners_before': ['JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE']
|
||||
}],
|
||||
'metadata': None,
|
||||
'operation': 'CREATE',
|
||||
'outputs': [{
|
||||
'amount': '1',
|
||||
'condition': {
|
||||
'details': {
|
||||
'public_key': 'JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE',
|
||||
'type': 'ed25519-sha-256'
|
||||
},
|
||||
'uri': 'ni:///sha-256;49C5UWNODwtcINxLgLc90bMCFqCymFYONGEmV4a0sG4?fpt=ed25519-sha-256&cost=131072'},
|
||||
'public_keys': ['JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE']
|
||||
}],
|
||||
'version': '1.0'
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fulfilled_transaction():
|
||||
return {
|
||||
'asset': {
|
||||
'data': {
|
||||
'msg': 'Hello BigchainDB!',
|
||||
}
|
||||
},
|
||||
'id': None,
|
||||
'inputs': [{
|
||||
'fulfillment': ('pGSAIP_2P1Juh-94sD3uno1lxMPd9EkIalRo7QB014pT6dD9g'
|
||||
'UANRNxasDy1Dfg9C2Fk4UgHdYFsJzItVYi5JJ_vWc6rKltn0k'
|
||||
'jagynI0xfyR6X9NhzccTt5oiNH9mThEb4QmagN'),
|
||||
'fulfills': None,
|
||||
'owners_before': ['JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE']
|
||||
}],
|
||||
'metadata': None,
|
||||
'operation': 'CREATE',
|
||||
'outputs': [{
|
||||
'amount': '1',
|
||||
'condition': {
|
||||
'details': {
|
||||
'public_key': 'JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE',
|
||||
'type': 'ed25519-sha-256'
|
||||
},
|
||||
'uri': 'ni:///sha-256;49C5UWNODwtcINxLgLc90bMCFqCymFYONGEmV4a0sG4?fpt=ed25519-sha-256&cost=131072'},
|
||||
'public_keys': ['JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE']
|
||||
}],
|
||||
'version': '1.0'
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fulfilled_and_hashed_transaction():
|
||||
return {
|
||||
'asset': {
|
||||
'data': {
|
||||
'msg': 'Hello BigchainDB!',
|
||||
}
|
||||
},
|
||||
'id': '7a7c827cf4ef7985f08f4e9d16f5ffc58ca4e82271921dfbed32e70cb462485f',
|
||||
'inputs': [{
|
||||
'fulfillment': ('pGSAIP_2P1Juh-94sD3uno1lxMPd9EkIalRo7QB014pT6dD9g'
|
||||
'UANRNxasDy1Dfg9C2Fk4UgHdYFsJzItVYi5JJ_vWc6rKltn0k'
|
||||
'jagynI0xfyR6X9NhzccTt5oiNH9mThEb4QmagN'),
|
||||
'fulfills': None,
|
||||
'owners_before': ['JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE']
|
||||
}],
|
||||
'metadata': None,
|
||||
'operation': 'CREATE',
|
||||
'outputs': [{
|
||||
'amount': '1',
|
||||
'condition': {
|
||||
'details': {
|
||||
'public_key': 'JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE',
|
||||
'type': 'ed25519-sha-256'
|
||||
},
|
||||
'uri': 'ni:///sha-256;49C5UWNODwtcINxLgLc90bMCFqCymFYONGEmV4a0sG4?fpt=ed25519-sha-256&cost=131072'},
|
||||
'public_keys': ['JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE']
|
||||
}],
|
||||
'version': '1.0'
|
||||
}
|
||||
|
||||
|
||||
# TODO For reviewers: Pick which approach you like best: parametrized or not?
|
||||
@pytest.fixture(params=(
|
||||
{'id': None,
|
||||
'fulfillment': {
|
||||
'public_key': 'JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE',
|
||||
'type': 'ed25519-sha-256'}},
|
||||
{'id': None,
|
||||
'fulfillment': ('pGSAIP_2P1Juh-94sD3uno1lxMPd9EkIalRo7QB014pT6dD9g'
|
||||
'UANRNxasDy1Dfg9C2Fk4UgHdYFsJzItVYi5JJ_vWc6rKltn0k'
|
||||
'jagynI0xfyR6X9NhzccTt5oiNH9mThEb4QmagN')},
|
||||
{'id': '7a7c827cf4ef7985f08f4e9d16f5ffc58ca4e82271921dfbed32e70cb462485f',
|
||||
'fulfillment': ('pGSAIP_2P1Juh-94sD3uno1lxMPd9EkIalRo7QB014pT6dD9g'
|
||||
'UANRNxasDy1Dfg9C2Fk4UgHdYFsJzItVYi5JJ_vWc6rKltn0k'
|
||||
'jagynI0xfyR6X9NhzccTt5oiNH9mThEb4QmagN')},
|
||||
))
|
||||
def tri_state_transaction(request):
|
||||
tx = {
|
||||
'asset': {
|
||||
'data': {
|
||||
'msg': 'Hello BigchainDB!',
|
||||
}
|
||||
},
|
||||
'id': None,
|
||||
'inputs': [{
|
||||
'fulfillment': None,
|
||||
'fulfills': None,
|
||||
'owners_before': ['JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE']
|
||||
}],
|
||||
'metadata': None,
|
||||
'operation': 'CREATE',
|
||||
'outputs': [{
|
||||
'amount': '1',
|
||||
'condition': {
|
||||
'details': {
|
||||
'public_key': 'JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE',
|
||||
'type': 'ed25519-sha-256'
|
||||
},
|
||||
'uri': 'ni:///sha-256;49C5UWNODwtcINxLgLc90bMCFqCymFYONGEmV4a0sG4?fpt=ed25519-sha-256&cost=131072'},
|
||||
'public_keys': ['JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE']
|
||||
}],
|
||||
'version': '1.0'
|
||||
}
|
||||
tx['id'] = request.param['id']
|
||||
tx['inputs'][0]['fulfillment'] = request.param['fulfillment']
|
||||
return tx
|
||||
|
@ -1,10 +1,12 @@
|
||||
"""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 pytest import raises
|
||||
from pytest import mark, raises
|
||||
from sha3 import sha3_256
|
||||
|
||||
|
||||
def test_input_serialization(ffill_uri, user_pub):
|
||||
@ -35,6 +37,7 @@ def test_input_deserialization_with_uri(ffill_uri, user_pub):
|
||||
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
|
||||
|
||||
@ -303,10 +306,8 @@ def test_create_default_asset_on_tx_initialization(asset_definition):
|
||||
def test_transaction_serialization(user_input, user_output, data):
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
|
||||
tx_id = 'l0l'
|
||||
|
||||
expected = {
|
||||
'id': tx_id,
|
||||
'id': None,
|
||||
'version': Transaction.VERSION,
|
||||
# NOTE: This test assumes that Inputs and Outputs can
|
||||
# successfully be serialized
|
||||
@ -322,37 +323,14 @@ def test_transaction_serialization(user_input, user_output, data):
|
||||
tx = Transaction(Transaction.CREATE, {'data': data}, [user_input],
|
||||
[user_output])
|
||||
tx_dict = tx.to_dict()
|
||||
tx_dict['id'] = tx_id
|
||||
|
||||
assert tx_dict == expected
|
||||
|
||||
|
||||
def test_transaction_deserialization(user_input, user_output, data):
|
||||
def test_transaction_deserialization(tri_state_transaction):
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
from .utils import validate_transaction_model
|
||||
|
||||
expected_asset = {'data': data}
|
||||
expected = Transaction(Transaction.CREATE, expected_asset, [user_input],
|
||||
[user_output], None, Transaction.VERSION)
|
||||
|
||||
tx = {
|
||||
'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_no_signatures = Transaction._remove_signatures(tx)
|
||||
tx['id'] = Transaction._to_hash(Transaction._to_str(tx_no_signatures))
|
||||
tx = Transaction.from_dict(tx)
|
||||
|
||||
assert tx == expected
|
||||
|
||||
tx = Transaction.from_dict(tri_state_transaction)
|
||||
validate_transaction_model(tx)
|
||||
|
||||
|
||||
@ -543,7 +521,6 @@ def test_validate_input_with_invalid_parameters(utx):
|
||||
|
||||
input_conditions = [out.fulfillment.condition_uri for out in utx.outputs]
|
||||
tx_dict = utx.to_dict()
|
||||
tx_dict = Transaction._remove_signatures(tx_dict)
|
||||
tx_serialized = Transaction._to_str(tx_dict)
|
||||
valid = utx._input_valid(utx.inputs[0], tx_serialized, input_conditions)
|
||||
assert not valid
|
||||
@ -834,6 +811,7 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
|
||||
from .utils import validate_transaction_model
|
||||
|
||||
expected = {
|
||||
'id': None,
|
||||
'outputs': [user2_output.to_dict()],
|
||||
'metadata': None,
|
||||
'asset': {
|
||||
@ -861,7 +839,6 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
|
||||
transfer_tx = transfer_tx.to_dict()
|
||||
|
||||
expected_input = deepcopy(inputs[0])
|
||||
expected['id'] = transfer_tx['id']
|
||||
expected_input.fulfillment.sign(
|
||||
serialize(expected).encode(), b58decode(user_priv))
|
||||
expected_ffill = expected_input.fulfillment.serialize_uri()
|
||||
@ -971,6 +948,35 @@ def test_cant_add_empty_input():
|
||||
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
|
||||
|
@ -335,6 +335,15 @@ def signed_transfer_tx(signed_create_tx, user_pk, user_sk):
|
||||
return tx.sign([user_sk])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def double_spend_tx(signed_create_tx, carol_pubkey, user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
inputs = signed_create_tx.to_inputs()
|
||||
tx = Transaction.transfer(
|
||||
inputs, [([carol_pubkey], 1)], asset_id=signed_create_tx.id)
|
||||
return tx.sign([user_sk])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def structurally_valid_vote():
|
||||
return {
|
||||
|
@ -635,7 +635,7 @@ class TestTransactionValidation(object):
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_non_create_double_spend(self, b, signed_create_tx,
|
||||
signed_transfer_tx):
|
||||
signed_transfer_tx, double_spend_tx):
|
||||
from bigchaindb.common.exceptions import DoubleSpend
|
||||
|
||||
block1 = b.create_block([signed_create_tx])
|
||||
@ -655,10 +655,8 @@ class TestTransactionValidation(object):
|
||||
|
||||
sleep(1)
|
||||
|
||||
signed_transfer_tx.metadata = {'different': 1}
|
||||
# FIXME: https://github.com/bigchaindb/bigchaindb/issues/592
|
||||
with pytest.raises(DoubleSpend):
|
||||
b.validate_transaction(signed_transfer_tx)
|
||||
b.validate_transaction(double_spend_tx)
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_valid_non_create_transaction_after_block_creation(self, b,
|
||||
|
@ -18,6 +18,7 @@ def test_check_for_quorum_invalid(b, user_pk):
|
||||
|
||||
# create blocks with transactions
|
||||
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx1.sign([b.me_private])
|
||||
test_block = b.create_block([tx1])
|
||||
|
||||
# simulate a federation with four voters
|
||||
@ -51,6 +52,7 @@ def test_check_for_quorum_invalid_prev_node(b, user_pk):
|
||||
|
||||
# create blocks with transactions
|
||||
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx1.sign([b.me_private])
|
||||
test_block = b.create_block([tx1])
|
||||
|
||||
# simulate a federation with four voters
|
||||
@ -94,6 +96,7 @@ def test_check_for_quorum_valid(b, user_pk):
|
||||
|
||||
# create blocks with transactions
|
||||
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx1.sign([b.me_private])
|
||||
test_block = b.create_block([tx1])
|
||||
|
||||
# add voters to block and write
|
||||
@ -129,6 +132,7 @@ def test_check_requeue_transaction(b, user_pk):
|
||||
|
||||
# create blocks with transactions
|
||||
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx1.sign([b.me_private])
|
||||
test_block = b.create_block([tx1])
|
||||
|
||||
e.requeue_transactions(test_block)
|
||||
|
@ -378,6 +378,10 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch,
|
||||
vote2_doc['signature']) is True
|
||||
|
||||
|
||||
@pytest.mark.skip(
|
||||
reason=('Needs important modification following issue #1891:'
|
||||
'https://github.com/bigchaindb/bigchaindb/issues/1891')
|
||||
)
|
||||
@pytest.mark.bdb
|
||||
def test_unsigned_tx_in_block_voting(monkeypatch, b, user_pk, genesis_block):
|
||||
from bigchaindb.backend import query
|
||||
|
@ -57,14 +57,15 @@ class TestBlockModel(object):
|
||||
from bigchaindb.common.utils import gen_timestamp, serialize
|
||||
from bigchaindb.models import Block, Transaction
|
||||
|
||||
transactions = [Transaction.create([b.me], [([b.me], 1)])]
|
||||
transaction = Transaction.create([b.me], [([b.me], 1)])
|
||||
transaction.sign([b.me_private])
|
||||
timestamp = gen_timestamp()
|
||||
voters = ['Qaaa', 'Qbbb']
|
||||
expected = Block(transactions, b.me, timestamp, voters)
|
||||
expected = Block([transaction], b.me, timestamp, voters)
|
||||
|
||||
block = {
|
||||
'timestamp': timestamp,
|
||||
'transactions': [tx.to_dict() for tx in transactions],
|
||||
'transactions': [transaction.to_dict()],
|
||||
'node_pubkey': b.me,
|
||||
'voters': voters,
|
||||
}
|
||||
@ -97,12 +98,13 @@ class TestBlockModel(object):
|
||||
from bigchaindb.common.utils import gen_timestamp, serialize
|
||||
from bigchaindb.models import Block, Transaction
|
||||
|
||||
transactions = [Transaction.create([b.me], [([b.me], 1)])]
|
||||
transaction = Transaction.create([b.me], [([b.me], 1)])
|
||||
transaction.sign([b.me_private])
|
||||
timestamp = gen_timestamp()
|
||||
|
||||
block = {
|
||||
'timestamp': timestamp,
|
||||
'transactions': [tx.to_dict() for tx in transactions],
|
||||
'transactions': [transaction.to_dict()],
|
||||
'node_pubkey': b.me,
|
||||
'voters': list(b.federation),
|
||||
}
|
||||
@ -168,12 +170,14 @@ class TestBlockModel(object):
|
||||
# create 3 assets
|
||||
for asset in assets:
|
||||
tx = Transaction.create([b.me], [([b.me], 1)], asset=asset)
|
||||
tx.sign([b.me_private])
|
||||
txs.append(tx)
|
||||
|
||||
# create a `TRANSFER` transaction.
|
||||
# the asset in `TRANSFER` transactions is not extracted
|
||||
tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)],
|
||||
asset_id=txs[0].id)
|
||||
tx.sign([b.me_private])
|
||||
txs.append(tx)
|
||||
|
||||
# create the block
|
||||
@ -203,12 +207,14 @@ class TestBlockModel(object):
|
||||
# create 3 assets
|
||||
for asset in assets:
|
||||
tx = Transaction.create([b.me], [([b.me], 1)], asset=asset)
|
||||
tx.sign([b.me_private])
|
||||
txs.append(tx)
|
||||
|
||||
# create a `TRANSFER` transaction.
|
||||
# the asset in `TRANSFER` transactions is not extracted
|
||||
tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)],
|
||||
asset_id=txs[0].id)
|
||||
tx.sign([b.me_private])
|
||||
txs.append(tx)
|
||||
|
||||
# create the block
|
||||
@ -236,12 +242,14 @@ class TestBlockModel(object):
|
||||
# create 3 assets
|
||||
for asset in assets:
|
||||
tx = Transaction.create([b.me], [([b.me], 1)], asset=asset)
|
||||
tx.sign([b.me_private])
|
||||
txs.append(tx)
|
||||
|
||||
# create a `TRANSFER` transaction.
|
||||
# the asset in `TRANSFER` transactions is not extracted
|
||||
tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)],
|
||||
asset_id=txs[0].id)
|
||||
tx.sign([b.me_private])
|
||||
txs.append(tx)
|
||||
|
||||
# create the block
|
||||
@ -268,12 +276,14 @@ class TestBlockModel(object):
|
||||
# create 3 assets
|
||||
for asset in assets:
|
||||
tx = Transaction.create([b.me], [([b.me], 1)], asset=asset)
|
||||
tx.sign([b.me_private])
|
||||
txs.append(tx)
|
||||
|
||||
# create a `TRANSFER` transaction.
|
||||
# the asset in `TRANSFER` transactions is not extracted
|
||||
tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)],
|
||||
asset_id=txs[0].id)
|
||||
tx.sign([b.me_private])
|
||||
txs.append(tx)
|
||||
|
||||
# create the block
|
||||
|
@ -49,17 +49,20 @@ def test_get_outputs_by_public_key(b, user_pk, user2_pk, blockdata):
|
||||
]
|
||||
|
||||
|
||||
def test_filter_spent_outputs(b, user_pk):
|
||||
def test_filter_spent_outputs(b, user_pk, user_sk):
|
||||
out = [([user_pk], 1)]
|
||||
tx1 = Transaction.create([user_pk], out * 3)
|
||||
|
||||
tx1.sign([user_sk])
|
||||
# There are 3 inputs
|
||||
inputs = tx1.to_inputs()
|
||||
|
||||
# Each spent individually
|
||||
tx2 = Transaction.transfer([inputs[0]], out, tx1.id)
|
||||
tx2.sign([user_sk])
|
||||
tx3 = Transaction.transfer([inputs[1]], out, tx1.id)
|
||||
tx3.sign([user_sk])
|
||||
tx4 = Transaction.transfer([inputs[2]], out, tx1.id)
|
||||
tx4.sign([user_sk])
|
||||
|
||||
# The CREATE and first TRANSFER are valid. tx2 produces a new unspent.
|
||||
for tx in [tx1, tx2]:
|
||||
@ -86,17 +89,21 @@ def test_filter_spent_outputs(b, user_pk):
|
||||
}
|
||||
|
||||
|
||||
def test_filter_unspent_outputs(b, user_pk):
|
||||
def test_filter_unspent_outputs(b, user_pk, user_sk):
|
||||
out = [([user_pk], 1)]
|
||||
tx1 = Transaction.create([user_pk], out * 3)
|
||||
tx1.sign([user_sk])
|
||||
|
||||
# There are 3 inputs
|
||||
inputs = tx1.to_inputs()
|
||||
|
||||
# Each spent individually
|
||||
tx2 = Transaction.transfer([inputs[0]], out, tx1.id)
|
||||
tx2.sign([user_sk])
|
||||
tx3 = Transaction.transfer([inputs[1]], out, tx1.id)
|
||||
tx3.sign([user_sk])
|
||||
tx4 = Transaction.transfer([inputs[2]], out, tx1.id)
|
||||
tx4.sign([user_sk])
|
||||
|
||||
# The CREATE and first TRANSFER are valid. tx2 produces a new unspent.
|
||||
for tx in [tx1, tx2]:
|
||||
|
@ -2,11 +2,13 @@
|
||||
structural / schematic issues are caught when reading a transaction
|
||||
(ie going from dict -> transaction).
|
||||
"""
|
||||
import json
|
||||
|
||||
import pytest
|
||||
import sha3
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from bigchaindb.common.exceptions import (AmountError, InvalidHash,
|
||||
from bigchaindb.common.exceptions import (AmountError,
|
||||
SchemaValidationError,
|
||||
ThresholdTooDeep)
|
||||
from bigchaindb.models import Transaction
|
||||
@ -28,137 +30,175 @@ def validate_raises(tx, exc=SchemaValidationError):
|
||||
|
||||
|
||||
# We should test that validation works when we expect it to
|
||||
def test_validation_passes(create_tx):
|
||||
validate(create_tx)
|
||||
def test_validation_passes(signed_create_tx):
|
||||
Transaction.from_dict(signed_create_tx.to_dict())
|
||||
|
||||
|
||||
################################################################################
|
||||
# ID
|
||||
|
||||
|
||||
def test_tx_serialization_hash_function(create_tx):
|
||||
import sha3
|
||||
import json
|
||||
tx = create_tx.to_dict()
|
||||
tx['inputs'][0]['fulfillment'] = None
|
||||
del tx['id']
|
||||
def test_tx_serialization_hash_function(signed_create_tx):
|
||||
tx = signed_create_tx.to_dict()
|
||||
tx['id'] = None
|
||||
payload = json.dumps(tx, skipkeys=False, sort_keys=True,
|
||||
separators=(',', ':'))
|
||||
assert sha3.sha3_256(payload.encode()).hexdigest() == create_tx.id
|
||||
assert sha3.sha3_256(payload.encode()).hexdigest() == signed_create_tx.id
|
||||
|
||||
|
||||
def test_tx_serialization_with_incorrect_hash(create_tx):
|
||||
tx = create_tx.to_dict()
|
||||
def test_tx_serialization_with_incorrect_hash(signed_create_tx):
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
from bigchaindb.common.exceptions import InvalidHash
|
||||
tx = signed_create_tx.to_dict()
|
||||
tx['id'] = 'a' * 64
|
||||
validate_raises(tx, InvalidHash)
|
||||
with pytest.raises(InvalidHash):
|
||||
Transaction.validate_id(tx)
|
||||
|
||||
|
||||
def test_tx_serialization_with_no_hash(create_tx):
|
||||
tx = create_tx.to_dict()
|
||||
def test_tx_serialization_with_no_hash(signed_create_tx):
|
||||
from bigchaindb.common.exceptions import InvalidHash
|
||||
tx = signed_create_tx.to_dict()
|
||||
del tx['id']
|
||||
validate_raises(tx)
|
||||
with pytest.raises(InvalidHash):
|
||||
Transaction.from_dict(tx)
|
||||
|
||||
|
||||
################################################################################
|
||||
# Operation
|
||||
|
||||
def test_validate_invalid_operation(create_tx):
|
||||
def test_validate_invalid_operation(b, create_tx):
|
||||
create_tx.operation = 'something invalid'
|
||||
validate_raises(create_tx)
|
||||
signed_tx = create_tx.sign([b.me_private])
|
||||
validate_raises(signed_tx)
|
||||
|
||||
|
||||
################################################################################
|
||||
# Metadata
|
||||
|
||||
def test_validate_fails_metadata_empty_dict(create_tx):
|
||||
def test_validate_fails_metadata_empty_dict(b, create_tx):
|
||||
create_tx.metadata = {'a': 1}
|
||||
validate(create_tx)
|
||||
signed_tx = create_tx.sign([b.me_private])
|
||||
validate(signed_tx)
|
||||
|
||||
create_tx._id = None
|
||||
create_tx.fulfillment = None
|
||||
create_tx.metadata = None
|
||||
validate(create_tx)
|
||||
signed_tx = create_tx.sign([b.me_private])
|
||||
validate(signed_tx)
|
||||
|
||||
create_tx._id = None
|
||||
create_tx.fulfillment = None
|
||||
create_tx.metadata = {}
|
||||
validate_raises(create_tx)
|
||||
signed_tx = create_tx.sign([b.me_private])
|
||||
validate_raises(signed_tx)
|
||||
|
||||
|
||||
################################################################################
|
||||
# Asset
|
||||
|
||||
def test_transfer_asset_schema(signed_transfer_tx):
|
||||
def test_transfer_asset_schema(user_sk, signed_transfer_tx):
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
tx = signed_transfer_tx.to_dict()
|
||||
validate(tx)
|
||||
tx['id'] = None
|
||||
tx['asset']['data'] = {}
|
||||
tx = Transaction.from_dict(tx).sign([user_sk]).to_dict()
|
||||
validate_raises(tx)
|
||||
tx['id'] = None
|
||||
del tx['asset']['data']
|
||||
tx['asset']['id'] = 'b' * 63
|
||||
tx = Transaction.from_dict(tx).sign([user_sk]).to_dict()
|
||||
validate_raises(tx)
|
||||
|
||||
|
||||
def test_create_tx_no_asset_id(create_tx):
|
||||
def test_create_tx_no_asset_id(b, create_tx):
|
||||
create_tx.asset['id'] = 'b' * 64
|
||||
validate_raises(create_tx)
|
||||
signed_tx = create_tx.sign([b.me_private])
|
||||
validate_raises(signed_tx)
|
||||
|
||||
|
||||
def test_create_tx_asset_type(create_tx):
|
||||
def test_create_tx_asset_type(b, create_tx):
|
||||
create_tx.asset['data'] = 'a'
|
||||
validate_raises(create_tx)
|
||||
signed_tx = create_tx.sign([b.me_private])
|
||||
validate_raises(signed_tx)
|
||||
|
||||
|
||||
def test_create_tx_no_asset_data(create_tx):
|
||||
def test_create_tx_no_asset_data(b, create_tx):
|
||||
tx_body = create_tx.to_dict()
|
||||
del tx_body['asset']['data']
|
||||
tx_body_no_signatures = Transaction._remove_signatures(tx_body)
|
||||
tx_body_serialized = Transaction._to_str(tx_body_no_signatures)
|
||||
tx_body['id'] = Transaction._to_hash(tx_body_serialized)
|
||||
tx_serialized = json.dumps(
|
||||
tx_body, skipkeys=False, sort_keys=True, separators=(',', ':'))
|
||||
tx_body['id'] = sha3.sha3_256(tx_serialized.encode()).hexdigest()
|
||||
validate_raises(tx_body)
|
||||
|
||||
|
||||
################################################################################
|
||||
# Inputs
|
||||
|
||||
def test_no_inputs(create_tx):
|
||||
def test_no_inputs(b, create_tx):
|
||||
create_tx.inputs = []
|
||||
validate_raises(create_tx)
|
||||
signed_tx = create_tx.sign([b.me_private])
|
||||
validate_raises(signed_tx)
|
||||
|
||||
|
||||
def test_create_single_input(create_tx):
|
||||
def test_create_single_input(b, create_tx):
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
tx = create_tx.to_dict()
|
||||
tx['inputs'] += tx['inputs']
|
||||
tx = Transaction.from_dict(tx).sign([b.me_private]).to_dict()
|
||||
validate_raises(tx)
|
||||
tx['id'] = None
|
||||
tx['inputs'] = []
|
||||
tx = Transaction.from_dict(tx).sign([b.me_private]).to_dict()
|
||||
validate_raises(tx)
|
||||
|
||||
|
||||
def test_create_tx_no_fulfills(create_tx):
|
||||
def test_create_tx_no_fulfills(b, create_tx):
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
tx = create_tx.to_dict()
|
||||
tx['inputs'][0]['fulfills'] = {'tx': 'a' * 64, 'output': 0}
|
||||
tx['inputs'][0]['fulfills'] = {'transaction_id': 'a' * 64,
|
||||
'output_index': 0}
|
||||
tx = Transaction.from_dict(tx).sign([b.me_private]).to_dict()
|
||||
validate_raises(tx)
|
||||
|
||||
|
||||
def test_transfer_has_inputs(signed_transfer_tx):
|
||||
def test_transfer_has_inputs(user_sk, signed_transfer_tx):
|
||||
signed_transfer_tx.inputs = []
|
||||
signed_transfer_tx._id = None
|
||||
signed_transfer_tx.sign([user_sk])
|
||||
validate_raises(signed_transfer_tx)
|
||||
|
||||
|
||||
################################################################################
|
||||
# Outputs
|
||||
|
||||
def test_low_amounts(create_tx, signed_transfer_tx):
|
||||
for tx in [create_tx, signed_transfer_tx]:
|
||||
def test_low_amounts(b, user_sk, create_tx, signed_transfer_tx):
|
||||
for sk, tx in [(b.me_private, create_tx), (user_sk, signed_transfer_tx)]:
|
||||
tx.outputs[0].amount = 0
|
||||
tx._id = None
|
||||
tx.sign([sk])
|
||||
validate_raises(tx, AmountError)
|
||||
tx.outputs[0].amount = -1
|
||||
tx._id = None
|
||||
tx.sign([sk])
|
||||
validate_raises(tx)
|
||||
|
||||
|
||||
def test_high_amounts(create_tx):
|
||||
def test_high_amounts(b, create_tx):
|
||||
# Should raise a SchemaValidationError - don't want to allow ridiculously
|
||||
# large numbers to get converted to int
|
||||
create_tx.outputs[0].amount = 10 ** 21
|
||||
create_tx.sign([b.me_private])
|
||||
validate_raises(create_tx)
|
||||
# Should raise AmountError
|
||||
create_tx.outputs[0].amount = 9 * 10 ** 18 + 1
|
||||
create_tx._id = None
|
||||
create_tx.sign([b.me_private])
|
||||
validate_raises(create_tx, AmountError)
|
||||
# Should pass
|
||||
create_tx.outputs[0].amount -= 1
|
||||
create_tx._id = None
|
||||
create_tx.sign([b.me_private])
|
||||
validate(create_tx)
|
||||
|
||||
|
||||
@ -196,10 +236,17 @@ def test_unsupported_condition_type():
|
||||
################################################################################
|
||||
# Version
|
||||
|
||||
def test_validate_version(create_tx):
|
||||
def test_validate_version(b, create_tx):
|
||||
create_tx.version = '1.0'
|
||||
create_tx.sign([b.me_private])
|
||||
validate(create_tx)
|
||||
|
||||
create_tx.version = '0.10'
|
||||
create_tx._id = None
|
||||
create_tx.sign([b.me_private])
|
||||
validate_raises(create_tx)
|
||||
|
||||
create_tx.version = '110'
|
||||
create_tx._id = None
|
||||
create_tx.sign([b.me_private])
|
||||
validate_raises(create_tx)
|
||||
|
@ -1,7 +1,11 @@
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
import base58
|
||||
import pytest
|
||||
from cryptoconditions import Ed25519Sha256
|
||||
from sha3 import sha3_256
|
||||
|
||||
from bigchaindb.common import crypto
|
||||
|
||||
|
||||
@ -165,9 +169,16 @@ def test_post_create_transaction_with_invalid_signature(mock_logger,
|
||||
from bigchaindb.models import Transaction
|
||||
user_priv, user_pub = crypto.generate_key_pair()
|
||||
|
||||
tx = Transaction.create([user_pub], [([user_pub], 1)])
|
||||
tx = tx.sign([user_priv]).to_dict()
|
||||
tx = Transaction.create([user_pub], [([user_pub], 1)]).to_dict()
|
||||
tx['inputs'][0]['fulfillment'] = 64 * '0'
|
||||
tx['id'] = sha3_256(
|
||||
json.dumps(
|
||||
tx,
|
||||
sort_keys=True,
|
||||
separators=(',', ':'),
|
||||
ensure_ascii=False,
|
||||
).encode(),
|
||||
).hexdigest()
|
||||
|
||||
res = client.post(TX_ENDPOINT, data=json.dumps(tx))
|
||||
expected_status_code = 400
|
||||
@ -202,9 +213,25 @@ def test_post_create_transaction_with_invalid_structure(client):
|
||||
def test_post_create_transaction_with_invalid_schema(mock_logger, client):
|
||||
from bigchaindb.models import Transaction
|
||||
user_priv, user_pub = crypto.generate_key_pair()
|
||||
tx = Transaction.create(
|
||||
[user_pub], [([user_pub], 1)]).sign([user_priv]).to_dict()
|
||||
tx = Transaction.create([user_pub], [([user_pub], 1)]).to_dict()
|
||||
del tx['version']
|
||||
ed25519 = Ed25519Sha256(public_key=base58.b58decode(user_pub))
|
||||
message = json.dumps(
|
||||
tx,
|
||||
sort_keys=True,
|
||||
separators=(',', ':'),
|
||||
ensure_ascii=False,
|
||||
).encode()
|
||||
ed25519.sign(message, base58.b58decode(user_priv))
|
||||
tx['inputs'][0]['fulfillment'] = ed25519.serialize_uri()
|
||||
tx['id'] = sha3_256(
|
||||
json.dumps(
|
||||
tx,
|
||||
sort_keys=True,
|
||||
separators=(',', ':'),
|
||||
ensure_ascii=False,
|
||||
).encode(),
|
||||
).hexdigest()
|
||||
res = client.post(TX_ENDPOINT, data=json.dumps(tx))
|
||||
expected_status_code = 400
|
||||
expected_error_message = (
|
||||
@ -308,6 +335,7 @@ def test_post_invalid_transfer_transaction_returns_400(b, client, user_pk):
|
||||
transfer_tx = Transaction.transfer(create_tx.to_inputs(),
|
||||
[([user_pub], 1)],
|
||||
asset_id=create_tx.id)
|
||||
transfer_tx._hash()
|
||||
|
||||
res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict()))
|
||||
expected_status_code = 400
|
||||
|
Loading…
x
Reference in New Issue
Block a user