mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge branch 'master' into tendermint
This commit is contained in:
commit
cb268a3832
@ -13,7 +13,9 @@ required:
|
|||||||
- version
|
- version
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
"$ref": "#/definitions/sha3_hexdigest"
|
anyOf:
|
||||||
|
- "$ref": "#/definitions/sha3_hexdigest"
|
||||||
|
- type: 'null'
|
||||||
operation:
|
operation:
|
||||||
"$ref": "#/definitions/operation"
|
"$ref": "#/definitions/operation"
|
||||||
asset:
|
asset:
|
||||||
|
@ -112,7 +112,7 @@ class Input(object):
|
|||||||
InvalidSignature: If an Input's URI couldn't be parsed.
|
InvalidSignature: If an Input's URI couldn't be parsed.
|
||||||
"""
|
"""
|
||||||
fulfillment = data['fulfillment']
|
fulfillment = data['fulfillment']
|
||||||
if not isinstance(fulfillment, Fulfillment):
|
if not isinstance(fulfillment, (Fulfillment, type(None))):
|
||||||
try:
|
try:
|
||||||
fulfillment = Fulfillment.from_uri(data['fulfillment'])
|
fulfillment = Fulfillment.from_uri(data['fulfillment'])
|
||||||
except ASN1DecodeError:
|
except ASN1DecodeError:
|
||||||
@ -477,7 +477,7 @@ class Transaction(object):
|
|||||||
VERSION = '1.0'
|
VERSION = '1.0'
|
||||||
|
|
||||||
def __init__(self, operation, asset, inputs=None, outputs=None,
|
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.
|
"""The constructor allows to create a customizable Transaction.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
@ -495,6 +495,7 @@ class Transaction(object):
|
|||||||
metadata (dict): Metadata to be stored along with the
|
metadata (dict): Metadata to be stored along with the
|
||||||
Transaction.
|
Transaction.
|
||||||
version (string): Defines the version number of a 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:
|
if operation not in Transaction.ALLOWED_OPERATIONS:
|
||||||
allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS)
|
allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS)
|
||||||
@ -528,6 +529,14 @@ class Transaction(object):
|
|||||||
self.inputs = inputs or []
|
self.inputs = inputs or []
|
||||||
self.outputs = outputs or []
|
self.outputs = outputs or []
|
||||||
self.metadata = metadata
|
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
|
@classmethod
|
||||||
def create(cls, tx_signers, recipients, metadata=None, asset=None):
|
def create(cls, tx_signers, recipients, metadata=None, asset=None):
|
||||||
@ -756,6 +765,9 @@ class Transaction(object):
|
|||||||
tx_serialized = Transaction._to_str(tx_dict)
|
tx_serialized = Transaction._to_str(tx_dict)
|
||||||
for i, input_ in enumerate(self.inputs):
|
for i, input_ in enumerate(self.inputs):
|
||||||
self.inputs[i] = self._sign_input(input_, tx_serialized, key_pairs)
|
self.inputs[i] = self._sign_input(input_, tx_serialized, key_pairs)
|
||||||
|
|
||||||
|
self._hash()
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -907,6 +919,7 @@ class Transaction(object):
|
|||||||
|
|
||||||
tx_dict = self.to_dict()
|
tx_dict = self.to_dict()
|
||||||
tx_dict = Transaction._remove_signatures(tx_dict)
|
tx_dict = Transaction._remove_signatures(tx_dict)
|
||||||
|
tx_dict['id'] = None
|
||||||
tx_serialized = Transaction._to_str(tx_dict)
|
tx_serialized = Transaction._to_str(tx_dict)
|
||||||
|
|
||||||
def validate(i, output_condition_uri=None):
|
def validate(i, output_condition_uri=None):
|
||||||
@ -965,22 +978,16 @@ class Transaction(object):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: The Transaction as an alternative serialization format.
|
dict: The Transaction as an alternative serialization format.
|
||||||
"""
|
"""
|
||||||
tx = {
|
return {
|
||||||
'inputs': [input_.to_dict() for input_ in self.inputs],
|
'inputs': [input_.to_dict() for input_ in self.inputs],
|
||||||
'outputs': [output.to_dict() for output in self.outputs],
|
'outputs': [output.to_dict() for output in self.outputs],
|
||||||
'operation': str(self.operation),
|
'operation': str(self.operation),
|
||||||
'metadata': self.metadata,
|
'metadata': self.metadata,
|
||||||
'asset': self.asset,
|
'asset': self.asset,
|
||||||
'version': self.version,
|
'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
|
@staticmethod
|
||||||
# TODO: Remove `_dict` prefix of variable.
|
# TODO: Remove `_dict` prefix of variable.
|
||||||
def _remove_signatures(tx_dict):
|
def _remove_signatures(tx_dict):
|
||||||
@ -1010,7 +1017,7 @@ class Transaction(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self):
|
def id(self):
|
||||||
return self.to_hash()
|
return self._id
|
||||||
|
|
||||||
def to_hash(self):
|
def to_hash(self):
|
||||||
return self.to_dict()['id']
|
return self.to_dict()['id']
|
||||||
@ -1069,12 +1076,13 @@ class Transaction(object):
|
|||||||
# NOTE: Remove reference to avoid side effects
|
# NOTE: Remove reference to avoid side effects
|
||||||
tx_body = deepcopy(tx_body)
|
tx_body = deepcopy(tx_body)
|
||||||
try:
|
try:
|
||||||
proposed_tx_id = tx_body.pop('id')
|
proposed_tx_id = tx_body['id']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise InvalidHash('No transaction id found!')
|
raise InvalidHash('No transaction id found!')
|
||||||
|
|
||||||
tx_body_no_signatures = Transaction._remove_signatures(tx_body)
|
tx_body['id'] = None
|
||||||
tx_body_serialized = Transaction._to_str(tx_body_no_signatures)
|
|
||||||
|
tx_body_serialized = Transaction._to_str(tx_body)
|
||||||
valid_tx_id = Transaction._to_hash(tx_body_serialized)
|
valid_tx_id = Transaction._to_hash(tx_body_serialized)
|
||||||
|
|
||||||
if proposed_tx_id != valid_tx_id:
|
if proposed_tx_id != valid_tx_id:
|
||||||
@ -1092,8 +1100,7 @@ class Transaction(object):
|
|||||||
Returns:
|
Returns:
|
||||||
:class:`~bigchaindb.common.transaction.Transaction`
|
:class:`~bigchaindb.common.transaction.Transaction`
|
||||||
"""
|
"""
|
||||||
cls.validate_id(tx)
|
|
||||||
inputs = [Input.from_dict(input_) for input_ in tx['inputs']]
|
inputs = [Input.from_dict(input_) for input_ in tx['inputs']]
|
||||||
outputs = [Output.from_dict(output) for output in tx['outputs']]
|
outputs = [Output.from_dict(output) for output in tx['outputs']]
|
||||||
return cls(tx['operation'], tx['asset'], inputs, outputs,
|
return cls(tx['operation'], tx['asset'], inputs, outputs,
|
||||||
tx['metadata'], tx['version'])
|
tx['metadata'], tx['version'], hash_id=tx['id'])
|
||||||
|
@ -88,6 +88,7 @@ class Transaction(Transaction):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, tx_body):
|
def from_dict(cls, tx_body):
|
||||||
|
super().validate_id(tx_body)
|
||||||
validate_transaction_schema(tx_body)
|
validate_transaction_schema(tx_body)
|
||||||
validate_txn_obj('asset', tx_body['asset'], 'data', validate_key)
|
validate_txn_obj('asset', tx_body['asset'], 'data', validate_key)
|
||||||
validate_txn_obj('metadata', tx_body, 'metadata', 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)],
|
tx1 = Transaction.create([b.me], [([user_pk], 1)],
|
||||||
metadata={'msg': random.random()})
|
metadata={'msg': random.random()})
|
||||||
|
tx1.sign([b.me_private])
|
||||||
tx2 = Transaction.create([b.me], [([user_pk], 1)],
|
tx2 = Transaction.create([b.me], [([user_pk], 1)],
|
||||||
metadata={'msg': random.random()})
|
metadata={'msg': random.random()})
|
||||||
|
tx2.sign([b.me_private])
|
||||||
|
|
||||||
with pytest.raises(AssetIdMismatch):
|
with pytest.raises(AssetIdMismatch):
|
||||||
Transaction.get_asset_id([tx1, tx2])
|
Transaction.get_asset_id([tx1, tx2])
|
||||||
|
@ -308,14 +308,13 @@ def test_count_blocks(signed_create_tx):
|
|||||||
assert query.count_blocks(conn) == 1
|
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
|
from bigchaindb.backend import connect, query
|
||||||
conn = connect()
|
conn = connect()
|
||||||
|
|
||||||
# create and insert some transations
|
# create and insert some transations
|
||||||
conn.db.backlog.insert_one(signed_create_tx.to_dict())
|
conn.db.backlog.insert_one(signed_create_tx.to_dict())
|
||||||
signed_create_tx.metadata = {'msg': 'aaa'}
|
conn.db.backlog.insert_one(signed_transfer_tx.to_dict())
|
||||||
conn.db.backlog.insert_one(signed_create_tx.to_dict())
|
|
||||||
|
|
||||||
assert query.count_backlog(conn) == 2
|
assert query.count_backlog(conn) == 2
|
||||||
|
|
||||||
@ -437,17 +436,21 @@ def test_get_new_blocks_feed(b, create_tx):
|
|||||||
assert list(feed) == [b3]
|
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.backend import connect, query
|
||||||
from bigchaindb.models import Block, Transaction
|
from bigchaindb.models import Block, Transaction
|
||||||
conn = connect()
|
conn = connect()
|
||||||
|
|
||||||
out = [([user_pk], 1)]
|
out = [([user_pk], 1)]
|
||||||
tx1 = Transaction.create([user_pk], out * 3)
|
tx1 = Transaction.create([user_pk], out * 3)
|
||||||
|
tx1.sign([user_sk])
|
||||||
inputs = tx1.to_inputs()
|
inputs = tx1.to_inputs()
|
||||||
tx2 = Transaction.transfer([inputs[0]], out, tx1.id)
|
tx2 = Transaction.transfer([inputs[0]], out, tx1.id)
|
||||||
|
tx2.sign([user_sk])
|
||||||
tx3 = Transaction.transfer([inputs[1]], out, tx1.id)
|
tx3 = Transaction.transfer([inputs[1]], out, tx1.id)
|
||||||
|
tx3.sign([user_sk])
|
||||||
tx4 = Transaction.transfer([inputs[2]], out, tx1.id)
|
tx4 = Transaction.transfer([inputs[2]], out, tx1.id)
|
||||||
|
tx4.sign([user_sk])
|
||||||
block = Block([tx1, tx2, tx3, tx4])
|
block = Block([tx1, tx2, tx3, tx4])
|
||||||
conn.db.bigchain.insert_one(block.to_dict())
|
conn.db.bigchain.insert_one(block.to_dict())
|
||||||
|
|
||||||
|
@ -202,3 +202,149 @@ def dummy_transaction():
|
|||||||
}],
|
}],
|
||||||
'version': '1.0'
|
'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.
|
"""These are tests of the API of the Transaction class and associated classes.
|
||||||
Tests for transaction validation are separate.
|
Tests for transaction validation are separate.
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
from base58 import b58encode, b58decode
|
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):
|
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
|
assert input == expected
|
||||||
|
|
||||||
|
|
||||||
|
@mark.skip(reason='None is tolerated because it is None before fulfilling.')
|
||||||
def test_input_deserialization_with_invalid_input(user_pub):
|
def test_input_deserialization_with_invalid_input(user_pub):
|
||||||
from bigchaindb.common.transaction import Input
|
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):
|
def test_transaction_serialization(user_input, user_output, data):
|
||||||
from bigchaindb.common.transaction import Transaction
|
from bigchaindb.common.transaction import Transaction
|
||||||
|
|
||||||
tx_id = 'l0l'
|
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
'id': tx_id,
|
'id': None,
|
||||||
'version': Transaction.VERSION,
|
'version': Transaction.VERSION,
|
||||||
# NOTE: This test assumes that Inputs and Outputs can
|
# NOTE: This test assumes that Inputs and Outputs can
|
||||||
# successfully be serialized
|
# successfully be serialized
|
||||||
@ -322,37 +323,14 @@ def test_transaction_serialization(user_input, user_output, data):
|
|||||||
tx = Transaction(Transaction.CREATE, {'data': data}, [user_input],
|
tx = Transaction(Transaction.CREATE, {'data': data}, [user_input],
|
||||||
[user_output])
|
[user_output])
|
||||||
tx_dict = tx.to_dict()
|
tx_dict = tx.to_dict()
|
||||||
tx_dict['id'] = tx_id
|
|
||||||
|
|
||||||
assert tx_dict == expected
|
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 bigchaindb.common.transaction import Transaction
|
||||||
from .utils import validate_transaction_model
|
from .utils import validate_transaction_model
|
||||||
|
tx = Transaction.from_dict(tri_state_transaction)
|
||||||
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
|
|
||||||
|
|
||||||
validate_transaction_model(tx)
|
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]
|
input_conditions = [out.fulfillment.condition_uri for out in utx.outputs]
|
||||||
tx_dict = utx.to_dict()
|
tx_dict = utx.to_dict()
|
||||||
tx_dict = Transaction._remove_signatures(tx_dict)
|
|
||||||
tx_serialized = Transaction._to_str(tx_dict)
|
tx_serialized = Transaction._to_str(tx_dict)
|
||||||
valid = utx._input_valid(utx.inputs[0], tx_serialized, input_conditions)
|
valid = utx._input_valid(utx.inputs[0], tx_serialized, input_conditions)
|
||||||
assert not valid
|
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
|
from .utils import validate_transaction_model
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
|
'id': None,
|
||||||
'outputs': [user2_output.to_dict()],
|
'outputs': [user2_output.to_dict()],
|
||||||
'metadata': None,
|
'metadata': None,
|
||||||
'asset': {
|
'asset': {
|
||||||
@ -861,7 +839,6 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
|
|||||||
transfer_tx = transfer_tx.to_dict()
|
transfer_tx = transfer_tx.to_dict()
|
||||||
|
|
||||||
expected_input = deepcopy(inputs[0])
|
expected_input = deepcopy(inputs[0])
|
||||||
expected['id'] = transfer_tx['id']
|
|
||||||
expected_input.fulfillment.sign(
|
expected_input.fulfillment.sign(
|
||||||
serialize(expected).encode(), b58decode(user_priv))
|
serialize(expected).encode(), b58decode(user_priv))
|
||||||
expected_ffill = expected_input.fulfillment.serialize_uri()
|
expected_ffill = expected_input.fulfillment.serialize_uri()
|
||||||
@ -971,6 +948,35 @@ def test_cant_add_empty_input():
|
|||||||
tx.add_input(None)
|
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):
|
def test_output_from_dict_invalid_amount(user_output):
|
||||||
from bigchaindb.common.transaction import Output
|
from bigchaindb.common.transaction import Output
|
||||||
from bigchaindb.common.exceptions import AmountError
|
from bigchaindb.common.exceptions import AmountError
|
||||||
|
@ -339,6 +339,15 @@ def signed_transfer_tx(signed_create_tx, user_pk, user_sk):
|
|||||||
return tx.sign([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
|
@pytest.fixture
|
||||||
def structurally_valid_vote():
|
def structurally_valid_vote():
|
||||||
return {
|
return {
|
||||||
|
@ -635,7 +635,7 @@ class TestTransactionValidation(object):
|
|||||||
|
|
||||||
@pytest.mark.usefixtures('inputs')
|
@pytest.mark.usefixtures('inputs')
|
||||||
def test_non_create_double_spend(self, b, signed_create_tx,
|
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
|
from bigchaindb.common.exceptions import DoubleSpend
|
||||||
|
|
||||||
block1 = b.create_block([signed_create_tx])
|
block1 = b.create_block([signed_create_tx])
|
||||||
@ -655,10 +655,8 @@ class TestTransactionValidation(object):
|
|||||||
|
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
signed_transfer_tx.metadata = {'different': 1}
|
|
||||||
# FIXME: https://github.com/bigchaindb/bigchaindb/issues/592
|
|
||||||
with pytest.raises(DoubleSpend):
|
with pytest.raises(DoubleSpend):
|
||||||
b.validate_transaction(signed_transfer_tx)
|
b.validate_transaction(double_spend_tx)
|
||||||
|
|
||||||
@pytest.mark.usefixtures('inputs')
|
@pytest.mark.usefixtures('inputs')
|
||||||
def test_valid_non_create_transaction_after_block_creation(self, b,
|
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
|
# create blocks with transactions
|
||||||
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
||||||
|
tx1.sign([b.me_private])
|
||||||
test_block = b.create_block([tx1])
|
test_block = b.create_block([tx1])
|
||||||
|
|
||||||
# simulate a federation with four voters
|
# 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
|
# create blocks with transactions
|
||||||
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
||||||
|
tx1.sign([b.me_private])
|
||||||
test_block = b.create_block([tx1])
|
test_block = b.create_block([tx1])
|
||||||
|
|
||||||
# simulate a federation with four voters
|
# simulate a federation with four voters
|
||||||
@ -94,6 +96,7 @@ def test_check_for_quorum_valid(b, user_pk):
|
|||||||
|
|
||||||
# create blocks with transactions
|
# create blocks with transactions
|
||||||
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
||||||
|
tx1.sign([b.me_private])
|
||||||
test_block = b.create_block([tx1])
|
test_block = b.create_block([tx1])
|
||||||
|
|
||||||
# add voters to block and write
|
# add voters to block and write
|
||||||
@ -129,6 +132,7 @@ def test_check_requeue_transaction(b, user_pk):
|
|||||||
|
|
||||||
# create blocks with transactions
|
# create blocks with transactions
|
||||||
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
||||||
|
tx1.sign([b.me_private])
|
||||||
test_block = b.create_block([tx1])
|
test_block = b.create_block([tx1])
|
||||||
|
|
||||||
e.requeue_transactions(test_block)
|
e.requeue_transactions(test_block)
|
||||||
|
@ -378,6 +378,10 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch,
|
|||||||
vote2_doc['signature']) is True
|
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
|
@pytest.mark.bdb
|
||||||
def test_unsigned_tx_in_block_voting(monkeypatch, b, user_pk, genesis_block):
|
def test_unsigned_tx_in_block_voting(monkeypatch, b, user_pk, genesis_block):
|
||||||
from bigchaindb.backend import query
|
from bigchaindb.backend import query
|
||||||
|
@ -57,14 +57,15 @@ class TestBlockModel(object):
|
|||||||
from bigchaindb.common.utils import gen_timestamp, serialize
|
from bigchaindb.common.utils import gen_timestamp, serialize
|
||||||
from bigchaindb.models import Block, Transaction
|
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()
|
timestamp = gen_timestamp()
|
||||||
voters = ['Qaaa', 'Qbbb']
|
voters = ['Qaaa', 'Qbbb']
|
||||||
expected = Block(transactions, b.me, timestamp, voters)
|
expected = Block([transaction], b.me, timestamp, voters)
|
||||||
|
|
||||||
block = {
|
block = {
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'transactions': [tx.to_dict() for tx in transactions],
|
'transactions': [transaction.to_dict()],
|
||||||
'node_pubkey': b.me,
|
'node_pubkey': b.me,
|
||||||
'voters': voters,
|
'voters': voters,
|
||||||
}
|
}
|
||||||
@ -97,12 +98,13 @@ class TestBlockModel(object):
|
|||||||
from bigchaindb.common.utils import gen_timestamp, serialize
|
from bigchaindb.common.utils import gen_timestamp, serialize
|
||||||
from bigchaindb.models import Block, Transaction
|
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()
|
timestamp = gen_timestamp()
|
||||||
|
|
||||||
block = {
|
block = {
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'transactions': [tx.to_dict() for tx in transactions],
|
'transactions': [transaction.to_dict()],
|
||||||
'node_pubkey': b.me,
|
'node_pubkey': b.me,
|
||||||
'voters': list(b.federation),
|
'voters': list(b.federation),
|
||||||
}
|
}
|
||||||
@ -168,12 +170,14 @@ class TestBlockModel(object):
|
|||||||
# create 3 assets
|
# create 3 assets
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
tx = Transaction.create([b.me], [([b.me], 1)], asset=asset)
|
tx = Transaction.create([b.me], [([b.me], 1)], asset=asset)
|
||||||
|
tx.sign([b.me_private])
|
||||||
txs.append(tx)
|
txs.append(tx)
|
||||||
|
|
||||||
# create a `TRANSFER` transaction.
|
# create a `TRANSFER` transaction.
|
||||||
# the asset in `TRANSFER` transactions is not extracted
|
# the asset in `TRANSFER` transactions is not extracted
|
||||||
tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)],
|
tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)],
|
||||||
asset_id=txs[0].id)
|
asset_id=txs[0].id)
|
||||||
|
tx.sign([b.me_private])
|
||||||
txs.append(tx)
|
txs.append(tx)
|
||||||
|
|
||||||
# create the block
|
# create the block
|
||||||
@ -203,12 +207,14 @@ class TestBlockModel(object):
|
|||||||
# create 3 assets
|
# create 3 assets
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
tx = Transaction.create([b.me], [([b.me], 1)], asset=asset)
|
tx = Transaction.create([b.me], [([b.me], 1)], asset=asset)
|
||||||
|
tx.sign([b.me_private])
|
||||||
txs.append(tx)
|
txs.append(tx)
|
||||||
|
|
||||||
# create a `TRANSFER` transaction.
|
# create a `TRANSFER` transaction.
|
||||||
# the asset in `TRANSFER` transactions is not extracted
|
# the asset in `TRANSFER` transactions is not extracted
|
||||||
tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)],
|
tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)],
|
||||||
asset_id=txs[0].id)
|
asset_id=txs[0].id)
|
||||||
|
tx.sign([b.me_private])
|
||||||
txs.append(tx)
|
txs.append(tx)
|
||||||
|
|
||||||
# create the block
|
# create the block
|
||||||
@ -236,12 +242,14 @@ class TestBlockModel(object):
|
|||||||
# create 3 assets
|
# create 3 assets
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
tx = Transaction.create([b.me], [([b.me], 1)], asset=asset)
|
tx = Transaction.create([b.me], [([b.me], 1)], asset=asset)
|
||||||
|
tx.sign([b.me_private])
|
||||||
txs.append(tx)
|
txs.append(tx)
|
||||||
|
|
||||||
# create a `TRANSFER` transaction.
|
# create a `TRANSFER` transaction.
|
||||||
# the asset in `TRANSFER` transactions is not extracted
|
# the asset in `TRANSFER` transactions is not extracted
|
||||||
tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)],
|
tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)],
|
||||||
asset_id=txs[0].id)
|
asset_id=txs[0].id)
|
||||||
|
tx.sign([b.me_private])
|
||||||
txs.append(tx)
|
txs.append(tx)
|
||||||
|
|
||||||
# create the block
|
# create the block
|
||||||
@ -268,12 +276,14 @@ class TestBlockModel(object):
|
|||||||
# create 3 assets
|
# create 3 assets
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
tx = Transaction.create([b.me], [([b.me], 1)], asset=asset)
|
tx = Transaction.create([b.me], [([b.me], 1)], asset=asset)
|
||||||
|
tx.sign([b.me_private])
|
||||||
txs.append(tx)
|
txs.append(tx)
|
||||||
|
|
||||||
# create a `TRANSFER` transaction.
|
# create a `TRANSFER` transaction.
|
||||||
# the asset in `TRANSFER` transactions is not extracted
|
# the asset in `TRANSFER` transactions is not extracted
|
||||||
tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)],
|
tx = Transaction.transfer(txs[0].to_inputs(), [([b.me], 1)],
|
||||||
asset_id=txs[0].id)
|
asset_id=txs[0].id)
|
||||||
|
tx.sign([b.me_private])
|
||||||
txs.append(tx)
|
txs.append(tx)
|
||||||
|
|
||||||
# create the block
|
# 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)]
|
out = [([user_pk], 1)]
|
||||||
tx1 = Transaction.create([user_pk], out * 3)
|
tx1 = Transaction.create([user_pk], out * 3)
|
||||||
|
tx1.sign([user_sk])
|
||||||
# There are 3 inputs
|
# There are 3 inputs
|
||||||
inputs = tx1.to_inputs()
|
inputs = tx1.to_inputs()
|
||||||
|
|
||||||
# Each spent individually
|
# Each spent individually
|
||||||
tx2 = Transaction.transfer([inputs[0]], out, tx1.id)
|
tx2 = Transaction.transfer([inputs[0]], out, tx1.id)
|
||||||
|
tx2.sign([user_sk])
|
||||||
tx3 = Transaction.transfer([inputs[1]], out, tx1.id)
|
tx3 = Transaction.transfer([inputs[1]], out, tx1.id)
|
||||||
|
tx3.sign([user_sk])
|
||||||
tx4 = Transaction.transfer([inputs[2]], out, tx1.id)
|
tx4 = Transaction.transfer([inputs[2]], out, tx1.id)
|
||||||
|
tx4.sign([user_sk])
|
||||||
|
|
||||||
# The CREATE and first TRANSFER are valid. tx2 produces a new unspent.
|
# The CREATE and first TRANSFER are valid. tx2 produces a new unspent.
|
||||||
for tx in [tx1, tx2]:
|
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)]
|
out = [([user_pk], 1)]
|
||||||
tx1 = Transaction.create([user_pk], out * 3)
|
tx1 = Transaction.create([user_pk], out * 3)
|
||||||
|
tx1.sign([user_sk])
|
||||||
|
|
||||||
# There are 3 inputs
|
# There are 3 inputs
|
||||||
inputs = tx1.to_inputs()
|
inputs = tx1.to_inputs()
|
||||||
|
|
||||||
# Each spent individually
|
# Each spent individually
|
||||||
tx2 = Transaction.transfer([inputs[0]], out, tx1.id)
|
tx2 = Transaction.transfer([inputs[0]], out, tx1.id)
|
||||||
|
tx2.sign([user_sk])
|
||||||
tx3 = Transaction.transfer([inputs[1]], out, tx1.id)
|
tx3 = Transaction.transfer([inputs[1]], out, tx1.id)
|
||||||
|
tx3.sign([user_sk])
|
||||||
tx4 = Transaction.transfer([inputs[2]], out, tx1.id)
|
tx4 = Transaction.transfer([inputs[2]], out, tx1.id)
|
||||||
|
tx4.sign([user_sk])
|
||||||
|
|
||||||
# The CREATE and first TRANSFER are valid. tx2 produces a new unspent.
|
# The CREATE and first TRANSFER are valid. tx2 produces a new unspent.
|
||||||
for tx in [tx1, tx2]:
|
for tx in [tx1, tx2]:
|
||||||
|
@ -2,11 +2,13 @@
|
|||||||
structural / schematic issues are caught when reading a transaction
|
structural / schematic issues are caught when reading a transaction
|
||||||
(ie going from dict -> transaction).
|
(ie going from dict -> transaction).
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import sha3
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from bigchaindb.common.exceptions import (AmountError, InvalidHash,
|
from bigchaindb.common.exceptions import (AmountError,
|
||||||
SchemaValidationError,
|
SchemaValidationError,
|
||||||
ThresholdTooDeep)
|
ThresholdTooDeep)
|
||||||
from bigchaindb.models import Transaction
|
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
|
# We should test that validation works when we expect it to
|
||||||
def test_validation_passes(create_tx):
|
def test_validation_passes(signed_create_tx):
|
||||||
validate(create_tx)
|
Transaction.from_dict(signed_create_tx.to_dict())
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# ID
|
# ID
|
||||||
|
|
||||||
|
|
||||||
def test_tx_serialization_hash_function(create_tx):
|
def test_tx_serialization_hash_function(signed_create_tx):
|
||||||
import sha3
|
tx = signed_create_tx.to_dict()
|
||||||
import json
|
tx['id'] = None
|
||||||
tx = create_tx.to_dict()
|
|
||||||
tx['inputs'][0]['fulfillment'] = None
|
|
||||||
del tx['id']
|
|
||||||
payload = json.dumps(tx, skipkeys=False, sort_keys=True,
|
payload = json.dumps(tx, skipkeys=False, sort_keys=True,
|
||||||
separators=(',', ':'))
|
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):
|
def test_tx_serialization_with_incorrect_hash(signed_create_tx):
|
||||||
tx = create_tx.to_dict()
|
from bigchaindb.common.transaction import Transaction
|
||||||
|
from bigchaindb.common.exceptions import InvalidHash
|
||||||
|
tx = signed_create_tx.to_dict()
|
||||||
tx['id'] = 'a' * 64
|
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):
|
def test_tx_serialization_with_no_hash(signed_create_tx):
|
||||||
tx = create_tx.to_dict()
|
from bigchaindb.common.exceptions import InvalidHash
|
||||||
|
tx = signed_create_tx.to_dict()
|
||||||
del tx['id']
|
del tx['id']
|
||||||
validate_raises(tx)
|
with pytest.raises(InvalidHash):
|
||||||
|
Transaction.from_dict(tx)
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Operation
|
# Operation
|
||||||
|
|
||||||
def test_validate_invalid_operation(create_tx):
|
def test_validate_invalid_operation(b, create_tx):
|
||||||
create_tx.operation = 'something invalid'
|
create_tx.operation = 'something invalid'
|
||||||
validate_raises(create_tx)
|
signed_tx = create_tx.sign([b.me_private])
|
||||||
|
validate_raises(signed_tx)
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Metadata
|
# 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}
|
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
|
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 = {}
|
create_tx.metadata = {}
|
||||||
validate_raises(create_tx)
|
signed_tx = create_tx.sign([b.me_private])
|
||||||
|
validate_raises(signed_tx)
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Asset
|
# 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()
|
tx = signed_transfer_tx.to_dict()
|
||||||
validate(tx)
|
validate(tx)
|
||||||
|
tx['id'] = None
|
||||||
tx['asset']['data'] = {}
|
tx['asset']['data'] = {}
|
||||||
|
tx = Transaction.from_dict(tx).sign([user_sk]).to_dict()
|
||||||
validate_raises(tx)
|
validate_raises(tx)
|
||||||
|
tx['id'] = None
|
||||||
del tx['asset']['data']
|
del tx['asset']['data']
|
||||||
tx['asset']['id'] = 'b' * 63
|
tx['asset']['id'] = 'b' * 63
|
||||||
|
tx = Transaction.from_dict(tx).sign([user_sk]).to_dict()
|
||||||
validate_raises(tx)
|
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
|
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'
|
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()
|
tx_body = create_tx.to_dict()
|
||||||
del tx_body['asset']['data']
|
del tx_body['asset']['data']
|
||||||
tx_body_no_signatures = Transaction._remove_signatures(tx_body)
|
tx_serialized = json.dumps(
|
||||||
tx_body_serialized = Transaction._to_str(tx_body_no_signatures)
|
tx_body, skipkeys=False, sort_keys=True, separators=(',', ':'))
|
||||||
tx_body['id'] = Transaction._to_hash(tx_body_serialized)
|
tx_body['id'] = sha3.sha3_256(tx_serialized.encode()).hexdigest()
|
||||||
validate_raises(tx_body)
|
validate_raises(tx_body)
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Inputs
|
# Inputs
|
||||||
|
|
||||||
def test_no_inputs(create_tx):
|
def test_no_inputs(b, create_tx):
|
||||||
create_tx.inputs = []
|
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 = create_tx.to_dict()
|
||||||
tx['inputs'] += tx['inputs']
|
tx['inputs'] += tx['inputs']
|
||||||
|
tx = Transaction.from_dict(tx).sign([b.me_private]).to_dict()
|
||||||
validate_raises(tx)
|
validate_raises(tx)
|
||||||
|
tx['id'] = None
|
||||||
tx['inputs'] = []
|
tx['inputs'] = []
|
||||||
|
tx = Transaction.from_dict(tx).sign([b.me_private]).to_dict()
|
||||||
validate_raises(tx)
|
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 = 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)
|
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.inputs = []
|
||||||
|
signed_transfer_tx._id = None
|
||||||
|
signed_transfer_tx.sign([user_sk])
|
||||||
validate_raises(signed_transfer_tx)
|
validate_raises(signed_transfer_tx)
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Outputs
|
# Outputs
|
||||||
|
|
||||||
def test_low_amounts(create_tx, signed_transfer_tx):
|
def test_low_amounts(b, user_sk, create_tx, signed_transfer_tx):
|
||||||
for tx in [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.outputs[0].amount = 0
|
||||||
|
tx._id = None
|
||||||
|
tx.sign([sk])
|
||||||
validate_raises(tx, AmountError)
|
validate_raises(tx, AmountError)
|
||||||
tx.outputs[0].amount = -1
|
tx.outputs[0].amount = -1
|
||||||
|
tx._id = None
|
||||||
|
tx.sign([sk])
|
||||||
validate_raises(tx)
|
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
|
# Should raise a SchemaValidationError - don't want to allow ridiculously
|
||||||
# large numbers to get converted to int
|
# large numbers to get converted to int
|
||||||
create_tx.outputs[0].amount = 10 ** 21
|
create_tx.outputs[0].amount = 10 ** 21
|
||||||
|
create_tx.sign([b.me_private])
|
||||||
validate_raises(create_tx)
|
validate_raises(create_tx)
|
||||||
# Should raise AmountError
|
# Should raise AmountError
|
||||||
create_tx.outputs[0].amount = 9 * 10 ** 18 + 1
|
create_tx.outputs[0].amount = 9 * 10 ** 18 + 1
|
||||||
|
create_tx._id = None
|
||||||
|
create_tx.sign([b.me_private])
|
||||||
validate_raises(create_tx, AmountError)
|
validate_raises(create_tx, AmountError)
|
||||||
# Should pass
|
# Should pass
|
||||||
create_tx.outputs[0].amount -= 1
|
create_tx.outputs[0].amount -= 1
|
||||||
|
create_tx._id = None
|
||||||
|
create_tx.sign([b.me_private])
|
||||||
validate(create_tx)
|
validate(create_tx)
|
||||||
|
|
||||||
|
|
||||||
@ -196,10 +236,17 @@ def test_unsupported_condition_type():
|
|||||||
################################################################################
|
################################################################################
|
||||||
# Version
|
# Version
|
||||||
|
|
||||||
def test_validate_version(create_tx):
|
def test_validate_version(b, create_tx):
|
||||||
create_tx.version = '1.0'
|
create_tx.version = '1.0'
|
||||||
|
create_tx.sign([b.me_private])
|
||||||
validate(create_tx)
|
validate(create_tx)
|
||||||
|
|
||||||
create_tx.version = '0.10'
|
create_tx.version = '0.10'
|
||||||
|
create_tx._id = None
|
||||||
|
create_tx.sign([b.me_private])
|
||||||
validate_raises(create_tx)
|
validate_raises(create_tx)
|
||||||
|
|
||||||
create_tx.version = '110'
|
create_tx.version = '110'
|
||||||
|
create_tx._id = None
|
||||||
|
create_tx.sign([b.me_private])
|
||||||
validate_raises(create_tx)
|
validate_raises(create_tx)
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import json
|
import json
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import base58
|
||||||
import pytest
|
import pytest
|
||||||
|
from cryptoconditions import Ed25519Sha256
|
||||||
|
from sha3 import sha3_256
|
||||||
|
|
||||||
from bigchaindb.common import crypto
|
from bigchaindb.common import crypto
|
||||||
|
|
||||||
|
|
||||||
@ -165,9 +169,16 @@ def test_post_create_transaction_with_invalid_signature(mock_logger,
|
|||||||
from bigchaindb.models import Transaction
|
from bigchaindb.models import Transaction
|
||||||
user_priv, user_pub = crypto.generate_key_pair()
|
user_priv, user_pub = crypto.generate_key_pair()
|
||||||
|
|
||||||
tx = Transaction.create([user_pub], [([user_pub], 1)])
|
tx = Transaction.create([user_pub], [([user_pub], 1)]).to_dict()
|
||||||
tx = tx.sign([user_priv]).to_dict()
|
|
||||||
tx['inputs'][0]['fulfillment'] = 64 * '0'
|
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))
|
res = client.post(TX_ENDPOINT, data=json.dumps(tx))
|
||||||
expected_status_code = 400
|
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):
|
def test_post_create_transaction_with_invalid_schema(mock_logger, client):
|
||||||
from bigchaindb.models import Transaction
|
from bigchaindb.models import Transaction
|
||||||
user_priv, user_pub = crypto.generate_key_pair()
|
user_priv, user_pub = crypto.generate_key_pair()
|
||||||
tx = Transaction.create(
|
tx = Transaction.create([user_pub], [([user_pub], 1)]).to_dict()
|
||||||
[user_pub], [([user_pub], 1)]).sign([user_priv]).to_dict()
|
|
||||||
del tx['version']
|
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))
|
res = client.post(TX_ENDPOINT, data=json.dumps(tx))
|
||||||
expected_status_code = 400
|
expected_status_code = 400
|
||||||
expected_error_message = (
|
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(),
|
transfer_tx = Transaction.transfer(create_tx.to_inputs(),
|
||||||
[([user_pub], 1)],
|
[([user_pub], 1)],
|
||||||
asset_id=create_tx.id)
|
asset_id=create_tx.id)
|
||||||
|
transfer_tx._hash()
|
||||||
|
|
||||||
res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict()))
|
res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict()))
|
||||||
expected_status_code = 400
|
expected_status_code = 400
|
||||||
|
Loading…
x
Reference in New Issue
Block a user