Merge pull request #925 from bigchaindb/inputs-outputs

Inputs & Outputs
This commit is contained in:
libscott 2016-12-22 13:52:19 +01:00 committed by GitHub
commit 5190e0a682
22 changed files with 912 additions and 908 deletions

View File

@ -105,13 +105,13 @@ def _get_asset_create_tx_query(asset_id):
@register_query(RethinkDBConnection) @register_query(RethinkDBConnection)
def get_spent(connection, transaction_id, condition_id): def get_spent(connection, transaction_id, output):
# TODO: use index! # TODO: use index!
return connection.run( return connection.run(
r.table('bigchain', read_mode=READ_MODE) r.table('bigchain', read_mode=READ_MODE)
.concat_map(lambda doc: doc['block']['transactions']) .concat_map(lambda doc: doc['block']['transactions'])
.filter(lambda transaction: transaction['fulfillments'].contains( .filter(lambda transaction: transaction['inputs'].contains(
lambda fulfillment: fulfillment['input'] == {'txid': transaction_id, 'cid': condition_id}))) lambda input: input['fulfills'] == {'txid': transaction_id, 'output': output})))
@register_query(RethinkDBConnection) @register_query(RethinkDBConnection)
@ -120,8 +120,8 @@ def get_owned_ids(connection, owner):
return connection.run( return connection.run(
r.table('bigchain', read_mode=READ_MODE) r.table('bigchain', read_mode=READ_MODE)
.concat_map(lambda doc: doc['block']['transactions']) .concat_map(lambda doc: doc['block']['transactions'])
.filter(lambda tx: tx['conditions'].contains( .filter(lambda tx: tx['outputs'].contains(
lambda c: c['owners_after'].contains(owner)))) lambda c: c['public_keys'].contains(owner))))
@register_query(RethinkDBConnection) @register_query(RethinkDBConnection)

View File

@ -79,7 +79,7 @@ class CyclicBlockchainError(Exception):
class TransactionNotInValidBlock(Exception): class TransactionNotInValidBlock(Exception):
"""Raised when a transfer transaction is attempting to fulfill the """Raised when a transfer transaction is attempting to fulfill the
conditions of a transaction that is in an invalid or undecided block""" outputs of a transaction that is in an invalid or undecided block"""
class AssetIdMismatch(Exception): class AssetIdMismatch(Exception):

View File

@ -8,8 +8,8 @@ description: |
A transaction represents the creation or transfer of assets in BigchainDB. A transaction represents the creation or transfer of assets in BigchainDB.
required: required:
- id - id
- fulfillments - inputs
- conditions - outputs
- operation - operation
- metadata - metadata
- asset - asset
@ -30,23 +30,23 @@ properties:
Description of the asset being transacted. Description of the asset being transacted.
See: `Asset`_. See: `Asset`_.
fulfillments: inputs:
type: array type: array
title: "Fulfillments list" title: "Transaction inputs"
description: | description: |
Array of the fulfillments (inputs) of a transaction. Array of the inputs of a transaction.
See: Fulfillment_. See: Input_.
items: items:
"$ref": "#/definitions/fulfillment" "$ref": "#/definitions/input"
conditions: outputs:
type: array type: array
description: | description: |
Array of conditions (outputs) provided by this transaction. Array of outputs provided by this transaction.
See: Condition_. See: Output_.
items: items:
"$ref": "#/definitions/condition" "$ref": "#/definitions/output"
metadata: metadata:
"$ref": "#/definitions/metadata" "$ref": "#/definitions/metadata"
description: | description: |
@ -67,7 +67,7 @@ definitions:
base58: base58:
pattern: "[1-9a-zA-Z^OIl]{43,44}" pattern: "[1-9a-zA-Z^OIl]{43,44}"
type: string type: string
owners_list: public_keys:
anyOf: anyOf:
- type: array - type: array
items: items:
@ -88,11 +88,10 @@ definitions:
Type of the transaction: Type of the transaction:
A ``CREATE`` transaction creates an asset in BigchainDB. This A ``CREATE`` transaction creates an asset in BigchainDB. This
transaction has outputs (conditions) but no inputs (fulfillments), transaction has outputs but no inputs, so a dummy input is created.
so a dummy fulfillment is used.
A ``TRANSFER`` transaction transfers ownership of an asset, by providing A ``TRANSFER`` transaction transfers ownership of an asset, by providing
fulfillments to conditions of earlier transactions. an input that meets the conditions of an earlier transaction's outputs.
A ``GENESIS`` transaction is a special case transaction used as the A ``GENESIS`` transaction is a special case transaction used as the
sole member of the first block in a BigchainDB ledger. sole member of the first block in a BigchainDB ledger.
@ -120,21 +119,27 @@ definitions:
- type: object - type: object
additionalProperties: true additionalProperties: true
- type: 'null' - type: 'null'
condition: output:
type: object type: object
description: | description: |
An output of a transaction. A condition describes a quantity of an asset A transaction output. Describes the quantity of an asset and the
and what conditions must be met in order for it to be fulfilled. See also: requirements that must be met to spend the output.
fulfillment_.
See also: Input_.
additionalProperties: false additionalProperties: false
required: required:
- owners_after
- condition
- amount - amount
- condition
- public_keys
properties: properties:
amount:
type: integer
description: |
Integral amount of the asset represented by this output.
In the case of a non divisible asset, this will always be 1.
condition: condition:
description: | description: |
Body of the condition. Has the properties: Describes the condition that needs to be met to spend the output. Has the properties:
- **details**: Details of the condition. - **details**: Details of the condition.
- **uri**: Condition encoded as an ASCII string. - **uri**: Condition encoded as an ASCII string.
@ -150,29 +155,26 @@ definitions:
uri: uri:
type: string type: string
pattern: "^cc:([1-9a-f][0-9a-f]{0,3}|0):[1-9a-f][0-9a-f]{0,15}:[a-zA-Z0-9_-]{0,86}:([1-9][0-9]{0,17}|0)$" pattern: "^cc:([1-9a-f][0-9a-f]{0,3}|0):[1-9a-f][0-9a-f]{0,15}:[a-zA-Z0-9_-]{0,86}:([1-9][0-9]{0,17}|0)$"
owners_after: public_keys:
"$ref": "#/definitions/owners_list" "$ref": "#/definitions/public_keys"
description: | description: |
List of public keys associated with asset ownership at the time List of public keys associated with the conditions on an output.
of the transaction.
amount: amount:
type: integer type: integer
description: | description: |
Integral amount of the asset represented by this condition. Integral amount of the asset represented by this condition.
fulfillment: input:
type: "object" type: "object"
description: description:
A fulfillment is an input to a transaction, named as such because it An input spends a previous output, by providing one or more fulfillments
fulfills a condition of a previous transaction. In the case of a that fulfill the conditions of the previous output.
``CREATE`` transaction, a fulfillment may provide no ``input``.
additionalProperties: false additionalProperties: false
required: required:
- owners_before - owners_before
- input
- fulfillment - fulfillment
properties: properties:
owners_before: owners_before:
"$ref": "#/definitions/owners_list" "$ref": "#/definitions/public_keys"
description: | description: |
List of public keys of the previous owners of the asset. List of public keys of the previous owners of the asset.
fulfillment: fulfillment:
@ -193,22 +195,29 @@ definitions:
type_id: type_id:
type: integer type: integer
description: | description: |
Fulfillment of a condition_, or put a different way, this is a Fulfillment of an `Output.condition`_, or, put a different way, a payload
payload that satisfies a condition in order to spend the associated that satisfies the condition of a previous output to prove that the
asset. creator(s) of this transaction have control over the listed asset.
- type: string - type: string
pattern: "^cf:([1-9a-f][0-9a-f]{0,3}|0):[a-zA-Z0-9_-]*$" pattern: "^cf:([1-9a-f][0-9a-f]{0,3}|0):[a-zA-Z0-9_-]*$"
input: fulfills:
anyOf: anyOf:
- type: 'object' - type: 'object'
description: | description: |
Reference to a condition of a previous transaction Reference to the output that is being spent.
additionalProperties: false additionalProperties: false
required:
- output
- txid
properties: properties:
cid: output:
"$ref": "#/definitions/offset" "$ref": "#/definitions/offset"
description: |
Index of the output containing the condition being fulfilled
txid: txid:
"$ref": "#/definitions/sha3_hexdigest" "$ref": "#/definitions/sha3_hexdigest"
description: |
Transaction ID containing the output to spend
- type: 'null' - type: 'null'
metadata: metadata:
anyOf: anyOf:

File diff suppressed because it is too large Load Diff

View File

@ -355,7 +355,7 @@ class Bigchain(object):
if cursor: if cursor:
return cursor[0]['asset'] return cursor[0]['asset']
def get_spent(self, txid, cid): def get_spent(self, txid, output):
"""Check if a `txid` was already used as an input. """Check if a `txid` was already used as an input.
A transaction can be used as an input for another transaction. Bigchain needs to make sure that a A transaction can be used as an input for another transaction. Bigchain needs to make sure that a
@ -363,15 +363,16 @@ class Bigchain(object):
Args: Args:
txid (str): The id of the transaction txid (str): The id of the transaction
cid (num): the index of the condition in the respective transaction output (num): the index of the output in the respective transaction
Returns: Returns:
The transaction (Transaction) that used the `txid` as an input else The transaction (Transaction) that used the `txid` as an input else
`None` `None`
""" """
# checks if an input was already spent # checks if an input was already spent
# checks if the bigchain has any transaction with input {'txid': ..., 'cid': ...} # checks if the bigchain has any transaction with input {'txid': ...,
transactions = list(backend.query.get_spent(self.connection, txid, cid)) # 'output': ...}
transactions = list(backend.query.get_spent(self.connection, txid, output))
# a transaction_id should have been spent at most one time # a transaction_id should have been spent at most one time
if transactions: if transactions:
@ -403,7 +404,7 @@ class Bigchain(object):
owner (str): base58 encoded public key. owner (str): base58 encoded public key.
Returns: Returns:
:obj:`list` of TransactionLink: list of ``txid`` s and ``cid`` s :obj:`list` of TransactionLink: list of ``txid`` s and ``output`` s
pointing to another transaction's condition pointing to another transaction's condition
""" """
@ -420,22 +421,22 @@ class Bigchain(object):
# NOTE: It's OK to not serialize the transaction here, as we do not # NOTE: It's OK to not serialize the transaction here, as we do not
# 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 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['conditions']): for index, output in enumerate(tx['outputs']):
# 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(output['public_keys']) == 1:
if cond['condition']['details']['public_key'] == owner: if output['condition']['details']['public_key'] == owner:
tx_link = TransactionLink(tx['id'], index) tx_link = TransactionLink(tx['id'], index)
else: else:
# for transactions with multiple `owners_after` there will be several subfulfillments nested # for transactions with multiple `public_keys` there will be several subfulfillments nested
# in the condition. We need to iterate the subfulfillments to make sure there is a # in the condition. We need to iterate the subfulfillments to make sure there is a
# subfulfillment for `owner` # subfulfillment for `owner`
if util.condition_details_has_owner(cond['condition']['details'], owner): if util.condition_details_has_owner(output['condition']['details'], owner):
tx_link = TransactionLink(tx['id'], index) tx_link = TransactionLink(tx['id'], index)
# check if input was already spent # check if input was already spent
if not self.get_spent(tx_link.txid, tx_link.cid): if not self.get_spent(tx_link.txid, tx_link.output):
owned.append(tx_link) owned.append(tx_link)
return owned return owned

View File

@ -33,14 +33,14 @@ class Transaction(Transaction):
InvalidHash: if the hash of the transaction is wrong InvalidHash: if the hash of the transaction is wrong
InvalidSignature: if the signature of the transaction is wrong InvalidSignature: if the signature of the transaction is wrong
""" """
if len(self.fulfillments) == 0: if len(self.inputs) == 0:
raise ValueError('Transaction contains no fulfillments') raise ValueError('Transaction contains no inputs')
input_conditions = [] input_conditions = []
inputs_defined = all([ffill.tx_input for ffill in self.fulfillments]) inputs_defined = all([input_.fulfills for input_ in self.inputs])
# validate amounts # validate amounts
if any(condition.amount < 1 for condition in self.conditions): if any(output.amount < 1 for output in self.outputs):
raise AmountError('`amount` needs to be greater than zero') raise AmountError('`amount` needs to be greater than zero')
if self.operation in (Transaction.CREATE, Transaction.GENESIS): if self.operation in (Transaction.CREATE, Transaction.GENESIS):
@ -63,9 +63,8 @@ class Transaction(Transaction):
# store the inputs so that we can check if the asset ids match # store the inputs so that we can check if the asset ids match
input_txs = [] input_txs = []
for ffill in self.fulfillments: for input_ in self.inputs:
input_txid = ffill.tx_input.txid input_txid = input_.fulfills.txid
input_cid = ffill.tx_input.cid
input_tx, status = bigchain.\ input_tx, status = bigchain.\
get_transaction(input_txid, include_status=True) get_transaction(input_txid, include_status=True)
@ -78,15 +77,16 @@ class Transaction(Transaction):
'input `{}` does not exist in a valid block'.format( 'input `{}` does not exist in a valid block'.format(
input_txid)) input_txid))
spent = bigchain.get_spent(input_txid, ffill.tx_input.cid) spent = bigchain.get_spent(input_txid, input_.fulfills.output)
if spent and spent.id != self.id: if spent and spent.id != self.id:
raise DoubleSpend('input `{}` was already spent' raise DoubleSpend('input `{}` was already spent'
.format(input_txid)) .format(input_txid))
input_condition = input_tx.conditions[input_cid] output = input_tx.outputs[input_.fulfills.output]
input_conditions.append(input_condition) input_conditions.append(output)
input_txs.append(input_tx) input_txs.append(input_tx)
if output.amount < 1:
raise AmountError('`amount` needs to be greater than zero')
# validate asset id # validate asset id
asset_id = Transaction.get_asset_id(input_txs) asset_id = Transaction.get_asset_id(input_txs)
@ -95,8 +95,13 @@ class Transaction(Transaction):
' match the asset id of the' ' match the asset id of the'
' transaction')) ' transaction'))
# validate the amounts
for output in self.outputs:
if output.amount < 1:
raise AmountError('`amount` needs to be greater than zero')
input_amount = sum([input_condition.amount for input_condition in input_conditions]) input_amount = sum([input_condition.amount for input_condition in input_conditions])
output_amount = sum([output_condition.amount for output_condition in self.conditions]) output_amount = sum([output_condition.amount for output_condition in self.outputs])
if output_amount != input_amount: if output_amount != input_amount:
raise AmountError(('The amount used in the inputs `{}`' raise AmountError(('The amount used in the inputs `{}`'
@ -109,7 +114,7 @@ class Transaction(Transaction):
raise TypeError('`operation`: `{}` must be either {}.' raise TypeError('`operation`: `{}` must be either {}.'
.format(self.operation, allowed_operations)) .format(self.operation, allowed_operations))
if not self.fulfillments_valid(input_conditions): if not self.inputs_valid(input_conditions):
raise InvalidSignature() raise InvalidSignature()
return self return self

View File

@ -11,7 +11,7 @@ BigchainDB achieves strong tamper-resistance in the following ways:
1. **Replication.** All data is sharded and shards are replicated in several (different) places. The replication factor can be set by the federation. The higher the replication factor, the more difficult it becomes to change or delete all replicas. 1. **Replication.** All data is sharded and shards are replicated in several (different) places. The replication factor can be set by the federation. The higher the replication factor, the more difficult it becomes to change or delete all replicas.
2. **Internal watchdogs.** All nodes monitor all changes and if some unallowed change happens, then appropriate action is taken. For example, if a valid block is deleted, then it is put back. 2. **Internal watchdogs.** All nodes monitor all changes and if some unallowed change happens, then appropriate action is taken. For example, if a valid block is deleted, then it is put back.
3. **External watchdogs.** Federations may opt to have trusted third-parties to monitor and audit their data, looking for irregularities. For federations with publicly-readable data, the public can act as an auditor. 3. **External watchdogs.** Federations may opt to have trusted third-parties to monitor and audit their data, looking for irregularities. For federations with publicly-readable data, the public can act as an auditor.
4. **Cryptographic signatures** are used throughout BigchainDB as a way to check if messages (transactions, blocks and votes) have been tampered with enroute, and as a way to verify who signed the messages. Each block is signed by the node that created it. Each vote is signed by the node that cast it. A creation transaction is signed by the node that created it, although there are plans to improve that by adding signatures from the sending client and multiple nodes; see [Issue #347](https://github.com/bigchaindb/bigchaindb/issues/347). Transfer transactions can contain multiple fulfillments (one per asset transferred). Each fulfillment will typically contain one or more signatures from the owners (i.e. the owners before the transfer). Hashlock fulfillments are an exception; theres an open issue ([#339](https://github.com/bigchaindb/bigchaindb/issues/339)) to address that. 4. **Cryptographic signatures** are used throughout BigchainDB as a way to check if messages (transactions, blocks and votes) have been tampered with enroute, and as a way to verify who signed the messages. Each block is signed by the node that created it. Each vote is signed by the node that cast it. A creation transaction is signed by the node that created it, although there are plans to improve that by adding signatures from the sending client and multiple nodes; see [Issue #347](https://github.com/bigchaindb/bigchaindb/issues/347). Transfer transactions can contain multiple inputs (fulfillments, one per asset transferred). Each fulfillment will typically contain one or more signatures from the owners (i.e. the owners before the transfer). Hashlock fulfillments are an exception; theres an open issue ([#339](https://github.com/bigchaindb/bigchaindb/issues/339)) to address that.
5. **Full or partial backups** of the database may be recorded from time to time, possibly on magnetic tape storage, other blockchains, printouts, etc. 5. **Full or partial backups** of the database may be recorded from time to time, possibly on magnetic tape storage, other blockchains, printouts, etc.
6. **Strong security.** Node owners can adopt and enforce strong security policies. 6. **Strong security.** Node owners can adopt and enforce strong security policies.
7. **Node diversity.** Diversity makes it so that no one thing (e.g. natural disaster or operating system bug) can compromise enough of the nodes. See [the section on the kinds of node diversity](diversity.html). 7. **Node diversity.** Diversity makes it so that no one thing (e.g. natural disaster or operating system bug) can compromise enough of the nodes. See [the section on the kinds of node diversity](diversity.html).

View File

@ -18,29 +18,31 @@ That means you can create/register an asset with an initial quantity,
e.g. 700 oak trees. Divisible assets can be split apart or recombined e.g. 700 oak trees. Divisible assets can be split apart or recombined
by transfer transactions (described more below). by transfer transactions (described more below).
A CREATE transaction also establishes the conditions that must be met to A CREATE transaction also establishes, in its outputs, the conditions that must
transfer the asset(s). For example, there may be a condition that any transfer be met to transfer the asset(s). The conditions may also be associated with a
must be signed (cryptographically) by the private key associated with a list of public keys that, depending on the condition, may have full or partial
given public key. More sophisticated conditions are possible. control over the asset(s). For example, there may be a condition that any
BigchainDB's conditions are based on the crypto-conditions of the [Interledger transfer must be signed (cryptographically) by the private key associated with a
Protocol (ILP)](https://interledger.org/). given public key. More sophisticated conditions are possible. BigchainDB's
conditions are based on the crypto-conditions of the [Interledger Protocol
(ILP)](https://interledger.org/).
## TRANSFER Transactions ## TRANSFER Transactions
A TRANSFER transaction can transfer an asset A TRANSFER transaction can transfer an asset
by fulfilling the current conditions on the asset. by providing inputs which fulfill the current output conditions on the asset.
It must also specify new transfer conditions. It must also specify new transfer conditions.
**Example 1:** Suppose a red car is owned and controlled by Joe. **Example 1:** Suppose a red car is owned and controlled by Joe.
Suppose the current transfer condition on the car says Suppose the current transfer condition on the car says
that any valid transfer must be signed by Joe. that any valid transfer must be signed by Joe.
Joe and a buyer named Rae could build a TRANSFER transaction containing Joe and a buyer named Rae could build a TRANSFER transaction containing
Joe's signature (to fulfill the current transfer condition) an input with Joe's signature (to fulfill the current output condition)
plus a new transfer condition saying that any valid transfer plus a new output condition saying that any valid transfer
must be signed by Rae. must be signed by Rae.
**Example 2:** Someone might construct a TRANSFER transaction **Example 2:** Someone might construct a TRANSFER transaction
that fulfills the transfer conditions on four that fulfills the output conditions on four
previously-untransferred assets of the same asset type previously-untransferred assets of the same asset type
e.g. paperclips. The amounts might be 20, 10, 45 and 25, say, e.g. paperclips. The amounts might be 20, 10, 45 and 25, say,
for a total of 100 paperclips. for a total of 100 paperclips.

View File

@ -57,9 +57,9 @@ Transaction Schema
* `Transaction`_ * `Transaction`_
* Condition_ * Input_
* Fulfillment_ * Output_
* Asset_ * Asset_
@ -71,15 +71,15 @@ Transaction
%(transaction)s %(transaction)s
Condition Input
---------- -----
%(condition)s %(input)s
Fulfillment Output
----------- ------
%(fulfillment)s %(output)s
Asset Asset
----- -----
@ -99,8 +99,8 @@ def generate_transaction_docs():
doc = TPL_TRANSACTION % { doc = TPL_TRANSACTION % {
'transaction': render_section('Transaction', schema), 'transaction': render_section('Transaction', schema),
'condition': render_section('Condition', defs['condition']), 'output': render_section('Output', defs['output']),
'fulfillment': render_section('Fulfillment', defs['fulfillment']), 'input': render_section('Input', defs['input']),
'asset': render_section('Asset', defs['asset']), 'asset': render_section('Asset', defs['asset']),
'metadata': render_section('Metadata', defs['metadata']['anyOf'][0]), 'metadata': render_section('Metadata', defs['metadata']['anyOf'][0]),
'container': 'transaction-schema', 'container': 'transaction-schema',

View File

@ -1,126 +0,0 @@
# Crypto-Conditions and Fulfillments
To create a transaction that transfers an asset to new owners, one must fulfill the assets current conditions (crypto-conditions). The most basic kinds of conditions are:
* **A hashlock condition:** One can fulfill a hashlock condition by providing the correct “preimage” (similar to a password or secret phrase)
* **A simple signature condition:** One can fulfill a simple signature condition by a providing a valid cryptographic signature (i.e. corresponding to the public key of an owner, usually)
* **A timeout condition:** Anyone can fulfill a timeout condition before the conditions expiry time. After the expiry time, nobody can fulfill the condition. Another way to say this is that a timeout conditions fulfillment is valid (TRUE) before the expiry time and invalid (FALSE) after the expiry time. Note: at the time of writing, timeout conditions are BigchainDB-specific (i.e. not part of the Interledger specs).
A more complex condition can be composed by using n of the above conditions as inputs to an m-of-n threshold condition (a logic gate which outputs TRUE iff m or more inputs are TRUE). If there are n inputs to a threshold condition:
* 1-of-n is the same as a logical OR of all the inputs
* n-of-n is the same as a logical AND of all the inputs
For example, one could create a condition requiring that m (of n) owners provide signatures before their asset can be transferred to new owners.
One can also put different weights on the inputs to threshold condition, along with a threshold that the weighted-sum-of-inputs must pass for the output to be TRUE. Weights could be used, for example, to express the number of shares that someone owns in an asset.
The (single) output of a threshold condition can be used as one of the inputs of other threshold conditions. This means that one can combine threshold conditions to build complex logical expressions, e.g. (x OR y) AND (u OR v).
Aside: In BigchainDB, the output of an m-of-n threshold condition can be inverted on the way out, so an output that would have been TRUE would get changed to FALSE (and vice versa). This enables the creation of NOT, NOR and NAND gates. At the time of writing, this “inverted threshold condition” is BigchainDB-specific (i.e. not part of the Interledger specs). It should only be used in combination with a timeout condition.
When one creates a condition, one can calculate its fulfillment length (e.g. 96). The more complex the condition, the larger its fulfillment length will be. A BigchainDB federation can put an upper limit on the allowed fulfillment length, as a way of capping the complexity of conditions (and the computing time required to validate them).
If someone tries to make a condition where the output of a threshold condition feeds into the input of another “earlier” threshold condition (i.e. in a closed logical circuit), then their computer will take forever to calculate the (infinite) “condition URI”, at least in theory. In practice, their computer will run out of memory or their client software will timeout after a while.
Aside: In what follows, the list of `owners_after` (in a condition) is always who owned the asset at the time the transaction completed, but before the next transaction started. The list of `owners_before` (in a fulfillment) is always equal to the list of `owners_after` in that asset's previous transaction.
## (Crypto-) Conditions
### One New Owner
If there is only one _new owner_, the condition will be a simple signature condition (i.e. only one signature is required).
```json
{
"condition": {
"details": {
"bitmask": "<base16 int>",
"public_key": "<new owner public key>",
"signature": null,
"type": "fulfillment",
"type_id": "<base16 int>"
},
"uri": "<string>"
},
"owners_after": ["<new owner public key>"],
"amount": "<int>"
}
```
- **Condition header**:
- `owners_after`: A list containing one item: the public key of the new owner.
- `amount`: The amount of shares for a divisible asset to send to the new owners.
- **Condition body**:
- `bitmask`: A set of bits representing the features required by the condition type.
- `public_key`: The new owner's public key.
- `type_id`: The fulfillment type ID; see the [ILP spec](https://interledger.org/five-bells-condition/spec.html).
- `uri`: Binary representation of the condition using only URL-safe characters.
### Multiple New Owners
If there are multiple _new owners_, they can create a ThresholdCondition requiring a signature from each of them in order
to spend the asset. For example:
```json
{
"condition": {
"details": {
"bitmask": 41,
"subfulfillments": [
{
"bitmask": 32,
"public_key": "<new owner 1 public key>",
"signature": null,
"type": "fulfillment",
"type_id": 4,
"weight": 1
},
{
"bitmask": 32,
"public_key": "<new owner 2 public key>",
"signature": null,
"type": "fulfillment",
"type_id": 4,
"weight": 1
}
],
"threshold": 2,
"type": "fulfillment",
"type_id": 2
},
"uri": "cc:2:29:ytNK3X6-bZsbF-nCGDTuopUIMi1HCyCkyPewm6oLI3o:206"},
"owners_after": [
"owner 1 public key>",
"owner 2 public key>"
]
}
```
- `subfulfillments`: a list of fulfillments
- `weight`: integer weight for each subfulfillment's contribution to the threshold
- `threshold`: threshold to reach for the subfulfillments to reach a valid fulfillment
The `weight`s and `threshold` could be adjusted. For example, if the `threshold` was changed to 1 above, then only one of the new owners would have to provide a signature to spend the asset.
## Fulfillments
### One Current Owner
If there is only one _current owner_, the fulfillment will be a simple signature fulfillment (i.e. containing just one signature).
```json
{
"owners_before": ["<public key of the owner before the transaction happened>"],
"fulfillment": "cf:4:RxFzIE679tFBk8zwEgizhmTuciAylvTUwy6EL6ehddHFJOhK5F4IjwQ1xLu2oQK9iyRCZJdfWAefZVjTt3DeG5j2exqxpGliOPYseNkRAWEakqJ_UrCwgnj92dnFRAEE",
"input": {
"cid": 0,
"txid": "11b3e7d893cc5fdfcf1a1706809c7def290a3b10b0bef6525d10b024649c42d3"
}
}
```
- `owners_before`: A list of public keys of the owners before the transaction; in this case it has just one public key.
- `fulfillment`: A crypto-conditions URI that encodes the cryptographic fulfillments like signatures and others, see [crypto-conditions](https://interledger.org/five-bells-condition/spec.html).
- `input`: Pointer to the asset and condition of a previous transaction
- `cid`: Condition index - the index of the condition in the array of conditions in the previous transaction
- `txid`: Transaction id

View File

@ -3,7 +3,7 @@ Data Models
BigchainDB stores all data in the underlying database as JSON documents (conceptually, at least). There are three main kinds: BigchainDB stores all data in the underlying database as JSON documents (conceptually, at least). There are three main kinds:
1. Transactions, which contain digital assets, conditions, fulfillments and other things 1. Transactions, which contain digital assets, inputs, outputs, and other things
2. Blocks 2. Blocks
3. Votes 3. Votes
@ -14,6 +14,6 @@ This section unpacks each one in turn.
transaction-model transaction-model
asset-model asset-model
crypto-conditions inputs-outputs
block-model block-model
vote-model vote-model

View File

@ -0,0 +1,129 @@
Inputs and Outputs
==================
BigchainDB is modelled around *assets*, and *inputs* and *outputs* are the mechanism by which control of an asset is transferred.
Amounts of an asset are encoded in the outputs of a transaction, and each output may be spent separately. In order to spend an output, the output's ``conditions`` must be met by an ``input`` that provides corresponding ``fulfillments``. Each output may be spent at most once, by a single input. Note that any asset associated with an output holding an amount greater than one is considered a divisible asset that may be split up in future transactions.
.. note::
This document (and various places in the BigchainDB documentation and code) talks about control of an asset in terms of *owners* and *ownership*. The language is chosen to represent the most common use cases, but in some more complex scenarios, it may not be accurate to say that the output is owned by the controllers of those public keysit would only be correct to say that those public keys are associated with the ability to fulfill the output. Also, depending on the use case, the entity controlling an output via a private key may not be the legal owner of the asset in the corresponding legal domain. However, since we aim to use language that is simple to understand and covers the majority of use cases, we talk in terms of *owners* of an output that have the ability to *spend* that output.
In the most basic case, an output may define a **simple signature condition**, which gives control of the output to the entity controlling a corresponding private key.
A more complex condition can be composed by using n of the above conditions as inputs to an m-of-n threshold condition (a logic gate which outputs TRUE if and only if m or more inputs are TRUE). If there are n inputs to a threshold condition:
* 1-of-n is the same as a logical OR of all the inputs
* n-of-n is the same as a logical AND of all the inputs
For example, one could create a condition requiring m (of n) signatures before their asset can be transferred.
One can also put different weights on the inputs to a threshold condition, along with a threshold that the weighted-sum-of-inputs must pass for the output to be TRUE.
The (single) output of a threshold condition can be used as one of the inputs of other threshold conditions. This means that one can combine threshold conditions to build complex logical expressions, e.g. (x OR y) AND (u OR v).
When one creates a condition, one can calculate its fulfillment length (e.g. 96). The more complex the condition, the larger its fulfillment length will be. A BigchainDB federation can put an upper limit on the allowed fulfillment length, as a way of capping the complexity of conditions (and the computing time required to validate them).
If someone tries to make a condition where the output of a threshold condition feeds into the input of another “earlier” threshold condition (i.e. in a closed logical circuit), then their computer will take forever to calculate the (infinite) “condition URI”, at least in theory. In practice, their computer will run out of memory or their client software will timeout after a while.
Outputs
-------
.. note::
In what follows, the list of ``public_keys`` (in a condition) is always the controllers of the asset at the time the transaction completed, but before the next transaction started. The list of ``owners_before`` (in an input) is always equal to the list of ``public_keys`` in that asset's previous transaction.
One New Owner
`````````````
If there is only one *new owner*, the output will contain a simple signature condition (i.e. only one signature is required).
.. code-block:: json
{
"condition": {
"details": {
"bitmask": "<base16 int>",
"public_key": "<new owner public key>",
"signature": null,
"type": "fulfillment",
"type_id": "<base16 int>"
},
"uri": "<string>"
},
"public_keys": ["<new owner public key>"],
"amount": "<int>"
}
See the reference on :ref:`outputs <Output>` for descriptions of the meaning of each field.
Multiple New Owners
```````````````````
If there are multiple *new owners*, they can create a ThresholdCondition requiring a signature from each of them in order
to spend the asset. For example:
.. code-block:: json
{
"condition": {
"details": {
"bitmask": 41,
"subfulfillments": [
{
"bitmask": 32,
"public_key": "<new owner 1 public key>",
"signature": null,
"type": "fulfillment",
"type_id": 4,
"weight": 1
},
{
"bitmask": 32,
"public_key": "<new owner 2 public key>",
"signature": null,
"type": "fulfillment",
"type_id": 4,
"weight": 1
}
],
"threshold": 2,
"type": "fulfillment",
"type_id": 2
},
"uri": "cc:2:29:ytNK3X6-bZsbF-nCGDTuopUIMi1HCyCkyPewm6oLI3o:206"},
"public_keys": [
"<owner 1 public key>",
"<owner 2 public key>"
]
}
- ``subfulfillments``: a list of fulfillments
- ``weight``: integer weight for each subfulfillment's contribution to the threshold
- ``threshold``: threshold to reach for the subfulfillments to reach a valid fulfillment
The ``weight``s and ``threshold`` could be adjusted. For example, if the ``threshold`` was changed to 1 above, then only one of the new owners would have to provide a signature to spend the asset.
Inputs
------
One Current Owner
`````````````````
If there is only one *current owner*, the fulfillment will be a simple signature fulfillment (i.e. containing just one signature).
.. code-block:: json
{
"owners_before": ["<public key of the owner before the transaction happened>"],
"fulfillment": "cf:4:RxFzIE679tFBk8zwEgizhmTuciAylvTUwy6EL6ehddHFJOhK5F4IjwQ1xLu2oQK9iyRCZJdfWAefZVjTt3DeG5j2exqxpGliOPYseNkRAWEakqJ_UrCwgnj92dnFRAEE",
"fulfills": {
"output": 0,
"txid": "11b3e7d893cc5fdfcf1a1706809c7def290a3b10b0bef6525d10b024649c42d3"
}
}
See the reference on :ref:`inputs <Input>` for descriptions of the meaning of each field.

View File

@ -22,8 +22,8 @@ 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>",
"fulfillments": ["<list of fulfillments>"], "inputs": ["<list of inputs>"],
"conditions": ["<list of conditions>"], "outputs": ["<list of outputs>"],
"operation": "<string>", "operation": "<string>",
"asset": "<digital asset description (explained in the next section)>", "asset": "<digital asset description (explained in the next section)>",
"metadata": "<any JSON document>" "metadata": "<any JSON document>"
@ -33,13 +33,13 @@ Here's some explanation of the contents of a :ref:`transaction <transaction>`:
- id: The :ref:`id <transaction.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.
- version: :ref:`Version <transaction.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.
- **fulfillments**: List of fulfillments. Each :ref:`fulfillment <Fulfillment>` contains a pointer to an unspent asset - **inputs**: List of inputs. Each :ref:`input <Input>` contains a pointer to an unspent output
and a *crypto fulfillment* that satisfies a spending condition set on the unspent asset. A *fulfillment* and a *crypto fulfillment* that satisfies the conditions of that output. 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:`./inputs-outputs`.
- **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. - **outputs**: List of outputs. Each :ref:`output <Output>` contains *crypto-conditions* that need to be fulfilled by a transfer transaction in order to transfer ownership to new owners.
See :doc:`./crypto-conditions`. See :doc:`./inputs-outputs`.
- **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.
@ -47,6 +47,6 @@ Here's some explanation of the contents of a :ref:`transaction <transaction>`:
- **metadata**: User-provided transaction :ref:`metadata <metadata>`: Can be any JSON document, or `NULL`. - **metadata**: User-provided transaction :ref:`metadata <metadata>`: Can be any JSON document, or `NULL`.
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 input. A creation transaction is signed by whoever created it. A transfer transaction is signed by whoever currently controls or owns it.
What gets signed? For each fulfillment in the transaction, the "fullfillment message" that gets signed includes the ``operation``, ``data``, ``version``, ``id``, corresponding ``condition``, and the fulfillment itself, except with its fulfillment string set to ``null``. The computed signature goes into creating the ``fulfillment`` string of the fulfillment. What gets signed? For each input in the transaction, the "fullfillment message" that gets signed includes the ``operation``, ``data``, ``version``, ``id``, corresponding ``condition``, and the fulfillment itself, except with its fulfillment string set to ``null``. The computed signature goes into creating the ``fulfillment`` string of the input.

View File

@ -133,14 +133,14 @@ GET /unspents/
.. http:get:: /unspents?owner_after={owner_after} .. http:get:: /unspents?owner_after={owner_after}
Get a list of links to transactions' conditions that have not been used in Get a list of links to transactions' outputs that have not been used in
a previous transaction and could hence be called unspent conditions/outputs a previous transaction and could hence be called unspent outputs
(or simply: unspents). (or simply: unspents).
This endpoint will return a ``HTTP 400 Bad Request`` if the querystring This endpoint will return a ``HTTP 400 Bad Request`` if the querystring
``owner_after`` happens to not be defined in the request. ``owner_after`` happens to not be defined in the request.
Note that if unspents for a certain ``owner_after`` have not been found by Note that if unspents for a certain ``public_key`` have not been found by
the server, this will result in the server returning a 200 OK HTTP status the server, this will result in the server returning a 200 OK HTTP status
code and an empty list in the response's body. code and an empty list in the response's body.
@ -162,8 +162,8 @@ GET /unspents/
Content-Type: application/json Content-Type: application/json
[ [
"../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/0", "../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/outputs/0",
"../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/1" "../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/outputs/1"
] ]
:statuscode 200: A list of outputs were found and returned in the body of the response. :statuscode 200: A list of outputs were found and returned in the body of the response.

View File

@ -188,7 +188,7 @@ def test_create_invalid_divisible_asset(b, user_pk, user_sk):
# Asset amount must be more than 0 # Asset amount must be more than 0
tx = Transaction.create([user_pk], [([user_pk], 1)]) tx = Transaction.create([user_pk], [([user_pk], 1)])
tx.conditions[0].amount = 0 tx.outputs[0].amount = 0
tx.sign([user_sk]) tx.sign([user_sk])
with pytest.raises(AmountError): with pytest.raises(AmountError):

View File

@ -13,9 +13,9 @@ def test_single_in_single_own_single_out_single_own_create(b, user_pk):
tx_signed = tx.sign([b.me_private]) tx_signed = tx.sign([b.me_private])
assert tx_signed.validate(b) == tx_signed assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 1 assert len(tx_signed.outputs) == 1
assert tx_signed.conditions[0].amount == 100 assert tx_signed.outputs[0].amount == 100
assert len(tx_signed.fulfillments) == 1 assert len(tx_signed.inputs) == 1
# CREATE divisible asset # CREATE divisible asset
@ -30,10 +30,10 @@ def test_single_in_single_own_multiple_out_single_own_create(b, user_pk):
tx_signed = tx.sign([b.me_private]) tx_signed = tx.sign([b.me_private])
assert tx_signed.validate(b) == tx_signed assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 2 assert len(tx_signed.outputs) == 2
assert tx_signed.conditions[0].amount == 50 assert tx_signed.outputs[0].amount == 50
assert tx_signed.conditions[1].amount == 50 assert tx_signed.outputs[1].amount == 50
assert len(tx_signed.fulfillments) == 1 assert len(tx_signed.inputs) == 1
# CREATE divisible asset # CREATE divisible asset
@ -48,14 +48,14 @@ def test_single_in_single_own_single_out_multiple_own_create(b, user_pk):
tx_signed = tx.sign([b.me_private]) tx_signed = tx.sign([b.me_private])
assert tx_signed.validate(b) == tx_signed assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 1 assert len(tx_signed.outputs) == 1
assert tx_signed.conditions[0].amount == 100 assert tx_signed.outputs[0].amount == 100
condition = tx_signed.conditions[0].to_dict() output = tx_signed.outputs[0].to_dict()
assert 'subfulfillments' in condition['condition']['details'] assert 'subfulfillments' in output['condition']['details']
assert len(condition['condition']['details']['subfulfillments']) == 2 assert len(output['condition']['details']['subfulfillments']) == 2
assert len(tx_signed.fulfillments) == 1 assert len(tx_signed.inputs) == 1
# CREATE divisible asset # CREATE divisible asset
@ -71,15 +71,15 @@ def test_single_in_single_own_multiple_out_mix_own_create(b, user_pk):
tx_signed = tx.sign([b.me_private]) tx_signed = tx.sign([b.me_private])
assert tx_signed.validate(b) == tx_signed assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 2 assert len(tx_signed.outputs) == 2
assert tx_signed.conditions[0].amount == 50 assert tx_signed.outputs[0].amount == 50
assert tx_signed.conditions[1].amount == 50 assert tx_signed.outputs[1].amount == 50
condition_cid1 = tx_signed.conditions[1].to_dict() output_cid1 = tx_signed.outputs[1].to_dict()
assert 'subfulfillments' in condition_cid1['condition']['details'] assert 'subfulfillments' in output_cid1['condition']['details']
assert len(condition_cid1['condition']['details']['subfulfillments']) == 2 assert len(output_cid1['condition']['details']['subfulfillments']) == 2
assert len(tx_signed.fulfillments) == 1 assert len(tx_signed.inputs) == 1
# CREATE divisible asset # CREATE divisible asset
@ -93,11 +93,11 @@ def test_single_in_multiple_own_single_out_single_own_create(b, user_pk,
tx = Transaction.create([b.me, user_pk], [([user_pk], 100)]) tx = Transaction.create([b.me, user_pk], [([user_pk], 100)])
tx_signed = tx.sign([b.me_private, user_sk]) tx_signed = tx.sign([b.me_private, user_sk])
assert tx_signed.validate(b) == tx_signed assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 1 assert len(tx_signed.outputs) == 1
assert tx_signed.conditions[0].amount == 100 assert tx_signed.outputs[0].amount == 100
assert len(tx_signed.fulfillments) == 1 assert len(tx_signed.inputs) == 1
ffill = tx_signed.fulfillments[0].fulfillment.to_dict() ffill = tx_signed.inputs[0].fulfillment.to_dict()
assert 'subfulfillments' in ffill assert 'subfulfillments' in ffill
assert len(ffill['subfulfillments']) == 2 assert len(ffill['subfulfillments']) == 2
@ -134,9 +134,9 @@ def test_single_in_single_own_single_out_single_own_transfer(b, user_pk,
tx_transfer_signed = tx_transfer.sign([user_sk]) tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b) assert tx_transfer_signed.validate(b)
assert len(tx_transfer_signed.conditions) == 1 assert len(tx_transfer_signed.outputs) == 1
assert tx_transfer_signed.conditions[0].amount == 100 assert tx_transfer_signed.outputs[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 1 assert len(tx_transfer_signed.inputs) == 1
# TRANSFER divisible asset # TRANSFER divisible asset
@ -168,10 +168,10 @@ def test_single_in_single_own_multiple_out_single_own_transfer(b, user_pk,
tx_transfer_signed = tx_transfer.sign([user_sk]) tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 2 assert len(tx_transfer_signed.outputs) == 2
assert tx_transfer_signed.conditions[0].amount == 50 assert tx_transfer_signed.outputs[0].amount == 50
assert tx_transfer_signed.conditions[1].amount == 50 assert tx_transfer_signed.outputs[1].amount == 50
assert len(tx_transfer_signed.fulfillments) == 1 assert len(tx_transfer_signed.inputs) == 1
# TRANSFER divisible asset # TRANSFER divisible asset
@ -203,14 +203,14 @@ def test_single_in_single_own_single_out_multiple_own_transfer(b, user_pk,
tx_transfer_signed = tx_transfer.sign([user_sk]) tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 1 assert len(tx_transfer_signed.outputs) == 1
assert tx_transfer_signed.conditions[0].amount == 100 assert tx_transfer_signed.outputs[0].amount == 100
condition = tx_transfer_signed.conditions[0].to_dict() condition = tx_transfer_signed.outputs[0].to_dict()
assert 'subfulfillments' in condition['condition']['details'] assert 'subfulfillments' in condition['condition']['details']
assert len(condition['condition']['details']['subfulfillments']) == 2 assert len(condition['condition']['details']['subfulfillments']) == 2
assert len(tx_transfer_signed.fulfillments) == 1 assert len(tx_transfer_signed.inputs) == 1
# TRANSFER divisible asset # TRANSFER divisible asset
@ -243,15 +243,15 @@ def test_single_in_single_own_multiple_out_mix_own_transfer(b, user_pk,
tx_transfer_signed = tx_transfer.sign([user_sk]) tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 2 assert len(tx_transfer_signed.outputs) == 2
assert tx_transfer_signed.conditions[0].amount == 50 assert tx_transfer_signed.outputs[0].amount == 50
assert tx_transfer_signed.conditions[1].amount == 50 assert tx_transfer_signed.outputs[1].amount == 50
condition_cid1 = tx_transfer_signed.conditions[1].to_dict() output_cid1 = tx_transfer_signed.outputs[1].to_dict()
assert 'subfulfillments' in condition_cid1['condition']['details'] assert 'subfulfillments' in output_cid1['condition']['details']
assert len(condition_cid1['condition']['details']['subfulfillments']) == 2 assert len(output_cid1['condition']['details']['subfulfillments']) == 2
assert len(tx_transfer_signed.fulfillments) == 1 assert len(tx_transfer_signed.inputs) == 1
# TRANSFER divisible asset # TRANSFER divisible asset
@ -282,11 +282,11 @@ def test_single_in_multiple_own_single_out_single_own_transfer(b, user_pk,
tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 1 assert len(tx_transfer_signed.outputs) == 1
assert tx_transfer_signed.conditions[0].amount == 100 assert tx_transfer_signed.outputs[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 1 assert len(tx_transfer_signed.inputs) == 1
ffill = tx_transfer_signed.fulfillments[0].fulfillment.to_dict() ffill = tx_transfer_signed.inputs[0].fulfillment.to_dict()
assert 'subfulfillments' in ffill assert 'subfulfillments' in ffill
assert len(ffill['subfulfillments']) == 2 assert len(ffill['subfulfillments']) == 2
@ -319,9 +319,9 @@ def test_multiple_in_single_own_single_out_single_own_transfer(b, user_pk,
tx_transfer_signed = tx_transfer.sign([user_sk]) tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b) assert tx_transfer_signed.validate(b)
assert len(tx_transfer_signed.conditions) == 1 assert len(tx_transfer_signed.outputs) == 1
assert tx_transfer_signed.conditions[0].amount == 100 assert tx_transfer_signed.outputs[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 2 assert len(tx_transfer_signed.inputs) == 2
# TRANSFER divisible asset # TRANSFER divisible asset
@ -352,12 +352,12 @@ def test_multiple_in_multiple_own_single_out_single_own_transfer(b, user_pk,
tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk])
assert tx_transfer_signed.validate(b) assert tx_transfer_signed.validate(b)
assert len(tx_transfer_signed.conditions) == 1 assert len(tx_transfer_signed.outputs) == 1
assert tx_transfer_signed.conditions[0].amount == 100 assert tx_transfer_signed.outputs[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 2 assert len(tx_transfer_signed.inputs) == 2
ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict() ffill_fid0 = tx_transfer_signed.inputs[0].fulfillment.to_dict()
ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict() ffill_fid1 = tx_transfer_signed.inputs[1].fulfillment.to_dict()
assert 'subfulfillments' in ffill_fid0 assert 'subfulfillments' in ffill_fid0
assert 'subfulfillments' in ffill_fid1 assert 'subfulfillments' in ffill_fid1
assert len(ffill_fid0['subfulfillments']) == 2 assert len(ffill_fid0['subfulfillments']) == 2
@ -393,12 +393,12 @@ def test_muiltiple_in_mix_own_multiple_out_single_own_transfer(b, user_pk,
tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 1 assert len(tx_transfer_signed.outputs) == 1
assert tx_transfer_signed.conditions[0].amount == 100 assert tx_transfer_signed.outputs[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 2 assert len(tx_transfer_signed.inputs) == 2
ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict() ffill_fid0 = tx_transfer_signed.inputs[0].fulfillment.to_dict()
ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict() ffill_fid1 = tx_transfer_signed.inputs[1].fulfillment.to_dict()
assert 'subfulfillments' not in ffill_fid0 assert 'subfulfillments' not in ffill_fid0
assert 'subfulfillments' in ffill_fid1 assert 'subfulfillments' in ffill_fid1
assert len(ffill_fid1['subfulfillments']) == 2 assert len(ffill_fid1['subfulfillments']) == 2
@ -435,19 +435,19 @@ def test_muiltiple_in_mix_own_multiple_out_mix_own_transfer(b, user_pk,
tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk]) tx_transfer_signed = tx_transfer.sign([b.me_private, user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 2 assert len(tx_transfer_signed.outputs) == 2
assert tx_transfer_signed.conditions[0].amount == 50 assert tx_transfer_signed.outputs[0].amount == 50
assert tx_transfer_signed.conditions[1].amount == 50 assert tx_transfer_signed.outputs[1].amount == 50
assert len(tx_transfer_signed.fulfillments) == 2 assert len(tx_transfer_signed.inputs) == 2
cond_cid0 = tx_transfer_signed.conditions[0].to_dict() cond_cid0 = tx_transfer_signed.outputs[0].to_dict()
cond_cid1 = tx_transfer_signed.conditions[1].to_dict() cond_cid1 = tx_transfer_signed.outputs[1].to_dict()
assert 'subfulfillments' not in cond_cid0['condition']['details'] assert 'subfulfillments' not in cond_cid0['condition']['details']
assert 'subfulfillments' in cond_cid1['condition']['details'] assert 'subfulfillments' in cond_cid1['condition']['details']
assert len(cond_cid1['condition']['details']['subfulfillments']) == 2 assert len(cond_cid1['condition']['details']['subfulfillments']) == 2
ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict() ffill_fid0 = tx_transfer_signed.inputs[0].fulfillment.to_dict()
ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict() ffill_fid1 = tx_transfer_signed.inputs[1].fulfillment.to_dict()
assert 'subfulfillments' not in ffill_fid0 assert 'subfulfillments' not in ffill_fid0
assert 'subfulfillments' in ffill_fid1 assert 'subfulfillments' in ffill_fid1
assert len(ffill_fid1['subfulfillments']) == 2 assert len(ffill_fid1['subfulfillments']) == 2
@ -502,12 +502,12 @@ def test_multiple_in_different_transactions(b, user_pk, user_sk):
tx_transfer2_signed = tx_transfer2.sign([user_sk]) tx_transfer2_signed = tx_transfer2.sign([user_sk])
assert tx_transfer2_signed.validate(b) == tx_transfer2_signed assert tx_transfer2_signed.validate(b) == tx_transfer2_signed
assert len(tx_transfer2_signed.conditions) == 1 assert len(tx_transfer2_signed.outputs) == 1
assert tx_transfer2_signed.conditions[0].amount == 100 assert tx_transfer2_signed.outputs[0].amount == 100
assert len(tx_transfer2_signed.fulfillments) == 2 assert len(tx_transfer2_signed.inputs) == 2
fid0_input = tx_transfer2_signed.fulfillments[0].to_dict()['input']['txid'] fid0_input = tx_transfer2_signed.inputs[0].fulfills.txid
fid1_input = tx_transfer2_signed.fulfillments[1].to_dict()['input']['txid'] fid1_input = tx_transfer2_signed.inputs[1].fulfills.txid
assert fid0_input == tx_create.id assert fid0_input == tx_create.id
assert fid1_input == tx_transfer1.id assert fid1_input == tx_transfer1.id
@ -604,8 +604,8 @@ def test_sum_amount(b, user_pk, user_sk):
tx_transfer_signed = tx_transfer.sign([user_sk]) tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 1 assert len(tx_transfer_signed.outputs) == 1
assert tx_transfer_signed.conditions[0].amount == 3 assert tx_transfer_signed.outputs[0].amount == 3
@pytest.mark.bdb @pytest.mark.bdb
@ -632,9 +632,9 @@ def test_divide(b, user_pk, user_sk):
tx_transfer_signed = tx_transfer.sign([user_sk]) tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 3 assert len(tx_transfer_signed.outputs) == 3
for condition in tx_transfer_signed.conditions: for output in tx_transfer_signed.outputs:
assert condition.amount == 1 assert output.amount == 1
# Check that negative inputs are caught when creating a TRANSFER transaction # Check that negative inputs are caught when creating a TRANSFER transaction
@ -684,7 +684,7 @@ def test_non_positive_amounts_on_transfer_validate(b, user_pk, user_sk):
tx_transfer = Transaction.transfer(tx_create.to_inputs(), tx_transfer = Transaction.transfer(tx_create.to_inputs(),
[([b.me], 4), ([b.me], 1)], [([b.me], 4), ([b.me], 1)],
asset_id=tx_create.id) asset_id=tx_create.id)
tx_transfer.conditions[1].amount = -1 tx_transfer.outputs[1].amount = -1
tx_transfer_signed = tx_transfer.sign([user_sk]) tx_transfer_signed = tx_transfer.sign([user_sk])
with pytest.raises(AmountError): with pytest.raises(AmountError):
@ -712,7 +712,7 @@ def test_non_positive_amounts_on_create_validate(b, user_pk):
# CREATE divisible asset with 1 output with amount 3 # CREATE divisible asset with 1 output with amount 3
tx_create = Transaction.create([b.me], [([user_pk], 3)]) tx_create = Transaction.create([b.me], [([user_pk], 3)])
tx_create.conditions[0].amount = -3 tx_create.outputs[0].amount = -3
tx_create_signed = tx_create.sign([b.me_private]) tx_create_signed = tx_create.sign([b.me_private])
with pytest.raises(AmountError): with pytest.raises(AmountError):

View File

@ -93,39 +93,39 @@ def user2_Ed25519(user2_pub):
@pytest.fixture @pytest.fixture
def user_ffill(user_Ed25519, user_pub): def user_input(user_Ed25519, user_pub):
from bigchaindb.common.transaction import Fulfillment from bigchaindb.common.transaction import Input
return Fulfillment(user_Ed25519, [user_pub]) return Input(user_Ed25519, [user_pub])
@pytest.fixture @pytest.fixture
def user2_ffill(user2_Ed25519, user2_pub): def user2_input(user2_Ed25519, user2_pub):
from bigchaindb.common.transaction import Fulfillment from bigchaindb.common.transaction import Input
return Fulfillment(user2_Ed25519, [user2_pub]) return Input(user2_Ed25519, [user2_pub])
@pytest.fixture @pytest.fixture
def user_user2_threshold_cond(user_user2_threshold, user_pub, user2_pub): def user_user2_threshold_output(user_user2_threshold, user_pub, user2_pub):
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
return Condition(user_user2_threshold, [user_pub, user2_pub]) return Output(user_user2_threshold, [user_pub, user2_pub])
@pytest.fixture @pytest.fixture
def user_user2_threshold_ffill(user_user2_threshold, user_pub, user2_pub): def user_user2_threshold_input(user_user2_threshold, user_pub, user2_pub):
from bigchaindb.common.transaction import Fulfillment from bigchaindb.common.transaction import Input
return Fulfillment(user_user2_threshold, [user_pub, user2_pub]) return Input(user_user2_threshold, [user_pub, user2_pub])
@pytest.fixture @pytest.fixture
def user_cond(user_Ed25519, user_pub): def user_output(user_Ed25519, user_pub):
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
return Condition(user_Ed25519, [user_pub]) return Output(user_Ed25519, [user_pub])
@pytest.fixture @pytest.fixture
def user2_cond(user2_Ed25519, user2_pub): def user2_output(user2_Ed25519, user2_pub):
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
return Condition(user2_Ed25519, [user2_pub]) return Output(user2_Ed25519, [user2_pub])
@pytest.fixture @pytest.fixture
@ -144,9 +144,10 @@ def data():
@pytest.fixture @pytest.fixture
def utx(user_ffill, user_cond): def utx(user_input, user_output):
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
return Transaction(Transaction.CREATE, {'data': None}, [user_ffill], [user_cond]) return Transaction(Transaction.CREATE, {'data': None}, [user_input],
[user_output])
@pytest.fixture @pytest.fixture
@ -155,14 +156,14 @@ def tx(utx, user_priv):
@pytest.fixture @pytest.fixture
def transfer_utx(user_cond, user2_cond, utx): def transfer_utx(user_output, user2_output, utx):
from bigchaindb.common.transaction import (Fulfillment, TransactionLink, from bigchaindb.common.transaction import (Input, TransactionLink,
Transaction) Transaction)
user_cond = user_cond.to_dict() user_output = user_output.to_dict()
ffill = Fulfillment(utx.conditions[0].fulfillment, input = Input(utx.outputs[0].fulfillment,
user_cond['owners_after'], user_output['public_keys'],
TransactionLink(utx.id, 0)) TransactionLink(utx.id, 0))
return Transaction('TRANSFER', {'id': utx.id}, [ffill], [user2_cond]) return Transaction('TRANSFER', {'id': utx.id}, [input], [user2_output])
@pytest.fixture @pytest.fixture

View File

@ -1,111 +1,110 @@
from pytest import raises from pytest import raises
def test_fulfillment_serialization(ffill_uri, user_pub): def test_input_serialization(ffill_uri, user_pub):
from bigchaindb.common.transaction import Fulfillment from bigchaindb.common.transaction import Input
from cryptoconditions import Fulfillment as CCFulfillment from cryptoconditions import Fulfillment
expected = { expected = {
'owners_before': [user_pub], 'owners_before': [user_pub],
'fulfillment': ffill_uri, 'fulfillment': ffill_uri,
'input': None, 'fulfills': None,
} }
ffill = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) input = Input(Fulfillment.from_uri(ffill_uri), [user_pub])
assert ffill.to_dict() == expected assert input.to_dict() == expected
def test_fulfillment_deserialization_with_uri(ffill_uri, user_pub): def test_input_deserialization_with_uri(ffill_uri, user_pub):
from bigchaindb.common.transaction import Fulfillment from bigchaindb.common.transaction import Input
from cryptoconditions import Fulfillment as CCFulfillment from cryptoconditions import Fulfillment
expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) expected = Input(Fulfillment.from_uri(ffill_uri), [user_pub])
ffill = { ffill = {
'owners_before': [user_pub], 'owners_before': [user_pub],
'fulfillment': ffill_uri, 'fulfillment': ffill_uri,
'input': None, 'fulfills': None,
} }
ffill = Fulfillment.from_dict(ffill) input = Input.from_dict(ffill)
assert ffill == expected assert input == expected
def test_fulfillment_deserialization_with_invalid_fulfillment(user_pub): def test_input_deserialization_with_invalid_input(user_pub):
from bigchaindb.common.transaction import Fulfillment from bigchaindb.common.transaction import Input
ffill = { ffill = {
'owners_before': [user_pub], 'owners_before': [user_pub],
'fulfillment': None, 'fulfillment': None,
'input': None, 'fulfills': None,
} }
with raises(TypeError): with raises(TypeError):
Fulfillment.from_dict(ffill) Input.from_dict(ffill)
def test_fulfillment_deserialization_with_invalid_fulfillment_uri(user_pub): def test_input_deserialization_with_invalid_fulfillment_uri(user_pub):
from bigchaindb.common.exceptions import InvalidSignature from bigchaindb.common.exceptions import InvalidSignature
from bigchaindb.common.transaction import Fulfillment from bigchaindb.common.transaction import Input
ffill = { ffill = {
'owners_before': [user_pub], 'owners_before': [user_pub],
'fulfillment': 'an invalid fulfillment', 'fulfillment': 'an invalid fulfillment',
'input': None, 'fulfills': None,
} }
with raises(InvalidSignature): with raises(InvalidSignature):
Fulfillment.from_dict(ffill) Input.from_dict(ffill)
def test_fulfillment_deserialization_with_unsigned_fulfillment(ffill_uri, def test_input_deserialization_with_unsigned_fulfillment(ffill_uri, user_pub):
user_pub): from bigchaindb.common.transaction import Input
from bigchaindb.common.transaction import Fulfillment from cryptoconditions import Fulfillment
from cryptoconditions import Fulfillment as CCFulfillment
expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) expected = Input(Fulfillment.from_uri(ffill_uri), [user_pub])
ffill = { ffill = {
'owners_before': [user_pub], 'owners_before': [user_pub],
'fulfillment': CCFulfillment.from_uri(ffill_uri), 'fulfillment': Fulfillment.from_uri(ffill_uri),
'input': None, 'fulfills': None,
} }
ffill = Fulfillment.from_dict(ffill) input = Input.from_dict(ffill)
assert ffill == expected assert input == expected
def test_condition_serialization(user_Ed25519, user_pub): def test_output_serialization(user_Ed25519, user_pub):
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
expected = { expected = {
'condition': { 'condition': {
'uri': user_Ed25519.condition_uri, 'uri': user_Ed25519.condition_uri,
'details': user_Ed25519.to_dict(), 'details': user_Ed25519.to_dict(),
}, },
'owners_after': [user_pub], 'public_keys': [user_pub],
'amount': 1, 'amount': 1,
} }
cond = Condition(user_Ed25519, [user_pub], 1) cond = Output(user_Ed25519, [user_pub], 1)
assert cond.to_dict() == expected assert cond.to_dict() == expected
def test_condition_deserialization(user_Ed25519, user_pub): def test_output_deserialization(user_Ed25519, user_pub):
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
expected = Condition(user_Ed25519, [user_pub], 1) expected = Output(user_Ed25519, [user_pub], 1)
cond = { cond = {
'condition': { 'condition': {
'uri': user_Ed25519.condition_uri, 'uri': user_Ed25519.condition_uri,
'details': user_Ed25519.to_dict() 'details': user_Ed25519.to_dict()
}, },
'owners_after': [user_pub], 'public_keys': [user_pub],
'amount': 1, 'amount': 1,
} }
cond = Condition.from_dict(cond) cond = Output.from_dict(cond)
assert cond == expected assert cond == expected
def test_condition_hashlock_serialization(): def test_output_hashlock_serialization():
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
from cryptoconditions import PreimageSha256Fulfillment from cryptoconditions import PreimageSha256Fulfillment
secret = b'wow much secret' secret = b'wow much secret'
@ -115,49 +114,49 @@ def test_condition_hashlock_serialization():
'condition': { 'condition': {
'uri': hashlock, 'uri': hashlock,
}, },
'owners_after': None, 'public_keys': None,
'amount': 1, 'amount': 1,
} }
cond = Condition(hashlock, amount=1) cond = Output(hashlock, amount=1)
assert cond.to_dict() == expected assert cond.to_dict() == expected
def test_condition_hashlock_deserialization(): def test_output_hashlock_deserialization():
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
from cryptoconditions import PreimageSha256Fulfillment from cryptoconditions import PreimageSha256Fulfillment
secret = b'wow much secret' secret = b'wow much secret'
hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri
expected = Condition(hashlock, amount=1) expected = Output(hashlock, amount=1)
cond = { cond = {
'condition': { 'condition': {
'uri': hashlock 'uri': hashlock
}, },
'owners_after': None, 'public_keys': None,
'amount': 1, 'amount': 1,
} }
cond = Condition.from_dict(cond) cond = Output.from_dict(cond)
assert cond == expected assert cond == expected
def test_invalid_condition_initialization(cond_uri, user_pub): def test_invalid_output_initialization(cond_uri, user_pub):
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
from bigchaindb.common.exceptions import AmountError from bigchaindb.common.exceptions import AmountError
with raises(TypeError): with raises(TypeError):
Condition(cond_uri, user_pub) Output(cond_uri, user_pub)
with raises(TypeError): with raises(TypeError):
Condition(cond_uri, [user_pub], 'amount') Output(cond_uri, [user_pub], 'amount')
with raises(AmountError): with raises(AmountError):
Condition(cond_uri, [user_pub], 0) Output(cond_uri, [user_pub], 0)
def test_generate_conditions_split_half_recursive(user_pub, user2_pub, def test_generate_output_split_half_recursive(user_pub, user2_pub,
user3_pub): user3_pub):
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment
expected_simple1 = Ed25519Fulfillment(public_key=user_pub) expected_simple1 = Ed25519Fulfillment(public_key=user_pub)
@ -171,13 +170,13 @@ def test_generate_conditions_split_half_recursive(user_pub, user2_pub,
expected_threshold.add_subfulfillment(expected_simple3) expected_threshold.add_subfulfillment(expected_simple3)
expected.add_subfulfillment(expected_threshold) expected.add_subfulfillment(expected_threshold)
cond = Condition.generate([user_pub, [user2_pub, expected_simple3]], 1) cond = Output.generate([user_pub, [user2_pub, expected_simple3]], 1)
assert cond.fulfillment.to_dict() == expected.to_dict() assert cond.fulfillment.to_dict() == expected.to_dict()
def test_generate_conditions_split_half_single_owner(user_pub, user2_pub, def test_generate_outputs_split_half_single_owner(user_pub, user2_pub,
user3_pub): user3_pub):
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment
expected_simple1 = Ed25519Fulfillment(public_key=user_pub) expected_simple1 = Ed25519Fulfillment(public_key=user_pub)
@ -191,12 +190,12 @@ def test_generate_conditions_split_half_single_owner(user_pub, user2_pub,
expected.add_subfulfillment(expected_threshold) expected.add_subfulfillment(expected_threshold)
expected.add_subfulfillment(expected_simple1) expected.add_subfulfillment(expected_simple1)
cond = Condition.generate([[expected_simple2, user3_pub], user_pub], 1) cond = Output.generate([[expected_simple2, user3_pub], user_pub], 1)
assert cond.fulfillment.to_dict() == expected.to_dict() assert cond.fulfillment.to_dict() == expected.to_dict()
def test_generate_conditions_flat_ownage(user_pub, user2_pub, user3_pub): def test_generate_outputs_flat_ownage(user_pub, user2_pub, user3_pub):
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment
expected_simple1 = Ed25519Fulfillment(public_key=user_pub) expected_simple1 = Ed25519Fulfillment(public_key=user_pub)
@ -208,42 +207,42 @@ def test_generate_conditions_flat_ownage(user_pub, user2_pub, user3_pub):
expected.add_subfulfillment(expected_simple2) expected.add_subfulfillment(expected_simple2)
expected.add_subfulfillment(expected_simple3) expected.add_subfulfillment(expected_simple3)
cond = Condition.generate([user_pub, user2_pub, expected_simple3], 1) cond = Output.generate([user_pub, user2_pub, expected_simple3], 1)
assert cond.fulfillment.to_dict() == expected.to_dict() assert cond.fulfillment.to_dict() == expected.to_dict()
def test_generate_conditions_single_owner(user_pub): def test_generate_output_single_owner(user_pub):
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Fulfillment from cryptoconditions import Ed25519Fulfillment
expected = Ed25519Fulfillment(public_key=user_pub) expected = Ed25519Fulfillment(public_key=user_pub)
cond = Condition.generate([user_pub], 1) cond = Output.generate([user_pub], 1)
assert cond.fulfillment.to_dict() == expected.to_dict() assert cond.fulfillment.to_dict() == expected.to_dict()
def test_generate_conditions_single_owner_with_condition(user_pub): def test_generate_output_single_owner_with_output(user_pub):
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Fulfillment from cryptoconditions import Ed25519Fulfillment
expected = Ed25519Fulfillment(public_key=user_pub) expected = Ed25519Fulfillment(public_key=user_pub)
cond = Condition.generate([expected], 1) cond = Output.generate([expected], 1)
assert cond.fulfillment.to_dict() == expected.to_dict() assert cond.fulfillment.to_dict() == expected.to_dict()
def test_generate_conditions_invalid_parameters(user_pub, user2_pub, def test_generate_output_invalid_parameters(user_pub, user2_pub,
user3_pub): user3_pub):
from bigchaindb.common.transaction import Condition from bigchaindb.common.transaction import Output
with raises(ValueError): with raises(ValueError):
Condition.generate([], 1) Output.generate([], 1)
with raises(TypeError): with raises(TypeError):
Condition.generate('not a list', 1) Output.generate('not a list', 1)
with raises(ValueError): with raises(ValueError):
Condition.generate([[user_pub, [user2_pub, [user3_pub]]]], 1) Output.generate([[user_pub, [user2_pub, [user3_pub]]]], 1)
with raises(ValueError): with raises(ValueError):
Condition.generate([[user_pub]], 1) Output.generate([[user_pub]], 1)
def test_invalid_transaction_initialization(asset_definition): def test_invalid_transaction_initialization(asset_definition):
@ -259,21 +258,21 @@ def test_invalid_transaction_initialization(asset_definition):
Transaction( Transaction(
operation='CREATE', operation='CREATE',
asset=asset_definition, asset=asset_definition,
conditions='invalid conditions' outputs='invalid outputs'
) )
with raises(TypeError): with raises(TypeError):
Transaction( Transaction(
operation='CREATE', operation='CREATE',
asset=asset_definition, asset=asset_definition,
conditions=[], outputs=[],
fulfillments='invalid fulfillments' inputs='invalid inputs'
) )
with raises(TypeError): with raises(TypeError):
Transaction( Transaction(
operation='CREATE', operation='CREATE',
asset=asset_definition, asset=asset_definition,
conditions=[], outputs=[],
fulfillments=[], inputs=[],
metadata='invalid metadata' metadata='invalid metadata'
) )
@ -288,18 +287,19 @@ def test_create_default_asset_on_tx_initialization(asset_definition):
assert asset == expected assert asset == expected
def test_transaction_serialization(user_ffill, user_cond, data): def test_transaction_serialization(user_input, user_output, data):
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
from .util import validate_transaction_model
tx_id = 'l0l' tx_id = 'l0l'
expected = { expected = {
'id': tx_id, 'id': tx_id,
'version': Transaction.VERSION, 'version': Transaction.VERSION,
# NOTE: This test assumes that Fulfillments and Conditions can # NOTE: This test assumes that Inputs and Outputs can
# successfully be serialized # successfully be serialized
'fulfillments': [user_ffill.to_dict()], 'inputs': [user_input.to_dict()],
'conditions': [user_cond.to_dict()], 'outputs': [user_output.to_dict()],
'operation': Transaction.CREATE, 'operation': Transaction.CREATE,
'metadata': None, 'metadata': None,
'asset': { 'asset': {
@ -307,28 +307,28 @@ def test_transaction_serialization(user_ffill, user_cond, data):
} }
} }
tx = Transaction(Transaction.CREATE, {'data': data}, [user_ffill], tx = Transaction(Transaction.CREATE, {'data': data}, [user_input],
[user_cond]) [user_output])
tx_dict = tx.to_dict() tx_dict = tx.to_dict()
tx_dict['id'] = tx_id tx_dict['id'] = tx_id
assert tx_dict == expected assert tx_dict == expected
def test_transaction_deserialization(user_ffill, user_cond, data): def test_transaction_deserialization(user_input, user_output, data):
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
from .util import validate_transaction_model from .util import validate_transaction_model
expected_asset = {'data': data} expected_asset = {'data': data}
expected = Transaction(Transaction.CREATE, expected_asset, [user_ffill], expected = Transaction(Transaction.CREATE, expected_asset, [user_input],
[user_cond], None, Transaction.VERSION) [user_output], None, Transaction.VERSION)
tx = { tx = {
'version': Transaction.VERSION, 'version': Transaction.VERSION,
# NOTE: This test assumes that Fulfillments and Conditions can # NOTE: This test assumes that Inputs and Outputs can
# successfully be serialized # successfully be serialized
'fulfillments': [user_ffill.to_dict()], 'inputs': [user_input.to_dict()],
'conditions': [user_cond.to_dict()], 'outputs': [user_output.to_dict()],
'operation': Transaction.CREATE, 'operation': Transaction.CREATE,
'metadata': None, 'metadata': None,
'asset': { 'asset': {
@ -355,13 +355,13 @@ def test_tx_serialization_with_incorrect_hash(utx):
utx_dict.pop('id') utx_dict.pop('id')
def test_invalid_fulfillment_initialization(user_ffill, user_pub): def test_invalid_input_initialization(user_input, user_pub):
from bigchaindb.common.transaction import Fulfillment from bigchaindb.common.transaction import Input
with raises(TypeError): with raises(TypeError):
Fulfillment(user_ffill, user_pub) Input(user_input, user_pub)
with raises(TypeError): with raises(TypeError):
Fulfillment(user_ffill, [], tx_input='somethingthatiswrong') Input(user_input, tx_input='somethingthatiswrong')
def test_transaction_link_serialization(): def test_transaction_link_serialization():
@ -370,7 +370,7 @@ def test_transaction_link_serialization():
tx_id = 'a transaction id' tx_id = 'a transaction id'
expected = { expected = {
'txid': tx_id, 'txid': tx_id,
'cid': 0, 'output': 0,
} }
tx_link = TransactionLink(tx_id, 0) tx_link = TransactionLink(tx_id, 0)
@ -393,7 +393,7 @@ def test_transaction_link_deserialization():
expected = TransactionLink(tx_id, 0) expected = TransactionLink(tx_id, 0)
tx_link = { tx_link = {
'txid': tx_id, 'txid': tx_id,
'cid': 0, 'output': 0,
} }
tx_link = TransactionLink.from_dict(tx_link) tx_link = TransactionLink.from_dict(tx_link)
@ -421,7 +421,7 @@ def test_transaction_link_empty_to_uri():
def test_transaction_link_to_uri(): def test_transaction_link_to_uri():
from bigchaindb.common.transaction import TransactionLink from bigchaindb.common.transaction import TransactionLink
expected = 'path/transactions/abc/conditions/0' expected = 'path/transactions/abc/outputs/0'
tx_link = TransactionLink('abc', 0).to_uri('path') tx_link = TransactionLink('abc', 0).to_uri('path')
assert expected == tx_link assert expected == tx_link
@ -437,41 +437,41 @@ def test_cast_transaction_link_to_boolean():
assert bool(TransactionLink(False, False)) is True assert bool(TransactionLink(False, False)) is True
def test_add_fulfillment_to_tx(user_ffill, asset_definition): def test_add_input_to_tx(user_input, asset_definition):
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
tx = Transaction(Transaction.CREATE, asset_definition, [], []) tx = Transaction(Transaction.CREATE, asset_definition, [], [])
tx.add_fulfillment(user_ffill) tx.add_input(user_input)
assert len(tx.fulfillments) == 1 assert len(tx.inputs) == 1
def test_add_fulfillment_to_tx_with_invalid_parameters(asset_definition): def test_add_input_to_tx_with_invalid_parameters(asset_definition):
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
tx = Transaction(Transaction.CREATE, asset_definition) tx = Transaction(Transaction.CREATE, asset_definition)
with raises(TypeError): with raises(TypeError):
tx.add_fulfillment('somewronginput') tx.add_input('somewronginput')
def test_add_condition_to_tx(user_cond, asset_definition): def test_add_output_to_tx(user_output, asset_definition):
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
from .util import validate_transaction_model from .util import validate_transaction_model
tx = Transaction(Transaction.CREATE, asset_definition) tx = Transaction(Transaction.CREATE, asset_definition)
tx.add_condition(user_cond) tx.add_output(user_output)
assert len(tx.conditions) == 1 assert len(tx.outputs) == 1
validate_transaction_model(tx) validate_transaction_model(tx)
def test_add_condition_to_tx_with_invalid_parameters(asset_definition): def test_add_output_to_tx_with_invalid_parameters(asset_definition):
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
tx = Transaction(Transaction.CREATE, asset_definition, [], []) tx = Transaction(Transaction.CREATE, asset_definition, [], [])
with raises(TypeError): with raises(TypeError):
tx.add_condition('somewronginput') tx.add_output('somewronginput')
def test_sign_with_invalid_parameters(utx, user_priv): def test_sign_with_invalid_parameters(utx, user_priv):
@ -481,69 +481,67 @@ def test_sign_with_invalid_parameters(utx, user_priv):
utx.sign(user_priv) utx.sign(user_priv)
def test_validate_tx_simple_create_signature(user_ffill, user_cond, user_priv, def test_validate_tx_simple_create_signature(user_input, user_output, user_priv,
asset_definition): asset_definition):
from copy import deepcopy from copy import deepcopy
from bigchaindb.common.crypto import PrivateKey from bigchaindb.common.crypto import PrivateKey
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
from .util import validate_transaction_model from .util import validate_transaction_model
tx = Transaction(Transaction.CREATE, asset_definition, [user_ffill], [user_cond]) tx = Transaction(Transaction.CREATE, asset_definition, [user_input], [user_output])
expected = deepcopy(user_cond) expected = deepcopy(user_output)
expected.fulfillment.sign(str(tx).encode(), PrivateKey(user_priv)) expected.fulfillment.sign(str(tx).encode(), PrivateKey(user_priv))
tx.sign([user_priv]) tx.sign([user_priv])
assert tx.fulfillments[0].to_dict()['fulfillment'] == \ assert tx.inputs[0].to_dict()['fulfillment'] == \
expected.fulfillment.serialize_uri() expected.fulfillment.serialize_uri()
assert tx.fulfillments_valid() is True assert tx.inputs_valid() is True
validate_transaction_model(tx) validate_transaction_model(tx)
def test_invoke_simple_signature_fulfillment_with_invalid_params(utx, def test_invoke_simple_signature_fulfillment_with_invalid_params(utx,
user_ffill): user_input):
from bigchaindb.common.exceptions import KeypairMismatchException from bigchaindb.common.exceptions import KeypairMismatchException
with raises(KeypairMismatchException): with raises(KeypairMismatchException):
invalid_key_pair = {'wrong_pub_key': 'wrong_priv_key'} invalid_key_pair = {'wrong_pub_key': 'wrong_priv_key'}
utx._sign_simple_signature_fulfillment(user_ffill, utx._sign_simple_signature_fulfillment(user_input,
0, 0,
'somemessage', 'somemessage',
invalid_key_pair) invalid_key_pair)
def test_sign_threshold_with_invalid_params(utx, user_user2_threshold_ffill, def test_sign_threshold_with_invalid_params(utx, user_user2_threshold_input,
user3_pub, user3_priv): user3_pub, user3_priv):
from bigchaindb.common.exceptions import KeypairMismatchException from bigchaindb.common.exceptions import KeypairMismatchException
with raises(KeypairMismatchException): with raises(KeypairMismatchException):
utx._sign_threshold_signature_fulfillment(user_user2_threshold_ffill, utx._sign_threshold_signature_fulfillment(user_user2_threshold_input,
0, 0,
'somemessage', 'somemessage',
{user3_pub: user3_priv}) {user3_pub: user3_priv})
with raises(KeypairMismatchException): with raises(KeypairMismatchException):
user_user2_threshold_ffill.owners_before = ['somewrongvalue'] user_user2_threshold_input.owners_before = ['somewrongvalue']
utx._sign_threshold_signature_fulfillment(user_user2_threshold_ffill, utx._sign_threshold_signature_fulfillment(user_user2_threshold_input,
0, 0,
'somemessage', 'somemessage',
None) None)
def test_validate_fulfillment_with_invalid_parameters(utx): def test_validate_input_with_invalid_parameters(utx):
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
input_conditions = [cond.fulfillment.condition_uri for cond input_conditions = [out.fulfillment.condition_uri for out in utx.outputs]
in utx.conditions]
tx_dict = utx.to_dict() tx_dict = utx.to_dict()
tx_dict = Transaction._remove_signatures(tx_dict) tx_dict = Transaction._remove_signatures(tx_dict)
tx_serialized = Transaction._to_str(tx_dict) tx_serialized = Transaction._to_str(tx_dict)
assert utx._fulfillment_valid(utx.fulfillments[0], valid = utx._input_valid(utx.inputs[0], tx_serialized, input_conditions)
tx_serialized, assert not valid
input_conditions) is False
def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv, def test_validate_multiple_inputs(user_input, user_output, user_priv,
asset_definition): asset_definition):
from copy import deepcopy from copy import deepcopy
from bigchaindb.common.crypto import PrivateKey from bigchaindb.common.crypto import PrivateKey
@ -551,33 +549,33 @@ def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv,
from .util import validate_transaction_model from .util import validate_transaction_model
tx = Transaction(Transaction.CREATE, asset_definition, tx = Transaction(Transaction.CREATE, asset_definition,
[user_ffill, deepcopy(user_ffill)], [user_input, deepcopy(user_input)],
[user_cond, deepcopy(user_cond)]) [user_output, deepcopy(user_output)])
expected_first = deepcopy(tx) expected_first = deepcopy(tx)
expected_second = deepcopy(tx) expected_second = deepcopy(tx)
expected_first.fulfillments = [expected_first.fulfillments[0]] expected_first.inputs = [expected_first.inputs[0]]
expected_second.fulfillments = [expected_second.fulfillments[1]] expected_second.inputs = [expected_second.inputs[1]]
expected_first_bytes = str(expected_first).encode() expected_first_bytes = str(expected_first).encode()
expected_first.fulfillments[0].fulfillment.sign(expected_first_bytes, expected_first.inputs[0].fulfillment.sign(expected_first_bytes,
PrivateKey(user_priv)) PrivateKey(user_priv))
expected_second_bytes = str(expected_second).encode() expected_second_bytes = str(expected_second).encode()
expected_second.fulfillments[0].fulfillment.sign(expected_second_bytes, expected_second.inputs[0].fulfillment.sign(expected_second_bytes,
PrivateKey(user_priv)) PrivateKey(user_priv))
tx.sign([user_priv]) tx.sign([user_priv])
assert tx.fulfillments[0].to_dict()['fulfillment'] == \ assert tx.inputs[0].to_dict()['fulfillment'] == \
expected_first.fulfillments[0].fulfillment.serialize_uri() expected_first.inputs[0].fulfillment.serialize_uri()
assert tx.fulfillments[1].to_dict()['fulfillment'] == \ assert tx.inputs[1].to_dict()['fulfillment'] == \
expected_second.fulfillments[0].fulfillment.serialize_uri() expected_second.inputs[0].fulfillment.serialize_uri()
assert tx.fulfillments_valid() is True assert tx.inputs_valid() is True
validate_transaction_model(tx) validate_transaction_model(tx)
def test_validate_tx_threshold_create_signature(user_user2_threshold_ffill, def test_validate_tx_threshold_create_signature(user_user2_threshold_input,
user_user2_threshold_cond, user_user2_threshold_output,
user_pub, user_pub,
user2_pub, user2_pub,
user_priv, user_priv,
@ -590,96 +588,90 @@ def test_validate_tx_threshold_create_signature(user_user2_threshold_ffill,
from .util import validate_transaction_model from .util import validate_transaction_model
tx = Transaction(Transaction.CREATE, asset_definition, tx = Transaction(Transaction.CREATE, asset_definition,
[user_user2_threshold_ffill], [user_user2_threshold_input],
[user_user2_threshold_cond]) [user_user2_threshold_output])
expected = deepcopy(user_user2_threshold_cond) expected = deepcopy(user_user2_threshold_output)
expected.fulfillment.subconditions[0]['body'].sign(str(tx).encode(), expected.fulfillment.subconditions[0]['body'].sign(str(tx).encode(),
PrivateKey(user_priv)) PrivateKey(user_priv))
expected.fulfillment.subconditions[1]['body'].sign(str(tx).encode(), expected.fulfillment.subconditions[1]['body'].sign(str(tx).encode(),
PrivateKey(user2_priv)) PrivateKey(user2_priv))
tx.sign([user_priv, user2_priv]) tx.sign([user_priv, user2_priv])
assert tx.fulfillments[0].to_dict()['fulfillment'] == \ assert tx.inputs[0].to_dict()['fulfillment'] == \
expected.fulfillment.serialize_uri() expected.fulfillment.serialize_uri()
assert tx.fulfillments_valid() is True assert tx.inputs_valid() is True
validate_transaction_model(tx) validate_transaction_model(tx)
def test_multiple_fulfillment_validation_of_transfer_tx(user_ffill, user_cond, def test_multiple_input_validation_of_transfer_tx(user_input, user_output,
user_priv, user2_pub, user_priv, user2_pub,
user2_priv, user3_pub, user2_priv, user3_pub,
user3_priv, user3_priv,
asset_definition): asset_definition):
from copy import deepcopy from copy import deepcopy
from bigchaindb.common.transaction import (Transaction, TransactionLink, from bigchaindb.common.transaction import (Transaction, TransactionLink,
Fulfillment, Condition) Input, Output)
from cryptoconditions import Ed25519Fulfillment from cryptoconditions import Ed25519Fulfillment
from .util import validate_transaction_model from .util import validate_transaction_model
tx = Transaction(Transaction.CREATE, asset_definition, tx = Transaction(Transaction.CREATE, asset_definition,
[user_ffill, deepcopy(user_ffill)], [user_input, deepcopy(user_input)],
[user_cond, deepcopy(user_cond)]) [user_output, deepcopy(user_output)])
tx.sign([user_priv]) tx.sign([user_priv])
fulfillments = [Fulfillment(cond.fulfillment, cond.owners_after, inputs = [Input(cond.fulfillment, cond.public_keys,
TransactionLink(tx.id, index)) TransactionLink(tx.id, index))
for index, cond in enumerate(tx.conditions)] for index, cond in enumerate(tx.outputs)]
conditions = [Condition(Ed25519Fulfillment(public_key=user3_pub), outputs = [Output(Ed25519Fulfillment(public_key=user3_pub), [user3_pub]),
[user3_pub]), Output(Ed25519Fulfillment(public_key=user3_pub), [user3_pub])]
Condition(Ed25519Fulfillment(public_key=user3_pub), transfer_tx = Transaction('TRANSFER', {'id': tx.id}, inputs, outputs)
[user3_pub])]
transfer_tx = Transaction('TRANSFER', {'id': tx.id},
fulfillments, conditions)
transfer_tx = transfer_tx.sign([user_priv]) transfer_tx = transfer_tx.sign([user_priv])
assert transfer_tx.fulfillments_valid(tx.conditions) is True assert transfer_tx.inputs_valid(tx.outputs) is True
validate_transaction_model(tx) validate_transaction_model(tx)
def test_validate_fulfillments_of_transfer_tx_with_invalid_params(transfer_tx, def test_validate_inputs_of_transfer_tx_with_invalid_params(
cond_uri, transfer_tx, cond_uri, utx, user2_pub, user_priv):
utx, from bigchaindb.common.transaction import Output
user2_pub,
user_priv):
from bigchaindb.common.transaction import Condition
from cryptoconditions import Ed25519Fulfillment from cryptoconditions import Ed25519Fulfillment
invalid_cond = Condition(Ed25519Fulfillment.from_uri('cf:0:'), ['invalid']) invalid_out = Output(Ed25519Fulfillment.from_uri('cf:0:'), ['invalid'])
assert transfer_tx.fulfillments_valid([invalid_cond]) is False assert transfer_tx.inputs_valid([invalid_out]) is False
invalid_cond = utx.conditions[0] invalid_out = utx.outputs[0]
invalid_cond.owners_after = 'invalid' invalid_out.public_key = 'invalid'
assert transfer_tx.fulfillments_valid([invalid_cond]) is True assert transfer_tx.inputs_valid([invalid_out]) is True
with raises(TypeError): with raises(TypeError):
assert transfer_tx.fulfillments_valid(None) is False assert transfer_tx.inputs_valid(None) is False
with raises(AttributeError): with raises(AttributeError):
transfer_tx.fulfillments_valid('not a list') transfer_tx.inputs_valid('not a list')
with raises(ValueError): with raises(ValueError):
transfer_tx.fulfillments_valid([]) transfer_tx.inputs_valid([])
with raises(TypeError): with raises(TypeError):
transfer_tx.operation = "Operation that doesn't exist" transfer_tx.operation = "Operation that doesn't exist"
transfer_tx.fulfillments_valid([utx.conditions[0]]) transfer_tx.inputs_valid([utx.outputs[0]])
def test_create_create_transaction_single_io(user_cond, user_pub, data): def test_create_create_transaction_single_io(user_output, user_pub, data):
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
from .util import validate_transaction_model from .util import validate_transaction_model
expected = { expected = {
'conditions': [user_cond.to_dict()], 'outputs': [user_output.to_dict()],
'metadata': data, 'metadata': data,
'asset': { 'asset': {
'data': data, 'data': data,
}, },
'fulfillments': [ 'inputs': [
{ {
'owners_before': [ 'owners_before': [
user_pub user_pub
], ],
'fulfillment': None, 'fulfillment': None,
'input': None 'fulfills': None
} }
], ],
'operation': 'CREATE', 'operation': 'CREATE',
@ -689,7 +681,7 @@ def test_create_create_transaction_single_io(user_cond, user_pub, data):
tx = Transaction.create([user_pub], [([user_pub], 1)], metadata=data, tx = Transaction.create([user_pub], [([user_pub], 1)], metadata=data,
asset=data) asset=data)
tx_dict = tx.to_dict() tx_dict = tx.to_dict()
tx_dict['fulfillments'][0]['fulfillment'] = None tx_dict['inputs'][0]['fulfillment'] = None
tx_dict.pop('id') tx_dict.pop('id')
assert tx_dict == expected assert tx_dict == expected
@ -703,23 +695,23 @@ def test_validate_single_io_create_transaction(user_pub, user_priv, data,
tx = Transaction.create([user_pub], [([user_pub], 1)], metadata=data) tx = Transaction.create([user_pub], [([user_pub], 1)], metadata=data)
tx = tx.sign([user_priv]) tx = tx.sign([user_priv])
assert tx.fulfillments_valid() is True assert tx.inputs_valid() is True
def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, def test_create_create_transaction_multiple_io(user_output, user2_output, user_pub,
user2_pub, asset_definition): user2_pub, asset_definition):
from bigchaindb.common.transaction import Transaction, Fulfillment from bigchaindb.common.transaction import Transaction, Input
# a fulfillment for a create transaction with multiple `owners_before` # a fulfillment for a create transaction with multiple `owners_before`
# is a fulfillment for an implicit threshold condition with # is a fulfillment for an implicit threshold condition with
# weight = len(owners_before) # weight = len(owners_before)
ffill = Fulfillment.generate([user_pub, user2_pub]).to_dict() input = Input.generate([user_pub, user2_pub]).to_dict()
expected = { expected = {
'conditions': [user_cond.to_dict(), user2_cond.to_dict()], 'outputs': [user_output.to_dict(), user2_output.to_dict()],
'metadata': { 'metadata': {
'message': 'hello' 'message': 'hello'
}, },
'fulfillments': [ffill], 'inputs': [input],
'operation': 'CREATE', 'operation': 'CREATE',
'version': 1 'version': 1
} }
@ -742,29 +734,29 @@ def test_validate_multiple_io_create_transaction(user_pub, user_priv,
[([user_pub], 1), ([user2_pub], 1)], [([user_pub], 1), ([user2_pub], 1)],
metadata={'message': 'hello'}) metadata={'message': 'hello'})
tx = tx.sign([user_priv, user2_priv]) tx = tx.sign([user_priv, user2_priv])
assert tx.fulfillments_valid() is True assert tx.inputs_valid() is True
validate_transaction_model(tx) validate_transaction_model(tx)
def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
user_user2_threshold_cond, user_user2_threshold_output,
user_user2_threshold_ffill, data): user_user2_threshold_input, data):
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
expected = { expected = {
'conditions': [user_user2_threshold_cond.to_dict()], 'outputs': [user_user2_threshold_output.to_dict()],
'metadata': data, 'metadata': data,
'asset': { 'asset': {
'data': data, 'data': data,
}, },
'fulfillments': [ 'inputs': [
{ {
'owners_before': [ 'owners_before': [
user_pub, user_pub,
], ],
'fulfillment': None, 'fulfillment': None,
'input': None 'fulfills': None,
}, },
], ],
'operation': 'CREATE', 'operation': 'CREATE',
@ -774,7 +766,7 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
metadata=data, asset=data) metadata=data, asset=data)
tx_dict = tx.to_dict() tx_dict = tx.to_dict()
tx_dict.pop('id') tx_dict.pop('id')
tx_dict['fulfillments'][0]['fulfillment'] = None tx_dict['inputs'][0]['fulfillment'] = None
assert tx_dict == expected assert tx_dict == expected
@ -787,7 +779,7 @@ def test_validate_threshold_create_transaction(user_pub, user_priv, user2_pub,
tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)], tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)],
metadata=data) metadata=data)
tx = tx.sign([user_priv]) tx = tx.sign([user_priv])
assert tx.fulfillments_valid() is True assert tx.inputs_valid() is True
validate_transaction_model(tx) validate_transaction_model(tx)
@ -816,18 +808,18 @@ def test_create_create_transaction_with_invalid_parameters(user_pub):
asset='not a dict or none') asset='not a dict or none')
def test_conditions_to_inputs(tx): def test_outputs_to_inputs(tx):
ffills = tx.to_inputs([0]) inputs = tx.to_inputs([0])
assert len(ffills) == 1 assert len(inputs) == 1
ffill = ffills.pop() input = inputs.pop()
assert ffill.fulfillment == tx.conditions[0].fulfillment assert input.owners_before == tx.outputs[0].public_keys
assert ffill.owners_before == tx.conditions[0].owners_after assert input.fulfillment == tx.outputs[0].fulfillment
assert ffill.tx_input.txid == tx.id assert input.fulfills.txid == tx.id
assert ffill.tx_input.cid == 0 assert input.fulfills.output == 0
def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
user2_cond, user_priv): user2_output, user_priv):
from copy import deepcopy from copy import deepcopy
from bigchaindb.common.crypto import PrivateKey from bigchaindb.common.crypto import PrivateKey
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
@ -835,20 +827,20 @@ 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 = {
'conditions': [user2_cond.to_dict()], 'outputs': [user2_output.to_dict()],
'metadata': None, 'metadata': None,
'asset': { 'asset': {
'id': tx.id, 'id': tx.id,
}, },
'fulfillments': [ 'inputs': [
{ {
'owners_before': [ 'owners_before': [
user_pub user_pub
], ],
'fulfillment': None, 'fulfillment': None,
'input': { 'fulfills': {
'txid': tx.id, 'txid': tx.id,
'cid': 0 'output': 0
} }
} }
], ],
@ -866,19 +858,19 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
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['fulfillments'][0]['fulfillment'] transfer_ffill = transfer_tx['inputs'][0]['fulfillment']
assert transfer_ffill == expected_ffill assert transfer_ffill == expected_ffill
transfer_tx = Transaction.from_dict(transfer_tx) transfer_tx = Transaction.from_dict(transfer_tx)
assert transfer_tx.fulfillments_valid([tx.conditions[0]]) is True assert transfer_tx.inputs_valid([tx.outputs[0]]) is True
validate_transaction_model(transfer_tx) validate_transaction_model(transfer_tx)
def test_create_transfer_transaction_multiple_io(user_pub, user_priv, def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
user2_pub, user2_priv, user2_pub, user2_priv,
user3_pub, user2_cond, user3_pub, user2_output,
asset_definition): asset_definition):
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
@ -887,26 +879,26 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
tx = tx.sign([user_priv]) tx = tx.sign([user_priv])
expected = { expected = {
'conditions': [user2_cond.to_dict(), user2_cond.to_dict()], 'outputs': [user2_output.to_dict(), user2_output.to_dict()],
'metadata': None, 'metadata': None,
'fulfillments': [ 'inputs': [
{ {
'owners_before': [ 'owners_before': [
user_pub user_pub
], ],
'fulfillment': None, 'fulfillment': None,
'input': { 'fulfills': {
'txid': tx.id, 'txid': tx.id,
'cid': 0 'output': 0
} }
}, { }, {
'owners_before': [ 'owners_before': [
user2_pub user2_pub
], ],
'fulfillment': None, 'fulfillment': None,
'input': { 'fulfills': {
'txid': tx.id, 'txid': tx.id,
'cid': 1 'output': 1
} }
} }
], ],
@ -919,14 +911,14 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
asset_id=tx.id) asset_id=tx.id)
transfer_tx = transfer_tx.sign([user_priv, user2_priv]) transfer_tx = transfer_tx.sign([user_priv, user2_priv])
assert len(transfer_tx.fulfillments) == 2 assert len(transfer_tx.inputs) == 2
assert len(transfer_tx.conditions) == 2 assert len(transfer_tx.outputs) == 2
assert transfer_tx.fulfillments_valid(tx.conditions) is True assert transfer_tx.inputs_valid(tx.outputs) is True
transfer_tx = transfer_tx.to_dict() transfer_tx = transfer_tx.to_dict()
transfer_tx['fulfillments'][0]['fulfillment'] = None transfer_tx['inputs'][0]['fulfillment'] = None
transfer_tx['fulfillments'][1]['fulfillment'] = None transfer_tx['inputs'][1]['fulfillment'] = None
transfer_tx.pop('asset') transfer_tx.pop('asset')
transfer_tx.pop('id') transfer_tx.pop('id')
@ -956,17 +948,17 @@ def test_create_transfer_with_invalid_parameters(tx, user_pub):
['not a string']) ['not a string'])
def test_cant_add_empty_condition(): def test_cant_add_empty_output():
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
tx = Transaction(Transaction.CREATE, None) tx = Transaction(Transaction.CREATE, None)
with raises(TypeError): with raises(TypeError):
tx.add_condition(None) tx.add_output(None)
def test_cant_add_empty_fulfillment(): def test_cant_add_empty_input():
from bigchaindb.common.transaction import Transaction from bigchaindb.common.transaction import Transaction
tx = Transaction(Transaction.CREATE, None) tx = Transaction(Transaction.CREATE, None)
with raises(TypeError): with raises(TypeError):
tx.add_fulfillment(None) tx.add_input(None)

View File

@ -279,7 +279,7 @@ class TestBigchainApi(object):
assert len(block['block']['transactions']) == 1 assert len(block['block']['transactions']) == 1
assert block['block']['transactions'][0]['operation'] == 'GENESIS' assert block['block']['transactions'][0]['operation'] == 'GENESIS'
assert block['block']['transactions'][0]['fulfillments'][0]['input'] is None assert block['block']['transactions'][0]['inputs'][0]['fulfills'] is None
@pytest.mark.genesis @pytest.mark.genesis
def test_create_genesis_block_fails_if_table_not_empty(self, b): def test_create_genesis_block_fails_if_table_not_empty(self, b):
@ -532,15 +532,15 @@ class TestBigchainApi(object):
def test_non_create_input_not_found(self, b, user_pk): def test_non_create_input_not_found(self, b, user_pk):
from cryptoconditions import Ed25519Fulfillment from cryptoconditions import Ed25519Fulfillment
from bigchaindb.common.exceptions import TransactionDoesNotExist from bigchaindb.common.exceptions import TransactionDoesNotExist
from bigchaindb.common.transaction import Fulfillment, TransactionLink from bigchaindb.common.transaction import Input, TransactionLink
from bigchaindb.models import Transaction from bigchaindb.models import Transaction
from bigchaindb import Bigchain from bigchaindb import Bigchain
# Create a fulfillment for a non existing transaction # Create an input for a non existing transaction
fulfillment = Fulfillment(Ed25519Fulfillment(public_key=user_pk), input = Input(Ed25519Fulfillment(public_key=user_pk),
[user_pk], [user_pk],
TransactionLink('somethingsomething', 0)) TransactionLink('somethingsomething', 0))
tx = Transaction.transfer([fulfillment], [([user_pk], 1)], tx = Transaction.transfer([input], [([user_pk], 1)],
asset_id='mock_asset_link') asset_id='mock_asset_link')
with pytest.raises(TransactionDoesNotExist): with pytest.raises(TransactionDoesNotExist):
@ -563,16 +563,16 @@ class TestTransactionValidation(object):
def test_create_operation_with_inputs(self, b, user_pk, create_tx): def test_create_operation_with_inputs(self, b, user_pk, create_tx):
from bigchaindb.common.transaction import TransactionLink from bigchaindb.common.transaction import TransactionLink
# Manipulate fulfillment so that it has a `tx_input` defined even # Manipulate input so that it has a `fulfills` defined even
# though it shouldn't have one # though it shouldn't have one
create_tx.fulfillments[0].tx_input = TransactionLink('abc', 0) create_tx.inputs[0].fulfills = TransactionLink('abc', 0)
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
b.validate_transaction(create_tx) b.validate_transaction(create_tx)
assert excinfo.value.args[0] == 'A CREATE operation has no inputs' assert excinfo.value.args[0] == 'A CREATE operation has no inputs'
def test_transfer_operation_no_inputs(self, b, user_pk, def test_transfer_operation_no_inputs(self, b, user_pk,
signed_transfer_tx): signed_transfer_tx):
signed_transfer_tx.fulfillments[0].tx_input = None signed_transfer_tx.inputs[0].fulfills = None
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
b.validate_transaction(signed_transfer_tx) b.validate_transaction(signed_transfer_tx)
@ -582,7 +582,7 @@ class TestTransactionValidation(object):
from bigchaindb.common.exceptions import TransactionDoesNotExist from bigchaindb.common.exceptions import TransactionDoesNotExist
from bigchaindb.common.transaction import TransactionLink from bigchaindb.common.transaction import TransactionLink
signed_transfer_tx.fulfillments[0].tx_input = TransactionLink('c', 0) signed_transfer_tx.inputs[0].fulfills = TransactionLink('c', 0)
with pytest.raises(TransactionDoesNotExist): with pytest.raises(TransactionDoesNotExist):
b.validate_transaction(signed_transfer_tx) b.validate_transaction(signed_transfer_tx)
@ -598,7 +598,7 @@ class TestTransactionValidation(object):
tx = Transaction.create([pk], [([user_pk], 1)]) tx = Transaction.create([pk], [([user_pk], 1)])
tx.operation = 'TRANSFER' tx.operation = 'TRANSFER'
tx.asset = {'id': input_transaction.id} tx.asset = {'id': input_transaction.id}
tx.fulfillments[0].tx_input = input_tx tx.inputs[0].fulfills = input_tx
with pytest.raises(InvalidSignature): with pytest.raises(InvalidSignature):
b.validate_transaction(tx) b.validate_transaction(tx)
@ -781,8 +781,8 @@ class TestMultipleInputs(object):
# validate transaction # validate transaction
assert b.is_valid_transaction(tx) == tx assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 1 assert len(tx.inputs) == 1
assert len(tx.conditions) == 1 assert len(tx.outputs) == 1
def test_single_owner_before_multiple_owners_after_single_input(self, b, def test_single_owner_before_multiple_owners_after_single_input(self, b,
user_sk, user_sk,
@ -803,8 +803,8 @@ class TestMultipleInputs(object):
tx = tx.sign([user_sk]) tx = tx.sign([user_sk])
assert b.is_valid_transaction(tx) == tx assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 1 assert len(tx.inputs) == 1
assert len(tx.conditions) == 1 assert len(tx.outputs) == 1
@pytest.mark.usefixtures('inputs') @pytest.mark.usefixtures('inputs')
def test_multiple_owners_before_single_owner_after_single_input(self, b, def test_multiple_owners_before_single_owner_after_single_input(self, b,
@ -835,8 +835,8 @@ class TestMultipleInputs(object):
# validate transaction # validate transaction
assert b.is_valid_transaction(transfer_tx) == transfer_tx assert b.is_valid_transaction(transfer_tx) == transfer_tx
assert len(transfer_tx.fulfillments) == 1 assert len(transfer_tx.inputs) == 1
assert len(transfer_tx.conditions) == 1 assert len(transfer_tx.outputs) == 1
@pytest.mark.usefixtures('inputs') @pytest.mark.usefixtures('inputs')
def test_multiple_owners_before_multiple_owners_after_single_input(self, b, def test_multiple_owners_before_multiple_owners_after_single_input(self, b,
@ -868,8 +868,8 @@ class TestMultipleInputs(object):
tx = tx.sign([user_sk, user2_sk]) tx = tx.sign([user_sk, user2_sk])
assert b.is_valid_transaction(tx) == tx assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 1 assert len(tx.inputs) == 1
assert len(tx.conditions) == 1 assert len(tx.outputs) == 1
def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_pk): def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_pk):
from bigchaindb.common import crypto from bigchaindb.common import crypto
@ -1025,8 +1025,8 @@ class TestMultipleInputs(object):
# check spents # check spents
input_txid = owned_inputs_user1.txid input_txid = owned_inputs_user1.txid
input_cid = owned_inputs_user1.cid input_idx = owned_inputs_user1.output
spent_inputs_user1 = b.get_spent(input_txid, input_cid) spent_inputs_user1 = b.get_spent(input_txid, input_idx)
assert spent_inputs_user1 is None assert spent_inputs_user1 is None
# create a transaction and block # create a transaction and block
@ -1036,7 +1036,7 @@ class TestMultipleInputs(object):
block = b.create_block([tx]) block = b.create_block([tx])
b.write_block(block) b.write_block(block)
spent_inputs_user1 = b.get_spent(input_txid, input_cid) spent_inputs_user1 = b.get_spent(input_txid, input_idx)
assert spent_inputs_user1 == tx assert spent_inputs_user1 == tx
def test_get_spent_single_tx_single_output_invalid_block(self, b, def test_get_spent_single_tx_single_output_invalid_block(self, b,
@ -1062,8 +1062,8 @@ class TestMultipleInputs(object):
# check spents # check spents
input_txid = owned_inputs_user1.txid input_txid = owned_inputs_user1.txid
input_cid = owned_inputs_user1.cid input_idx = owned_inputs_user1.output
spent_inputs_user1 = b.get_spent(input_txid, input_cid) spent_inputs_user1 = b.get_spent(input_txid, input_idx)
assert spent_inputs_user1 is None assert spent_inputs_user1 is None
# create a transaction and block # create a transaction and block
@ -1078,7 +1078,7 @@ class TestMultipleInputs(object):
b.write_vote(vote) b.write_vote(vote)
# NOTE: I have no idea why this line is here # NOTE: I have no idea why this line is here
b.get_transaction(tx.id) b.get_transaction(tx.id)
spent_inputs_user1 = b.get_spent(input_txid, input_cid) spent_inputs_user1 = b.get_spent(input_txid, input_idx)
# Now there should be no spents (the block is invalid) # Now there should be no spents (the block is invalid)
assert spent_inputs_user1 is None assert spent_inputs_user1 is None
@ -1103,7 +1103,7 @@ class TestMultipleInputs(object):
# check spents # check spents
for input_tx in owned_inputs_user1: for input_tx in owned_inputs_user1:
assert b.get_spent(input_tx.txid, input_tx.cid) is None assert b.get_spent(input_tx.txid, input_tx.output) is None
# transfer the first 2 inputs # transfer the first 2 inputs
tx_transfer = Transaction.transfer(tx_create.to_inputs()[:2], tx_transfer = Transaction.transfer(tx_create.to_inputs()[:2],
@ -1115,12 +1115,12 @@ class TestMultipleInputs(object):
# check that used inputs are marked as spent # check that used inputs are marked as spent
for ffill in tx_create.to_inputs()[:2]: for ffill in tx_create.to_inputs()[:2]:
spent_tx = b.get_spent(ffill.tx_input.txid, ffill.tx_input.cid) spent_tx = b.get_spent(ffill.fulfills.txid, ffill.fulfills.output)
assert spent_tx == tx_transfer_signed assert spent_tx == tx_transfer_signed
# check if remaining transaction that was unspent is also perceived # check if remaining transaction that was unspent is also perceived
# spendable by BigchainDB # spendable by BigchainDB
assert b.get_spent(tx_create.to_inputs()[2].tx_input.txid, 2) is None assert b.get_spent(tx_create.to_inputs()[2].fulfills.txid, 2) is None
def test_get_spent_multiple_owners(self, b, user_sk, user_pk): def test_get_spent_multiple_owners(self, b, user_sk, user_pk):
from bigchaindb.common import crypto from bigchaindb.common import crypto
@ -1143,7 +1143,7 @@ class TestMultipleInputs(object):
# check spents # check spents
for input_tx in owned_inputs_user1: for input_tx in owned_inputs_user1:
assert b.get_spent(input_tx.txid, input_tx.cid) is None assert b.get_spent(input_tx.txid, input_tx.output) is None
# create a transaction # create a transaction
tx = Transaction.transfer(transactions[0].to_inputs(), tx = Transaction.transfer(transactions[0].to_inputs(),

View File

@ -154,7 +154,7 @@ def test_vote_accumulates_transactions(b):
validation = vote_obj.validate_tx(tx, 123, 1) validation = vote_obj.validate_tx(tx, 123, 1)
assert validation == (True, 123, 1) assert validation == (True, 123, 1)
tx.fulfillments[0].fulfillment.signature = None tx.inputs[0].fulfillment.signature = None
validation = vote_obj.validate_tx(tx, 456, 10) validation = vote_obj.validate_tx(tx, 456, 10)
assert validation == (False, 456, 10) assert validation == (False, 456, 10)

View File

@ -12,7 +12,7 @@ class TestTransactionModel(object):
tx.validate(b) tx.validate(b)
tx.operation = 'CREATE' tx.operation = 'CREATE'
tx.fulfillments = [] tx.inputs = []
with raises(ValueError): with raises(ValueError):
tx.validate(b) tx.validate(b)

View File

@ -37,8 +37,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['fulfillments'][0]['owners_before'][0] == user_pub assert res.json['inputs'][0]['owners_before'][0] == user_pub
assert res.json['conditions'][0]['owners_after'][0] == user_pub assert res.json['outputs'][0]['public_keys'][0] == user_pub
def test_post_create_transaction_with_invalid_id(b, client): def test_post_create_transaction_with_invalid_id(b, client):
@ -65,7 +65,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['fulfillments'][0]['fulfillment'] = 'cf:0:0' tx['inputs'][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
@ -139,8 +139,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['fulfillments'][0]['owners_before'][0] == user_pk assert res.json['inputs'][0]['owners_before'][0] == user_pk
assert res.json['conditions'][0]['owners_after'][0] == user_pub assert res.json['outputs'][0]['public_keys'][0] == user_pub
@pytest.mark.bdb @pytest.mark.bdb