Merge pull request #855 from bigchaindb/flat-transaction

Flat transaction
This commit is contained in:
libscott 2016-12-01 15:03:43 +01:00 committed by GitHub
commit 97ff0efd8b
11 changed files with 194 additions and 234 deletions

View File

@ -5,10 +5,14 @@ type: object
additionalProperties: false additionalProperties: false
title: Transaction Schema title: Transaction Schema
description: | description: |
This is the outer transaction wrapper. It contains the ID, version and the body of the transaction, which is also called ``transaction``. A transaction represents the creation or transfer of assets in BigchainDB.
required: required:
- id - id
- transaction - fulfillments
- conditions
- operation
- metadata
- asset
- version - version
properties: properties:
id: id:
@ -18,51 +22,38 @@ properties:
derived hashes and signatures from the transaction, serializing it to derived hashes and signatures from the transaction, serializing it to
JSON with keys in sorted order and then hashing the resulting string JSON with keys in sorted order and then hashing the resulting string
with sha3. with sha3.
transaction: operation:
type: object "$ref": "#/definitions/operation"
title: transaction asset:
"$ref": "#/definitions/asset"
description: | description: |
See: `Transaction Body`_. Description of the asset being transacted.
additionalProperties: false
required:
- fulfillments
- conditions
- operation
- metadata
- asset
properties:
operation:
"$ref": "#/definitions/operation"
asset:
"$ref": "#/definitions/asset"
description: |
Description of the asset being transacted.
See: `Asset`_. See: `Asset`_.
fulfillments: fulfillments:
type: array type: array
title: "Fulfillments list" title: "Fulfillments list"
description: | description: |
Array of the fulfillments (inputs) of a transaction. Array of the fulfillments (inputs) of a transaction.
See: Fulfillment_. See: Fulfillment_.
items: items:
"$ref": "#/definitions/fulfillment" "$ref": "#/definitions/fulfillment"
conditions: conditions:
type: array type: array
description: | description: |
Array of conditions (outputs) provided by this transaction. Array of conditions (outputs) provided by this transaction.
See: Condition_. See: Condition_.
items: items:
"$ref": "#/definitions/condition" "$ref": "#/definitions/condition"
metadata: metadata:
"$ref": "#/definitions/metadata" "$ref": "#/definitions/metadata"
description: | description: |
User provided transaction metadata. This field may be ``null`` or may User provided transaction metadata. This field may be ``null`` or may
contain an object with freeform metadata. contain an id and an object with freeform metadata.
See: `Metadata`_. See: `Metadata`_.
version: version:
type: integer type: integer
minimum: 1 minimum: 1

View File

@ -1110,7 +1110,7 @@ class Transaction(object):
# NOTE: An `asset` in a `TRANSFER` only contains the asset's id # NOTE: An `asset` in a `TRANSFER` only contains the asset's id
asset = {'id': self.asset.data_id} asset = {'id': self.asset.data_id}
tx_body = { tx = {
'fulfillments': [fulfillment.to_dict(fid) for fid, fulfillment 'fulfillments': [fulfillment.to_dict(fid) for fid, fulfillment
in enumerate(self.fulfillments)], in enumerate(self.fulfillments)],
'conditions': [condition.to_dict(cid) for cid, condition 'conditions': [condition.to_dict(cid) for cid, condition
@ -1118,10 +1118,7 @@ class Transaction(object):
'operation': str(self.operation), 'operation': str(self.operation),
'metadata': self.metadata, 'metadata': self.metadata,
'asset': asset, 'asset': asset,
}
tx = {
'version': self.version, 'version': self.version,
'transaction': tx_body,
} }
tx_no_signatures = Transaction._remove_signatures(tx) tx_no_signatures = Transaction._remove_signatures(tx)
@ -1146,7 +1143,7 @@ class Transaction(object):
# NOTE: We remove the reference since we need `tx_dict` only for the # NOTE: We remove the reference since we need `tx_dict` only for the
# transaction's hash # transaction's hash
tx_dict = deepcopy(tx_dict) tx_dict = deepcopy(tx_dict)
for fulfillment in tx_dict['transaction']['fulfillments']: for fulfillment in tx_dict['fulfillments']:
# NOTE: Not all Cryptoconditions return a `signature` key (e.g. # NOTE: Not all Cryptoconditions return a `signature` key (e.g.
# ThresholdSha256Fulfillment), so setting it to `None` in any # ThresholdSha256Fulfillment), so setting it to `None` in any
# case could yield incorrect signatures. This is why we only # case could yield incorrect signatures. This is why we only
@ -1196,7 +1193,7 @@ class Transaction(object):
raise InvalidHash() raise InvalidHash()
@classmethod @classmethod
def from_dict(cls, tx_body): def from_dict(cls, tx):
"""Transforms a Python dictionary to a Transaction object. """Transforms a Python dictionary to a Transaction object.
Args: Args:
@ -1205,8 +1202,7 @@ class Transaction(object):
Returns: Returns:
:class:`~bigchaindb.common.transaction.Transaction` :class:`~bigchaindb.common.transaction.Transaction`
""" """
cls.validate_structure(tx_body) cls.validate_structure(tx)
tx = tx_body['transaction']
fulfillments = [Fulfillment.from_dict(fulfillment) for fulfillment fulfillments = [Fulfillment.from_dict(fulfillment) for fulfillment
in tx['fulfillments']] in tx['fulfillments']]
conditions = [Condition.from_dict(condition) for condition conditions = [Condition.from_dict(condition) for condition
@ -1217,4 +1213,4 @@ class Transaction(object):
asset = AssetLink.from_dict(tx['asset']) asset = AssetLink.from_dict(tx['asset'])
return cls(tx['operation'], asset, fulfillments, conditions, return cls(tx['operation'], asset, fulfillments, conditions,
tx['metadata'], tx_body['version']) tx['metadata'], tx['version'])

View File

@ -367,7 +367,7 @@ class Bigchain(object):
cursor = self.backend.get_asset_by_id(asset_id) cursor = self.backend.get_asset_by_id(asset_id)
cursor = list(cursor) cursor = list(cursor)
if cursor: if cursor:
return Asset.from_dict(cursor[0]['transaction']['asset']) return Asset.from_dict(cursor[0]['asset'])
def get_spent(self, txid, cid): def get_spent(self, txid, cid):
"""Check if a `txid` was already used as an input. """Check if a `txid` was already used as an input.
@ -436,7 +436,7 @@ class Bigchain(object):
# use it after the execution of this function. # use it after the execution of this function.
# a transaction can contain multiple outputs (conditions) so we need to iterate over all of them # a transaction can contain multiple outputs (conditions) so we need to iterate over all of them
# to get a list of outputs available to spend # to get a list of outputs available to spend
for index, cond in enumerate(tx['transaction']['conditions']): for index, cond in enumerate(tx['conditions']):
# for simple signature conditions there are no subfulfillments # for simple signature conditions there are no subfulfillments
# check if the owner is in the condition `owners_after` # check if the owner is in the condition `owners_after`
if len(cond['owners_after']) == 1: if len(cond['owners_after']) == 1:

View File

@ -159,7 +159,7 @@ class RethinkDBBackend:
r.table('bigchain', read_mode=self.read_mode) r.table('bigchain', read_mode=self.read_mode)
.get_all(asset_id, index='asset_id') .get_all(asset_id, index='asset_id')
.concat_map(lambda block: block['block']['transactions']) .concat_map(lambda block: block['block']['transactions'])
.filter(lambda transaction: transaction['transaction']['asset']['id'] == asset_id) .filter(lambda transaction: transaction['asset']['id'] == asset_id)
.get_field('id')) .get_field('id'))
def get_asset_by_id(self, asset_id): def get_asset_by_id(self, asset_id):
@ -176,10 +176,10 @@ class RethinkDBBackend:
.get_all(asset_id, index='asset_id') .get_all(asset_id, index='asset_id')
.concat_map(lambda block: block['block']['transactions']) .concat_map(lambda block: block['block']['transactions'])
.filter(lambda transaction: .filter(lambda transaction:
transaction['transaction']['asset']['id'] == asset_id) transaction['asset']['id'] == asset_id)
.filter(lambda transaction: .filter(lambda transaction:
transaction['transaction']['operation'] == 'CREATE') transaction['operation'] == 'CREATE')
.pluck({'transaction': 'asset'})) .pluck('asset'))
def get_spent(self, transaction_id, condition_id): def get_spent(self, transaction_id, condition_id):
"""Check if a `txid` was already used as an input. """Check if a `txid` was already used as an input.
@ -199,7 +199,7 @@ class RethinkDBBackend:
return self.connection.run( return self.connection.run(
r.table('bigchain', read_mode=self.read_mode) r.table('bigchain', read_mode=self.read_mode)
.concat_map(lambda doc: doc['block']['transactions']) .concat_map(lambda doc: doc['block']['transactions'])
.filter(lambda transaction: transaction['transaction']['fulfillments'].contains( .filter(lambda transaction: transaction['fulfillments'].contains(
lambda fulfillment: fulfillment['input'] == {'txid': transaction_id, 'cid': condition_id}))) lambda fulfillment: fulfillment['input'] == {'txid': transaction_id, 'cid': condition_id})))
def get_owned_ids(self, owner): def get_owned_ids(self, owner):
@ -216,7 +216,7 @@ class RethinkDBBackend:
return self.connection.run( return self.connection.run(
r.table('bigchain', read_mode=self.read_mode) r.table('bigchain', read_mode=self.read_mode)
.concat_map(lambda doc: doc['block']['transactions']) .concat_map(lambda doc: doc['block']['transactions'])
.filter(lambda tx: tx['transaction']['conditions'].contains( .filter(lambda tx: tx['conditions'].contains(
lambda c: c['owners_after'].contains(owner)))) lambda c: c['owners_after'].contains(owner))))
def get_votes_by_block_id(self, block_id): def get_votes_by_block_id(self, block_id):

View File

@ -119,7 +119,7 @@ def create_bigchain_secondary_index(conn, dbname):
# secondary index for asset uuid # secondary index for asset uuid
r.db(dbname).table('bigchain')\ r.db(dbname).table('bigchain')\
.index_create('asset_id', .index_create('asset_id',
r.row['block']['transactions']['transaction']['asset']['id'], multi=True)\ r.row['block']['transactions']['asset']['id'], multi=True)\
.run(conn) .run(conn)
# wait for rethinkdb to finish creating secondary indexes # wait for rethinkdb to finish creating secondary indexes

View File

@ -156,4 +156,4 @@ def is_genesis_block(block):
try: try:
return block.transactions[0].operation == 'GENESIS' return block.transactions[0].operation == 'GENESIS'
except AttributeError: except AttributeError:
return block['block']['transactions'][0]['transaction']['operation'] == 'GENESIS' return block['block']['transactions'][0]['operation'] == 'GENESIS'

View File

@ -27,8 +27,6 @@ Transaction Schema
* `Transaction`_ * `Transaction`_
* `Transaction Body`_
* Condition_ * Condition_
* Fulfillment_ * Fulfillment_
@ -58,11 +56,6 @@ Transaction Schema
Transaction Transaction
----------- -----------
%(wrapper)s
Transaction Body
----------------
%(transaction)s %(transaction)s
Condition Condition
@ -158,9 +151,7 @@ def main():
""" Main function """ """ Main function """
defs = TX_SCHEMA['definitions'] defs = TX_SCHEMA['definitions']
doc = TPL_DOC % { doc = TPL_DOC % {
'wrapper': render_section('Transaction', TX_SCHEMA), 'transaction': render_section('Transaction', TX_SCHEMA),
'transaction': render_section('Transaction',
TX_SCHEMA['properties']['transaction']),
'condition': render_section('Condition', defs['condition']), 'condition': render_section('Condition', defs['condition']),
'fulfillment': render_section('Fulfillment', defs['fulfillment']), 'fulfillment': render_section('Fulfillment', defs['fulfillment']),
'asset': render_section('Asset', defs['asset']), 'asset': render_section('Asset', defs['asset']),

View File

@ -22,38 +22,35 @@ A transaction has the following structure:
{ {
"id": "<hash of transaction, excluding signatures (see explanation)>", "id": "<hash of transaction, excluding signatures (see explanation)>",
"version": "<version number of the transaction model>", "version": "<version number of the transaction model>",
"transaction": { "fulfillments": ["<list of fulfillments>"],
"fulfillments": ["<list of fulfillments>"], "conditions": ["<list of conditions>"],
"conditions": ["<list of conditions>"], "operation": "<string>",
"operation": "<string>", "asset": "<digital asset description (explained in the next section)>",
"asset": "<digital asset description (explained in the next section)>", "metadata": {
"metadata": { "id": "<uuid>",
"id": "<uuid>", "data": "<any JSON document>"
"data": "<any JSON document>"
}
} }
} }
Here's some explanation of the contents of a :ref:`transaction <transaction>`: Here's some explanation of the contents of a :ref:`transaction <transaction>`:
- :ref:`id <transaction.id>`: The id of the transaction, and also the database primary key. - id: The :ref:`id <transaction.id>` of the transaction, and also the database primary key.
- :ref:`version <transaction.version>`: Version number of the transaction model, so that software can support different transaction models. - version: :ref:`Version <transaction.version>` number of the transaction model, so that software can support different transaction models.
- :ref:`transaction <Transaction Body>`: - **fulfillments**: List of fulfillments. Each :ref:`fulfillment <Fulfillment>` contains a pointer to an unspent asset
- **fulfillments**: List of fulfillments. Each :ref:`fulfillment <Fulfillment>` contains a pointer to an unspent asset and a *crypto fulfillment* that satisfies a spending condition set on the unspent asset. A *fulfillment*
and a *crypto fulfillment* that satisfies a spending condition set on the unspent asset. A *fulfillment* is usually a signature proving the ownership of the asset.
is usually a signature proving the ownership of the asset. See :doc:`./crypto-conditions`.
See :doc:`./crypto-conditions`.
- **conditions**: List of conditions. Each :ref:`condition <Condition>` is a *crypto-condition* that needs to be fulfilled by a transfer transaction in order to transfer ownership to new owners. - **conditions**: List of conditions. Each :ref:`condition <Condition>` is a *crypto-condition* that needs to be fulfilled by a transfer transaction in order to transfer ownership to new owners.
See :doc:`./crypto-conditions`. See :doc:`./crypto-conditions`.
- **operation**: String representation of the :ref:`operation <transaction.operation>` being performed (currently either "CREATE", "TRANSFER" or "GENESIS"). It determines how the transaction should be validated. - **operation**: String representation of the :ref:`operation <transaction.operation>` being performed (currently either "CREATE", "TRANSFER" or "GENESIS"). It determines how the transaction should be validated.
- **asset**: Definition of the digital :ref:`asset <Asset>`. See next section. - **asset**: Definition of the digital :ref:`asset <Asset>`. See next section.
- **metadata**: - **metadata**:
- :ref:`id <metadata.id>`: UUID version 4 (random) converted to a string of hex digits in standard form. - :ref:`id <metadata.id>`: UUID version 4 (random) converted to a string of hex digits in standard form.
- :ref:`data <metadata.data>`: Can be any JSON document. It may be empty in the case of a transfer transaction. - :ref:`data <metadata.data>`: Can be any JSON document. It may be empty in the case of a transfer transaction.
Later, when we get to the models for the block and the vote, we'll see that both include a signature (from the node which created it). You may wonder why transactions don't have signatures... The answer is that they do! They're just hidden inside the ``fulfillment`` string of each fulfillment. A creation transaction is signed by whoever created it. A transfer transaction is signed by whoever currently controls or owns it. Later, when we get to the models for the block and the vote, we'll see that both include a signature (from the node which created it). You may wonder why transactions don't have signatures... The answer is that they do! They're just hidden inside the ``fulfillment`` string of each fulfillment. A creation transaction is signed by whoever created it. A transfer transaction is signed by whoever currently controls or owns it.

View File

@ -301,20 +301,18 @@ def test_transaction_serialization(user_ffill, user_cond, data, data_id):
expected = { expected = {
'id': tx_id, 'id': tx_id,
'version': Transaction.VERSION, 'version': Transaction.VERSION,
'transaction': { # NOTE: This test assumes that Fulfillments and Conditions can
# NOTE: This test assumes that Fulfillments and Conditions can # successfully be serialized
# successfully be serialized 'fulfillments': [user_ffill.to_dict(0)],
'fulfillments': [user_ffill.to_dict(0)], 'conditions': [user_cond.to_dict(0)],
'conditions': [user_cond.to_dict(0)], 'operation': Transaction.CREATE,
'operation': Transaction.CREATE, 'metadata': None,
'metadata': None, 'asset': {
'asset': { 'id': data_id,
'id': data_id, 'divisible': False,
'divisible': False, 'updatable': False,
'updatable': False, 'refillable': False,
'refillable': False, 'data': data,
'data': data,
}
} }
} }
@ -322,7 +320,7 @@ def test_transaction_serialization(user_ffill, user_cond, data, data_id):
[user_cond]) [user_cond])
tx_dict = tx.to_dict() tx_dict = tx.to_dict()
tx_dict['id'] = tx_id tx_dict['id'] = tx_id
tx_dict['transaction']['asset']['id'] = data_id tx_dict['asset']['id'] = data_id
assert tx_dict == expected assert tx_dict == expected
@ -342,20 +340,18 @@ def test_transaction_deserialization(user_ffill, user_cond, data, uuid4):
tx = { tx = {
'version': Transaction.VERSION, 'version': Transaction.VERSION,
'transaction': { # NOTE: This test assumes that Fulfillments and Conditions can
# NOTE: This test assumes that Fulfillments and Conditions can # successfully be serialized
# successfully be serialized 'fulfillments': [user_ffill.to_dict()],
'fulfillments': [user_ffill.to_dict()], 'conditions': [user_cond.to_dict()],
'conditions': [user_cond.to_dict()], 'operation': Transaction.CREATE,
'operation': Transaction.CREATE, 'metadata': None,
'metadata': None, 'asset': {
'asset': { 'id': uuid4,
'id': uuid4, 'divisible': False,
'divisible': False, 'updatable': False,
'updatable': False, 'refillable': False,
'refillable': False, 'data': data,
'data': data,
}
} }
} }
tx_no_signatures = Transaction._remove_signatures(tx) tx_no_signatures = Transaction._remove_signatures(tx)
@ -732,35 +728,33 @@ def test_create_create_transaction_single_io(user_cond, user_pub, data, uuid4):
from .util import validate_transaction_model from .util import validate_transaction_model
expected = { expected = {
'transaction': { 'conditions': [user_cond.to_dict(0)],
'conditions': [user_cond.to_dict(0)], 'metadata': data,
'metadata': data, 'asset': {
'asset': { 'id': uuid4,
'id': uuid4, 'divisible': False,
'divisible': False, 'updatable': False,
'updatable': False, 'refillable': False,
'refillable': False, 'data': data,
'data': data,
},
'fulfillments': [
{
'owners_before': [
user_pub
],
'fid': 0,
'fulfillment': None,
'input': None
}
],
'operation': 'CREATE',
}, },
'fulfillments': [
{
'owners_before': [
user_pub
],
'fid': 0,
'fulfillment': None,
'input': None
}
],
'operation': 'CREATE',
'version': 1, 'version': 1,
} }
asset = Asset(data, uuid4) asset = Asset(data, uuid4)
tx = Transaction.create([user_pub], [([user_pub], 1)], data, asset) tx = Transaction.create([user_pub], [([user_pub], 1)], data, asset)
tx_dict = tx.to_dict() tx_dict = tx.to_dict()
tx_dict['transaction']['fulfillments'][0]['fulfillment'] = None tx_dict['fulfillments'][0]['fulfillment'] = None
tx_dict.pop('id') tx_dict.pop('id')
assert tx_dict == expected assert tx_dict == expected
@ -786,14 +780,12 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub,
ffill = Fulfillment.generate([user_pub, user2_pub]).to_dict() ffill = Fulfillment.generate([user_pub, user2_pub]).to_dict()
ffill.update({'fid': 0}) ffill.update({'fid': 0})
expected = { expected = {
'transaction': { 'conditions': [user_cond.to_dict(0), user2_cond.to_dict(1)],
'conditions': [user_cond.to_dict(0), user2_cond.to_dict(1)], 'metadata': {
'metadata': { 'message': 'hello'
'message': 'hello'
},
'fulfillments': [ffill],
'operation': 'CREATE',
}, },
'fulfillments': [ffill],
'operation': 'CREATE',
'version': 1 'version': 1
} }
asset = Asset(divisible=True) asset = Asset(divisible=True)
@ -802,7 +794,7 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub,
asset=asset, asset=asset,
metadata={'message': 'hello'}).to_dict() metadata={'message': 'hello'}).to_dict()
tx.pop('id') tx.pop('id')
tx['transaction'].pop('asset') tx.pop('asset')
assert tx == expected assert tx == expected
@ -829,28 +821,26 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
from bigchaindb.common.transaction import Transaction, Asset from bigchaindb.common.transaction import Transaction, Asset
expected = { expected = {
'transaction': { 'conditions': [user_user2_threshold_cond.to_dict(0)],
'conditions': [user_user2_threshold_cond.to_dict(0)], 'metadata': data,
'metadata': data, 'asset': {
'asset': { 'id': uuid4,
'id': uuid4, 'divisible': False,
'divisible': False, 'updatable': False,
'updatable': False, 'refillable': False,
'refillable': False, 'data': data,
'data': data,
},
'fulfillments': [
{
'owners_before': [
user_pub,
],
'fid': 0,
'fulfillment': None,
'input': None
},
],
'operation': 'CREATE',
}, },
'fulfillments': [
{
'owners_before': [
user_pub,
],
'fid': 0,
'fulfillment': None,
'input': None
},
],
'operation': 'CREATE',
'version': 1 'version': 1
} }
asset = Asset(data, uuid4) asset = Asset(data, uuid4)
@ -858,7 +848,7 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
data, asset) data, asset)
tx_dict = tx.to_dict() tx_dict = tx.to_dict()
tx_dict.pop('id') tx_dict.pop('id')
tx_dict['transaction']['fulfillments'][0]['fulfillment'] = None tx_dict['fulfillments'][0]['fulfillment'] = None
assert tx_dict == expected assert tx_dict == expected
@ -912,27 +902,25 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
from .util import validate_transaction_model from .util import validate_transaction_model
expected = { expected = {
'transaction': { 'conditions': [user2_cond.to_dict(0)],
'conditions': [user2_cond.to_dict(0)], 'metadata': None,
'metadata': None, 'asset': {
'asset': { 'id': uuid4,
'id': uuid4,
},
'fulfillments': [
{
'owners_before': [
user_pub
],
'fid': 0,
'fulfillment': None,
'input': {
'txid': tx.id,
'cid': 0
}
}
],
'operation': 'TRANSFER',
}, },
'fulfillments': [
{
'owners_before': [
user_pub
],
'fid': 0,
'fulfillment': None,
'input': {
'txid': tx.id,
'cid': 0
}
}
],
'operation': 'TRANSFER',
'version': 1 'version': 1
} }
inputs = tx.to_inputs([0]) inputs = tx.to_inputs([0])
@ -940,14 +928,13 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
transfer_tx = Transaction.transfer(inputs, [([user2_pub], 1)], asset=asset) transfer_tx = Transaction.transfer(inputs, [([user2_pub], 1)], asset=asset)
transfer_tx = transfer_tx.sign([user_priv]) transfer_tx = transfer_tx.sign([user_priv])
transfer_tx = transfer_tx.to_dict() transfer_tx = transfer_tx.to_dict()
transfer_tx_body = transfer_tx['transaction']
expected_input = deepcopy(inputs[0]) expected_input = deepcopy(inputs[0])
expected['id'] = transfer_tx['id'] expected['id'] = transfer_tx['id']
expected_input.fulfillment.sign(serialize(expected).encode(), expected_input.fulfillment.sign(serialize(expected).encode(),
PrivateKey(user_priv)) PrivateKey(user_priv))
expected_ffill = expected_input.fulfillment.serialize_uri() expected_ffill = expected_input.fulfillment.serialize_uri()
transfer_ffill = transfer_tx_body['fulfillments'][0]['fulfillment'] transfer_ffill = transfer_tx['fulfillments'][0]['fulfillment']
assert transfer_ffill == expected_ffill assert transfer_ffill == expected_ffill
@ -968,34 +955,32 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
tx = tx.sign([user_priv]) tx = tx.sign([user_priv])
expected = { expected = {
'transaction': { 'conditions': [user2_cond.to_dict(0), user2_cond.to_dict(1)],
'conditions': [user2_cond.to_dict(0), user2_cond.to_dict(1)], 'metadata': None,
'metadata': None, 'fulfillments': [
'fulfillments': [ {
{ 'owners_before': [
'owners_before': [ user_pub
user_pub ],
], 'fid': 0,
'fid': 0, 'fulfillment': None,
'fulfillment': None, 'input': {
'input': { 'txid': tx.id,
'txid': tx.id, 'cid': 0
'cid': 0
}
}, {
'owners_before': [
user2_pub
],
'fid': 1,
'fulfillment': None,
'input': {
'txid': tx.id,
'cid': 1
}
} }
], }, {
'operation': 'TRANSFER', 'owners_before': [
}, user2_pub
],
'fid': 1,
'fulfillment': None,
'input': {
'txid': tx.id,
'cid': 1
}
}
],
'operation': 'TRANSFER',
'version': 1 'version': 1
} }
@ -1010,10 +995,10 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
assert transfer_tx.fulfillments_valid(tx.conditions) is True assert transfer_tx.fulfillments_valid(tx.conditions) is True
transfer_tx = transfer_tx.to_dict() transfer_tx = transfer_tx.to_dict()
transfer_tx['transaction']['fulfillments'][0]['fulfillment'] = None transfer_tx['fulfillments'][0]['fulfillment'] = None
transfer_tx['transaction']['fulfillments'][1]['fulfillment'] = None transfer_tx['fulfillments'][1]['fulfillment'] = None
transfer_tx.pop('asset')
transfer_tx.pop('id') transfer_tx.pop('id')
transfer_tx['transaction'].pop('asset')
assert expected == transfer_tx assert expected == transfer_tx

View File

@ -273,8 +273,8 @@ class TestBigchainApi(object):
block = b.backend.get_genesis_block() block = b.backend.get_genesis_block()
assert len(block['block']['transactions']) == 1 assert len(block['block']['transactions']) == 1
assert block['block']['transactions'][0]['transaction']['operation'] == 'GENESIS' assert block['block']['transactions'][0]['operation'] == 'GENESIS'
assert block['block']['transactions'][0]['transaction']['fulfillments'][0]['input'] is None assert block['block']['transactions'][0]['fulfillments'][0]['input'] is None
def test_create_genesis_block_fails_if_table_not_empty(self, b): def test_create_genesis_block_fails_if_table_not_empty(self, b):
from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError

View File

@ -33,8 +33,8 @@ def test_post_create_transaction_endpoint(b, client):
tx = tx.sign([user_priv]) tx = tx.sign([user_priv])
res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict())) res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict()))
assert res.json['transaction']['fulfillments'][0]['owners_before'][0] == user_pub assert res.json['fulfillments'][0]['owners_before'][0] == user_pub
assert res.json['transaction']['conditions'][0]['owners_after'][0] == user_pub assert res.json['conditions'][0]['owners_after'][0] == user_pub
def test_post_create_transaction_with_invalid_id(b, client): def test_post_create_transaction_with_invalid_id(b, client):
@ -55,7 +55,7 @@ def test_post_create_transaction_with_invalid_signature(b, client):
tx = Transaction.create([user_pub], [([user_pub], 1)]) tx = Transaction.create([user_pub], [([user_pub], 1)])
tx = tx.sign([user_priv]).to_dict() tx = tx.sign([user_priv]).to_dict()
tx['transaction']['fulfillments'][0]['fulfillment'] = 'cf:0:0' tx['fulfillments'][0]['fulfillment'] = 'cf:0:0'
res = client.post(TX_ENDPOINT, data=json.dumps(tx)) res = client.post(TX_ENDPOINT, data=json.dumps(tx))
assert res.status_code == 400 assert res.status_code == 400
@ -81,8 +81,8 @@ def test_post_transfer_transaction_endpoint(b, client, user_pk, user_sk):
res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict())) res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict()))
assert res.json['transaction']['fulfillments'][0]['owners_before'][0] == user_pk assert res.json['fulfillments'][0]['owners_before'][0] == user_pk
assert res.json['transaction']['conditions'][0]['owners_after'][0] == user_pub assert res.json['conditions'][0]['owners_after'][0] == user_pub
@pytest.mark.usefixtures('inputs') @pytest.mark.usefixtures('inputs')