flatten transaction - code changes

This commit is contained in:
Scott Sadler 2016-11-23 10:01:44 +01:00
parent e4026db85f
commit 8d4677f456
10 changed files with 173 additions and 211 deletions

View File

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

View File

@ -1110,7 +1110,7 @@ class Transaction(object):
# NOTE: An `asset` in a `TRANSFER` only contains the asset's id
asset = {'id': self.asset.data_id}
tx_body = {
tx = {
'fulfillments': [fulfillment.to_dict(fid) for fid, fulfillment
in enumerate(self.fulfillments)],
'conditions': [condition.to_dict(cid) for cid, condition
@ -1118,10 +1118,7 @@ class Transaction(object):
'operation': str(self.operation),
'metadata': self.metadata,
'asset': asset,
}
tx = {
'version': self.version,
'transaction': tx_body,
}
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
# transaction's hash
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.
# ThresholdSha256Fulfillment), so setting it to `None` in any
# case could yield incorrect signatures. This is why we only
@ -1196,7 +1193,7 @@ class Transaction(object):
raise InvalidHash()
@classmethod
def from_dict(cls, tx_body):
def from_dict(cls, tx):
"""Transforms a Python dictionary to a Transaction object.
Args:
@ -1205,8 +1202,7 @@ class Transaction(object):
Returns:
:class:`~bigchaindb.common.transaction.Transaction`
"""
cls.validate_structure(tx_body)
tx = tx_body['transaction']
cls.validate_structure(tx)
fulfillments = [Fulfillment.from_dict(fulfillment) for fulfillment
in tx['fulfillments']]
conditions = [Condition.from_dict(condition) for condition
@ -1217,4 +1213,4 @@ class Transaction(object):
asset = AssetLink.from_dict(tx['asset'])
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 = list(cursor)
if cursor:
return Asset.from_dict(cursor[0]['transaction']['asset'])
return Asset.from_dict(cursor[0]['asset'])
def get_spent(self, txid, cid):
"""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.
# 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
for index, cond in enumerate(tx['transaction']['conditions']):
for index, cond in enumerate(tx['conditions']):
# for simple signature conditions there are no subfulfillments
# check if the owner is in the condition `owners_after`
if len(cond['owners_after']) == 1:

View File

@ -159,7 +159,7 @@ class RethinkDBBackend:
r.table('bigchain', read_mode=self.read_mode)
.get_all(asset_id, index='asset_id')
.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'))
def get_asset_by_id(self, asset_id):
@ -176,10 +176,9 @@ class RethinkDBBackend:
.get_all(asset_id, index='asset_id')
.concat_map(lambda block: block['block']['transactions'])
.filter(lambda transaction:
transaction['transaction']['asset']['id'] == asset_id)
transaction['asset']['id'] == asset_id)
.filter(lambda transaction:
transaction['transaction']['operation'] == 'CREATE')
.pluck({'transaction': 'asset'}))
transaction['operation'] == 'CREATE'))
def get_spent(self, transaction_id, condition_id):
"""Check if a `txid` was already used as an input.
@ -199,7 +198,7 @@ class RethinkDBBackend:
return self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.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})))
def get_owned_ids(self, owner):
@ -216,7 +215,7 @@ class RethinkDBBackend:
return self.connection.run(
r.table('bigchain', read_mode=self.read_mode)
.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))))
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
r.db(dbname).table('bigchain')\
.index_create('asset_id',
r.row['block']['transactions']['transaction']['asset']['id'], multi=True)\
r.row['block']['transactions']['asset']['id'], multi=True)\
.run(conn)
# wait for rethinkdb to finish creating secondary indexes

View File

@ -156,4 +156,4 @@ def is_genesis_block(block):
try:
return block.transactions[0].operation == 'GENESIS'
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 Body`_
* Condition_
* Fulfillment_
@ -58,11 +56,6 @@ Transaction Schema
Transaction
-----------
%(wrapper)s
Transaction Body
----------------
%(transaction)s
Condition
@ -158,9 +151,7 @@ def main():
""" Main function """
defs = TX_SCHEMA['definitions']
doc = TPL_DOC % {
'wrapper': render_section('Transaction', TX_SCHEMA),
'transaction': render_section('Transaction',
TX_SCHEMA['properties']['transaction']),
'transaction': render_section('Transaction', TX_SCHEMA),
'condition': render_section('Condition', defs['condition']),
'fulfillment': render_section('Fulfillment', defs['fulfillment']),
'asset': render_section('Asset', defs['asset']),

View File

@ -301,20 +301,18 @@ def test_transaction_serialization(user_ffill, user_cond, data, data_id):
expected = {
'id': tx_id,
'version': Transaction.VERSION,
'transaction': {
# NOTE: This test assumes that Fulfillments and Conditions can
# successfully be serialized
'fulfillments': [user_ffill.to_dict(0)],
'conditions': [user_cond.to_dict(0)],
'operation': Transaction.CREATE,
'metadata': None,
'asset': {
'id': data_id,
'divisible': False,
'updatable': False,
'refillable': False,
'data': data,
}
# NOTE: This test assumes that Fulfillments and Conditions can
# successfully be serialized
'fulfillments': [user_ffill.to_dict(0)],
'conditions': [user_cond.to_dict(0)],
'operation': Transaction.CREATE,
'metadata': None,
'asset': {
'id': data_id,
'divisible': False,
'updatable': False,
'refillable': False,
'data': data,
}
}
@ -322,7 +320,7 @@ def test_transaction_serialization(user_ffill, user_cond, data, data_id):
[user_cond])
tx_dict = tx.to_dict()
tx_dict['id'] = tx_id
tx_dict['transaction']['asset']['id'] = data_id
tx_dict['asset']['id'] = data_id
assert tx_dict == expected
@ -342,20 +340,18 @@ def test_transaction_deserialization(user_ffill, user_cond, data, uuid4):
tx = {
'version': Transaction.VERSION,
'transaction': {
# NOTE: This test assumes that Fulfillments and Conditions can
# successfully be serialized
'fulfillments': [user_ffill.to_dict()],
'conditions': [user_cond.to_dict()],
'operation': Transaction.CREATE,
'metadata': None,
'asset': {
'id': uuid4,
'divisible': False,
'updatable': False,
'refillable': False,
'data': data,
}
# NOTE: This test assumes that Fulfillments and Conditions can
# successfully be serialized
'fulfillments': [user_ffill.to_dict()],
'conditions': [user_cond.to_dict()],
'operation': Transaction.CREATE,
'metadata': None,
'asset': {
'id': uuid4,
'divisible': False,
'updatable': False,
'refillable': False,
'data': data,
}
}
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
expected = {
'transaction': {
'conditions': [user_cond.to_dict(0)],
'metadata': data,
'asset': {
'id': uuid4,
'divisible': False,
'updatable': False,
'refillable': False,
'data': data,
},
'fulfillments': [
{
'owners_before': [
user_pub
],
'fid': 0,
'fulfillment': None,
'input': None
}
],
'operation': 'CREATE',
'conditions': [user_cond.to_dict(0)],
'metadata': data,
'asset': {
'id': uuid4,
'divisible': False,
'updatable': False,
'refillable': False,
'data': data,
},
'fulfillments': [
{
'owners_before': [
user_pub
],
'fid': 0,
'fulfillment': None,
'input': None
}
],
'operation': 'CREATE',
'version': 1,
}
asset = Asset(data, uuid4)
tx = Transaction.create([user_pub], [([user_pub], 1)], data, asset)
tx_dict = tx.to_dict()
tx_dict['transaction']['fulfillments'][0]['fulfillment'] = None
tx_dict['fulfillments'][0]['fulfillment'] = None
tx_dict.pop('id')
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.update({'fid': 0})
expected = {
'transaction': {
'conditions': [user_cond.to_dict(0), user2_cond.to_dict(1)],
'metadata': {
'message': 'hello'
},
'fulfillments': [ffill],
'operation': 'CREATE',
'conditions': [user_cond.to_dict(0), user2_cond.to_dict(1)],
'metadata': {
'message': 'hello'
},
'fulfillments': [ffill],
'operation': 'CREATE',
'version': 1
}
asset = Asset(divisible=True)
@ -802,7 +794,7 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub,
asset=asset,
metadata={'message': 'hello'}).to_dict()
tx.pop('id')
tx['transaction'].pop('asset')
tx.pop('asset')
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
expected = {
'transaction': {
'conditions': [user_user2_threshold_cond.to_dict(0)],
'metadata': data,
'asset': {
'id': uuid4,
'divisible': False,
'updatable': False,
'refillable': False,
'data': data,
},
'fulfillments': [
{
'owners_before': [
user_pub,
],
'fid': 0,
'fulfillment': None,
'input': None
},
],
'operation': 'CREATE',
'conditions': [user_user2_threshold_cond.to_dict(0)],
'metadata': data,
'asset': {
'id': uuid4,
'divisible': False,
'updatable': False,
'refillable': False,
'data': data,
},
'fulfillments': [
{
'owners_before': [
user_pub,
],
'fid': 0,
'fulfillment': None,
'input': None
},
],
'operation': 'CREATE',
'version': 1
}
asset = Asset(data, uuid4)
@ -858,7 +848,7 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
data, asset)
tx_dict = tx.to_dict()
tx_dict.pop('id')
tx_dict['transaction']['fulfillments'][0]['fulfillment'] = None
tx_dict['fulfillments'][0]['fulfillment'] = None
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
expected = {
'transaction': {
'conditions': [user2_cond.to_dict(0)],
'metadata': None,
'asset': {
'id': uuid4,
},
'fulfillments': [
{
'owners_before': [
user_pub
],
'fid': 0,
'fulfillment': None,
'input': {
'txid': tx.id,
'cid': 0
}
}
],
'operation': 'TRANSFER',
'conditions': [user2_cond.to_dict(0)],
'metadata': None,
'asset': {
'id': uuid4,
},
'fulfillments': [
{
'owners_before': [
user_pub
],
'fid': 0,
'fulfillment': None,
'input': {
'txid': tx.id,
'cid': 0
}
}
],
'operation': 'TRANSFER',
'version': 1
}
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 = transfer_tx.sign([user_priv])
transfer_tx = transfer_tx.to_dict()
transfer_tx_body = transfer_tx['transaction']
expected_input = deepcopy(inputs[0])
expected['id'] = transfer_tx['id']
expected_input.fulfillment.sign(serialize(expected).encode(),
PrivateKey(user_priv))
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
@ -968,34 +955,32 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
tx = tx.sign([user_priv])
expected = {
'transaction': {
'conditions': [user2_cond.to_dict(0), user2_cond.to_dict(1)],
'metadata': None,
'fulfillments': [
{
'owners_before': [
user_pub
],
'fid': 0,
'fulfillment': None,
'input': {
'txid': tx.id,
'cid': 0
}
}, {
'owners_before': [
user2_pub
],
'fid': 1,
'fulfillment': None,
'input': {
'txid': tx.id,
'cid': 1
}
'conditions': [user2_cond.to_dict(0), user2_cond.to_dict(1)],
'metadata': None,
'fulfillments': [
{
'owners_before': [
user_pub
],
'fid': 0,
'fulfillment': None,
'input': {
'txid': tx.id,
'cid': 0
}
],
'operation': 'TRANSFER',
},
}, {
'owners_before': [
user2_pub
],
'fid': 1,
'fulfillment': None,
'input': {
'txid': tx.id,
'cid': 1
}
}
],
'operation': 'TRANSFER',
'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
transfer_tx = transfer_tx.to_dict()
transfer_tx['transaction']['fulfillments'][0]['fulfillment'] = None
transfer_tx['transaction']['fulfillments'][1]['fulfillment'] = None
transfer_tx['fulfillments'][0]['fulfillment'] = None
transfer_tx['fulfillments'][1]['fulfillment'] = None
transfer_tx.pop('asset')
transfer_tx.pop('id')
transfer_tx['transaction'].pop('asset')
assert expected == transfer_tx

View File

@ -273,8 +273,8 @@ class TestBigchainApi(object):
block = b.backend.get_genesis_block()
assert len(block['block']['transactions']) == 1
assert block['block']['transactions'][0]['transaction']['operation'] == 'GENESIS'
assert block['block']['transactions'][0]['transaction']['fulfillments'][0]['input'] is None
assert block['block']['transactions'][0]['operation'] == 'GENESIS'
assert block['block']['transactions'][0]['fulfillments'][0]['input'] is None
def test_create_genesis_block_fails_if_table_not_empty(self, b):
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])
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['transaction']['conditions'][0]['owners_after'][0] == user_pub
assert res.json['fulfillments'][0]['owners_before'][0] == user_pub
assert res.json['conditions'][0]['owners_after'][0] == user_pub
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 = 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))
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()))
assert res.json['transaction']['fulfillments'][0]['owners_before'][0] == user_pk
assert res.json['transaction']['conditions'][0]['owners_after'][0] == user_pub
assert res.json['fulfillments'][0]['owners_before'][0] == user_pk
assert res.json['conditions'][0]['owners_after'][0] == user_pub
@pytest.mark.usefixtures('inputs')