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

View File

@ -79,7 +79,7 @@ class CyclicBlockchainError(Exception):
class TransactionNotInValidBlock(Exception):
"""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):

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -355,7 +355,7 @@ class Bigchain(object):
if cursor:
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.
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:
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:
The transaction (Transaction) that used the `txid` as an input else
`None`
"""
# checks if an input was already spent
# checks if the bigchain has any transaction with input {'txid': ..., 'cid': ...}
transactions = list(backend.query.get_spent(self.connection, txid, cid))
# checks if the bigchain has any transaction with input {'txid': ...,
# 'output': ...}
transactions = list(backend.query.get_spent(self.connection, txid, output))
# a transaction_id should have been spent at most one time
if transactions:
@ -403,7 +404,7 @@ class Bigchain(object):
owner (str): base58 encoded public key.
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
"""
@ -420,22 +421,22 @@ class Bigchain(object):
# NOTE: It's OK to not serialize the transaction here, as we do not
# 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
for index, cond in enumerate(tx['conditions']):
for index, output in enumerate(tx['outputs']):
# for simple signature conditions there are no subfulfillments
# check if the owner is in the condition `owners_after`
if len(cond['owners_after']) == 1:
if cond['condition']['details']['public_key'] == owner:
if len(output['public_keys']) == 1:
if output['condition']['details']['public_key'] == owner:
tx_link = TransactionLink(tx['id'], index)
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
# 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)
# 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)
return owned

View File

@ -33,14 +33,14 @@ class Transaction(Transaction):
InvalidHash: if the hash of the transaction is wrong
InvalidSignature: if the signature of the transaction is wrong
"""
if len(self.fulfillments) == 0:
raise ValueError('Transaction contains no fulfillments')
if len(self.inputs) == 0:
raise ValueError('Transaction contains no inputs')
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
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')
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
input_txs = []
for ffill in self.fulfillments:
input_txid = ffill.tx_input.txid
input_cid = ffill.tx_input.cid
for input_ in self.inputs:
input_txid = input_.fulfills.txid
input_tx, status = bigchain.\
get_transaction(input_txid, include_status=True)
@ -78,15 +77,16 @@ class Transaction(Transaction):
'input `{}` does not exist in a valid block'.format(
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:
raise DoubleSpend('input `{}` was already spent'
.format(input_txid))
input_condition = input_tx.conditions[input_cid]
input_conditions.append(input_condition)
output = input_tx.outputs[input_.fulfills.output]
input_conditions.append(output)
input_txs.append(input_tx)
if output.amount < 1:
raise AmountError('`amount` needs to be greater than zero')
# validate asset id
asset_id = Transaction.get_asset_id(input_txs)
@ -95,8 +95,13 @@ class Transaction(Transaction):
' match the asset id of the'
' 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])
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:
raise AmountError(('The amount used in the inputs `{}`'
@ -109,7 +114,7 @@ class Transaction(Transaction):
raise TypeError('`operation`: `{}` must be either {}.'
.format(self.operation, allowed_operations))
if not self.fulfillments_valid(input_conditions):
if not self.inputs_valid(input_conditions):
raise InvalidSignature()
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.
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.
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.
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).

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
by transfer transactions (described more below).
A CREATE transaction also establishes the conditions that must be met to
transfer the asset(s). For example, there may be a condition that any transfer
must be signed (cryptographically) by the private key associated with a
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/).
A CREATE transaction also establishes, in its outputs, the conditions that must
be met to transfer the asset(s). The conditions may also be associated with a
list of public keys that, depending on the condition, may have full or partial
control over the asset(s). For example, there may be a condition that any
transfer must be signed (cryptographically) by the private key associated with a
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
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.
**Example 1:** Suppose a red car is owned and controlled by Joe.
Suppose the current transfer condition on the car says
that any valid transfer must be signed by Joe.
Joe and a buyer named Rae could build a TRANSFER transaction containing
Joe's signature (to fulfill the current transfer condition)
plus a new transfer condition saying that any valid transfer
an input with Joe's signature (to fulfill the current output condition)
plus a new output condition saying that any valid transfer
must be signed by Rae.
**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
e.g. paperclips. The amounts might be 20, 10, 45 and 25, say,
for a total of 100 paperclips.

View File

@ -57,9 +57,9 @@ Transaction Schema
* `Transaction`_
* Condition_
* Input_
* Fulfillment_
* Output_
* Asset_
@ -71,15 +71,15 @@ Transaction
%(transaction)s
Condition
----------
Input
-----
%(condition)s
%(input)s
Fulfillment
-----------
Output
------
%(fulfillment)s
%(output)s
Asset
-----
@ -99,8 +99,8 @@ def generate_transaction_docs():
doc = TPL_TRANSACTION % {
'transaction': render_section('Transaction', schema),
'condition': render_section('Condition', defs['condition']),
'fulfillment': render_section('Fulfillment', defs['fulfillment']),
'output': render_section('Output', defs['output']),
'input': render_section('Input', defs['input']),
'asset': render_section('Asset', defs['asset']),
'metadata': render_section('Metadata', defs['metadata']['anyOf'][0]),
'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:
1. Transactions, which contain digital assets, conditions, fulfillments and other things
1. Transactions, which contain digital assets, inputs, outputs, and other things
2. Blocks
3. Votes
@ -14,6 +14,6 @@ This section unpacks each one in turn.
transaction-model
asset-model
crypto-conditions
inputs-outputs
block-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)>",
"version": "<version number of the transaction model>",
"fulfillments": ["<list of fulfillments>"],
"conditions": ["<list of conditions>"],
"inputs": ["<list of inputs>"],
"outputs": ["<list of outputs>"],
"operation": "<string>",
"asset": "<digital asset description (explained in the next section)>",
"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.
- 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
and a *crypto fulfillment* that satisfies a spending condition set on the unspent asset. A *fulfillment*
- **inputs**: List of inputs. Each :ref:`input <Input>` contains a pointer to an unspent output
and a *crypto fulfillment* that satisfies the conditions of that output. A *fulfillment*
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.
See :doc:`./crypto-conditions`.
- **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:`./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.
@ -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`.
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}
Get a list of links to transactions' conditions that have not been used in
a previous transaction and could hence be called unspent conditions/outputs
Get a list of links to transactions' outputs that have not been used in
a previous transaction and could hence be called unspent outputs
(or simply: unspents).
This endpoint will return a ``HTTP 400 Bad Request`` if the querystring
``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
code and an empty list in the response's body.
@ -162,8 +162,8 @@ GET /unspents/
Content-Type: application/json
[
"../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/0",
"../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/1"
"../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/outputs/0",
"../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/outputs/1"
]
: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
tx = Transaction.create([user_pk], [([user_pk], 1)])
tx.conditions[0].amount = 0
tx.outputs[0].amount = 0
tx.sign([user_sk])
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])
assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 1
assert tx_signed.conditions[0].amount == 100
assert len(tx_signed.fulfillments) == 1
assert len(tx_signed.outputs) == 1
assert tx_signed.outputs[0].amount == 100
assert len(tx_signed.inputs) == 1
# 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])
assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 2
assert tx_signed.conditions[0].amount == 50
assert tx_signed.conditions[1].amount == 50
assert len(tx_signed.fulfillments) == 1
assert len(tx_signed.outputs) == 2
assert tx_signed.outputs[0].amount == 50
assert tx_signed.outputs[1].amount == 50
assert len(tx_signed.inputs) == 1
# 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])
assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 1
assert tx_signed.conditions[0].amount == 100
assert len(tx_signed.outputs) == 1
assert tx_signed.outputs[0].amount == 100
condition = tx_signed.conditions[0].to_dict()
assert 'subfulfillments' in condition['condition']['details']
assert len(condition['condition']['details']['subfulfillments']) == 2
output = tx_signed.outputs[0].to_dict()
assert 'subfulfillments' in output['condition']['details']
assert len(output['condition']['details']['subfulfillments']) == 2
assert len(tx_signed.fulfillments) == 1
assert len(tx_signed.inputs) == 1
# 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])
assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 2
assert tx_signed.conditions[0].amount == 50
assert tx_signed.conditions[1].amount == 50
assert len(tx_signed.outputs) == 2
assert tx_signed.outputs[0].amount == 50
assert tx_signed.outputs[1].amount == 50
condition_cid1 = tx_signed.conditions[1].to_dict()
assert 'subfulfillments' in condition_cid1['condition']['details']
assert len(condition_cid1['condition']['details']['subfulfillments']) == 2
output_cid1 = tx_signed.outputs[1].to_dict()
assert 'subfulfillments' in output_cid1['condition']['details']
assert len(output_cid1['condition']['details']['subfulfillments']) == 2
assert len(tx_signed.fulfillments) == 1
assert len(tx_signed.inputs) == 1
# 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_signed = tx.sign([b.me_private, user_sk])
assert tx_signed.validate(b) == tx_signed
assert len(tx_signed.conditions) == 1
assert tx_signed.conditions[0].amount == 100
assert len(tx_signed.fulfillments) == 1
assert len(tx_signed.outputs) == 1
assert tx_signed.outputs[0].amount == 100
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 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])
assert tx_transfer_signed.validate(b)
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 1
assert len(tx_transfer_signed.outputs) == 1
assert tx_transfer_signed.outputs[0].amount == 100
assert len(tx_transfer_signed.inputs) == 1
# 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])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 2
assert tx_transfer_signed.conditions[0].amount == 50
assert tx_transfer_signed.conditions[1].amount == 50
assert len(tx_transfer_signed.fulfillments) == 1
assert len(tx_transfer_signed.outputs) == 2
assert tx_transfer_signed.outputs[0].amount == 50
assert tx_transfer_signed.outputs[1].amount == 50
assert len(tx_transfer_signed.inputs) == 1
# 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])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 100
assert len(tx_transfer_signed.outputs) == 1
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 len(condition['condition']['details']['subfulfillments']) == 2
assert len(tx_transfer_signed.fulfillments) == 1
assert len(tx_transfer_signed.inputs) == 1
# 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])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 2
assert tx_transfer_signed.conditions[0].amount == 50
assert tx_transfer_signed.conditions[1].amount == 50
assert len(tx_transfer_signed.outputs) == 2
assert tx_transfer_signed.outputs[0].amount == 50
assert tx_transfer_signed.outputs[1].amount == 50
condition_cid1 = tx_transfer_signed.conditions[1].to_dict()
assert 'subfulfillments' in condition_cid1['condition']['details']
assert len(condition_cid1['condition']['details']['subfulfillments']) == 2
output_cid1 = tx_transfer_signed.outputs[1].to_dict()
assert 'subfulfillments' in output_cid1['condition']['details']
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
@ -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])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 1
assert len(tx_transfer_signed.outputs) == 1
assert tx_transfer_signed.outputs[0].amount == 100
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 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])
assert tx_transfer_signed.validate(b)
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 2
assert len(tx_transfer_signed.outputs) == 1
assert tx_transfer_signed.outputs[0].amount == 100
assert len(tx_transfer_signed.inputs) == 2
# 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])
assert tx_transfer_signed.validate(b)
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 2
assert len(tx_transfer_signed.outputs) == 1
assert tx_transfer_signed.outputs[0].amount == 100
assert len(tx_transfer_signed.inputs) == 2
ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict()
ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict()
ffill_fid0 = tx_transfer_signed.inputs[0].fulfillment.to_dict()
ffill_fid1 = tx_transfer_signed.inputs[1].fulfillment.to_dict()
assert 'subfulfillments' in ffill_fid0
assert 'subfulfillments' in ffill_fid1
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])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 100
assert len(tx_transfer_signed.fulfillments) == 2
assert len(tx_transfer_signed.outputs) == 1
assert tx_transfer_signed.outputs[0].amount == 100
assert len(tx_transfer_signed.inputs) == 2
ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict()
ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict()
ffill_fid0 = tx_transfer_signed.inputs[0].fulfillment.to_dict()
ffill_fid1 = tx_transfer_signed.inputs[1].fulfillment.to_dict()
assert 'subfulfillments' not in ffill_fid0
assert 'subfulfillments' in ffill_fid1
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])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 2
assert tx_transfer_signed.conditions[0].amount == 50
assert tx_transfer_signed.conditions[1].amount == 50
assert len(tx_transfer_signed.fulfillments) == 2
assert len(tx_transfer_signed.outputs) == 2
assert tx_transfer_signed.outputs[0].amount == 50
assert tx_transfer_signed.outputs[1].amount == 50
assert len(tx_transfer_signed.inputs) == 2
cond_cid0 = tx_transfer_signed.conditions[0].to_dict()
cond_cid1 = tx_transfer_signed.conditions[1].to_dict()
cond_cid0 = tx_transfer_signed.outputs[0].to_dict()
cond_cid1 = tx_transfer_signed.outputs[1].to_dict()
assert 'subfulfillments' not in cond_cid0['condition']['details']
assert 'subfulfillments' in cond_cid1['condition']['details']
assert len(cond_cid1['condition']['details']['subfulfillments']) == 2
ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict()
ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict()
ffill_fid0 = tx_transfer_signed.inputs[0].fulfillment.to_dict()
ffill_fid1 = tx_transfer_signed.inputs[1].fulfillment.to_dict()
assert 'subfulfillments' not in ffill_fid0
assert 'subfulfillments' in ffill_fid1
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])
assert tx_transfer2_signed.validate(b) == tx_transfer2_signed
assert len(tx_transfer2_signed.conditions) == 1
assert tx_transfer2_signed.conditions[0].amount == 100
assert len(tx_transfer2_signed.fulfillments) == 2
assert len(tx_transfer2_signed.outputs) == 1
assert tx_transfer2_signed.outputs[0].amount == 100
assert len(tx_transfer2_signed.inputs) == 2
fid0_input = tx_transfer2_signed.fulfillments[0].to_dict()['input']['txid']
fid1_input = tx_transfer2_signed.fulfillments[1].to_dict()['input']['txid']
fid0_input = tx_transfer2_signed.inputs[0].fulfills.txid
fid1_input = tx_transfer2_signed.inputs[1].fulfills.txid
assert fid0_input == tx_create.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])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 1
assert tx_transfer_signed.conditions[0].amount == 3
assert len(tx_transfer_signed.outputs) == 1
assert tx_transfer_signed.outputs[0].amount == 3
@pytest.mark.bdb
@ -632,9 +632,9 @@ def test_divide(b, user_pk, user_sk):
tx_transfer_signed = tx_transfer.sign([user_sk])
assert tx_transfer_signed.validate(b) == tx_transfer_signed
assert len(tx_transfer_signed.conditions) == 3
for condition in tx_transfer_signed.conditions:
assert condition.amount == 1
assert len(tx_transfer_signed.outputs) == 3
for output in tx_transfer_signed.outputs:
assert output.amount == 1
# 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(),
[([b.me], 4), ([b.me], 1)],
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])
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
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])
with pytest.raises(AmountError):

View File

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

View File

@ -1,111 +1,110 @@
from pytest import raises
def test_fulfillment_serialization(ffill_uri, user_pub):
from bigchaindb.common.transaction import Fulfillment
from cryptoconditions import Fulfillment as CCFulfillment
def test_input_serialization(ffill_uri, user_pub):
from bigchaindb.common.transaction import Input
from cryptoconditions import Fulfillment
expected = {
'owners_before': [user_pub],
'fulfillment': ffill_uri,
'input': None,
'fulfills': None,
}
ffill = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub])
assert ffill.to_dict() == expected
input = Input(Fulfillment.from_uri(ffill_uri), [user_pub])
assert input.to_dict() == expected
def test_fulfillment_deserialization_with_uri(ffill_uri, user_pub):
from bigchaindb.common.transaction import Fulfillment
from cryptoconditions import Fulfillment as CCFulfillment
def test_input_deserialization_with_uri(ffill_uri, user_pub):
from bigchaindb.common.transaction import Input
from cryptoconditions import Fulfillment
expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub])
expected = Input(Fulfillment.from_uri(ffill_uri), [user_pub])
ffill = {
'owners_before': [user_pub],
'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):
from bigchaindb.common.transaction import Fulfillment
def test_input_deserialization_with_invalid_input(user_pub):
from bigchaindb.common.transaction import Input
ffill = {
'owners_before': [user_pub],
'fulfillment': None,
'input': None,
'fulfills': None,
}
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.transaction import Fulfillment
from bigchaindb.common.transaction import Input
ffill = {
'owners_before': [user_pub],
'fulfillment': 'an invalid fulfillment',
'input': None,
'fulfills': None,
}
with raises(InvalidSignature):
Fulfillment.from_dict(ffill)
Input.from_dict(ffill)
def test_fulfillment_deserialization_with_unsigned_fulfillment(ffill_uri,
user_pub):
from bigchaindb.common.transaction import Fulfillment
from cryptoconditions import Fulfillment as CCFulfillment
def test_input_deserialization_with_unsigned_fulfillment(ffill_uri, user_pub):
from bigchaindb.common.transaction import Input
from cryptoconditions import Fulfillment
expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub])
expected = Input(Fulfillment.from_uri(ffill_uri), [user_pub])
ffill = {
'owners_before': [user_pub],
'fulfillment': CCFulfillment.from_uri(ffill_uri),
'input': None,
'fulfillment': Fulfillment.from_uri(ffill_uri),
'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):
from bigchaindb.common.transaction import Condition
def test_output_serialization(user_Ed25519, user_pub):
from bigchaindb.common.transaction import Output
expected = {
'condition': {
'uri': user_Ed25519.condition_uri,
'details': user_Ed25519.to_dict(),
},
'owners_after': [user_pub],
'public_keys': [user_pub],
'amount': 1,
}
cond = Condition(user_Ed25519, [user_pub], 1)
cond = Output(user_Ed25519, [user_pub], 1)
assert cond.to_dict() == expected
def test_condition_deserialization(user_Ed25519, user_pub):
from bigchaindb.common.transaction import Condition
def test_output_deserialization(user_Ed25519, user_pub):
from bigchaindb.common.transaction import Output
expected = Condition(user_Ed25519, [user_pub], 1)
expected = Output(user_Ed25519, [user_pub], 1)
cond = {
'condition': {
'uri': user_Ed25519.condition_uri,
'details': user_Ed25519.to_dict()
},
'owners_after': [user_pub],
'public_keys': [user_pub],
'amount': 1,
}
cond = Condition.from_dict(cond)
cond = Output.from_dict(cond)
assert cond == expected
def test_condition_hashlock_serialization():
from bigchaindb.common.transaction import Condition
def test_output_hashlock_serialization():
from bigchaindb.common.transaction import Output
from cryptoconditions import PreimageSha256Fulfillment
secret = b'wow much secret'
@ -115,49 +114,49 @@ def test_condition_hashlock_serialization():
'condition': {
'uri': hashlock,
},
'owners_after': None,
'public_keys': None,
'amount': 1,
}
cond = Condition(hashlock, amount=1)
cond = Output(hashlock, amount=1)
assert cond.to_dict() == expected
def test_condition_hashlock_deserialization():
from bigchaindb.common.transaction import Condition
def test_output_hashlock_deserialization():
from bigchaindb.common.transaction import Output
from cryptoconditions import PreimageSha256Fulfillment
secret = b'wow much secret'
hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri
expected = Condition(hashlock, amount=1)
expected = Output(hashlock, amount=1)
cond = {
'condition': {
'uri': hashlock
},
'owners_after': None,
'public_keys': None,
'amount': 1,
}
cond = Condition.from_dict(cond)
cond = Output.from_dict(cond)
assert cond == expected
def test_invalid_condition_initialization(cond_uri, user_pub):
from bigchaindb.common.transaction import Condition
def test_invalid_output_initialization(cond_uri, user_pub):
from bigchaindb.common.transaction import Output
from bigchaindb.common.exceptions import AmountError
with raises(TypeError):
Condition(cond_uri, user_pub)
Output(cond_uri, user_pub)
with raises(TypeError):
Condition(cond_uri, [user_pub], 'amount')
Output(cond_uri, [user_pub], 'amount')
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):
from bigchaindb.common.transaction import Condition
from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment
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.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()
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):
from bigchaindb.common.transaction import Condition
from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment
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_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()
def test_generate_conditions_flat_ownage(user_pub, user2_pub, user3_pub):
from bigchaindb.common.transaction import Condition
def test_generate_outputs_flat_ownage(user_pub, user2_pub, user3_pub):
from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment
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_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()
def test_generate_conditions_single_owner(user_pub):
from bigchaindb.common.transaction import Condition
def test_generate_output_single_owner(user_pub):
from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Fulfillment
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()
def test_generate_conditions_single_owner_with_condition(user_pub):
from bigchaindb.common.transaction import Condition
def test_generate_output_single_owner_with_output(user_pub):
from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Fulfillment
expected = Ed25519Fulfillment(public_key=user_pub)
cond = Condition.generate([expected], 1)
cond = Output.generate([expected], 1)
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):
from bigchaindb.common.transaction import Condition
from bigchaindb.common.transaction import Output
with raises(ValueError):
Condition.generate([], 1)
Output.generate([], 1)
with raises(TypeError):
Condition.generate('not a list', 1)
Output.generate('not a list', 1)
with raises(ValueError):
Condition.generate([[user_pub, [user2_pub, [user3_pub]]]], 1)
Output.generate([[user_pub, [user2_pub, [user3_pub]]]], 1)
with raises(ValueError):
Condition.generate([[user_pub]], 1)
Output.generate([[user_pub]], 1)
def test_invalid_transaction_initialization(asset_definition):
@ -259,21 +258,21 @@ def test_invalid_transaction_initialization(asset_definition):
Transaction(
operation='CREATE',
asset=asset_definition,
conditions='invalid conditions'
outputs='invalid outputs'
)
with raises(TypeError):
Transaction(
operation='CREATE',
asset=asset_definition,
conditions=[],
fulfillments='invalid fulfillments'
outputs=[],
inputs='invalid inputs'
)
with raises(TypeError):
Transaction(
operation='CREATE',
asset=asset_definition,
conditions=[],
fulfillments=[],
outputs=[],
inputs=[],
metadata='invalid metadata'
)
@ -288,18 +287,19 @@ def test_create_default_asset_on_tx_initialization(asset_definition):
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 .util import validate_transaction_model
tx_id = 'l0l'
expected = {
'id': tx_id,
'version': Transaction.VERSION,
# NOTE: This test assumes that Fulfillments and Conditions can
# NOTE: This test assumes that Inputs and Outputs can
# successfully be serialized
'fulfillments': [user_ffill.to_dict()],
'conditions': [user_cond.to_dict()],
'inputs': [user_input.to_dict()],
'outputs': [user_output.to_dict()],
'operation': Transaction.CREATE,
'metadata': None,
'asset': {
@ -307,28 +307,28 @@ def test_transaction_serialization(user_ffill, user_cond, data):
}
}
tx = Transaction(Transaction.CREATE, {'data': data}, [user_ffill],
[user_cond])
tx = Transaction(Transaction.CREATE, {'data': data}, [user_input],
[user_output])
tx_dict = tx.to_dict()
tx_dict['id'] = tx_id
assert tx_dict == expected
def test_transaction_deserialization(user_ffill, user_cond, data):
def test_transaction_deserialization(user_input, user_output, data):
from bigchaindb.common.transaction import Transaction
from .util import validate_transaction_model
expected_asset = {'data': data}
expected = Transaction(Transaction.CREATE, expected_asset, [user_ffill],
[user_cond], None, Transaction.VERSION)
expected = Transaction(Transaction.CREATE, expected_asset, [user_input],
[user_output], None, Transaction.VERSION)
tx = {
'version': Transaction.VERSION,
# NOTE: This test assumes that Fulfillments and Conditions can
# NOTE: This test assumes that Inputs and Outputs can
# successfully be serialized
'fulfillments': [user_ffill.to_dict()],
'conditions': [user_cond.to_dict()],
'inputs': [user_input.to_dict()],
'outputs': [user_output.to_dict()],
'operation': Transaction.CREATE,
'metadata': None,
'asset': {
@ -355,13 +355,13 @@ def test_tx_serialization_with_incorrect_hash(utx):
utx_dict.pop('id')
def test_invalid_fulfillment_initialization(user_ffill, user_pub):
from bigchaindb.common.transaction import Fulfillment
def test_invalid_input_initialization(user_input, user_pub):
from bigchaindb.common.transaction import Input
with raises(TypeError):
Fulfillment(user_ffill, user_pub)
Input(user_input, user_pub)
with raises(TypeError):
Fulfillment(user_ffill, [], tx_input='somethingthatiswrong')
Input(user_input, tx_input='somethingthatiswrong')
def test_transaction_link_serialization():
@ -370,7 +370,7 @@ def test_transaction_link_serialization():
tx_id = 'a transaction id'
expected = {
'txid': tx_id,
'cid': 0,
'output': 0,
}
tx_link = TransactionLink(tx_id, 0)
@ -393,7 +393,7 @@ def test_transaction_link_deserialization():
expected = TransactionLink(tx_id, 0)
tx_link = {
'txid': tx_id,
'cid': 0,
'output': 0,
}
tx_link = TransactionLink.from_dict(tx_link)
@ -421,7 +421,7 @@ def test_transaction_link_empty_to_uri():
def test_transaction_link_to_uri():
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')
assert expected == tx_link
@ -437,41 +437,41 @@ def test_cast_transaction_link_to_boolean():
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
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
tx = Transaction(Transaction.CREATE, asset_definition)
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 .util import validate_transaction_model
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)
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
tx = Transaction(Transaction.CREATE, asset_definition, [], [])
with raises(TypeError):
tx.add_condition('somewronginput')
tx.add_output('somewronginput')
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)
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):
from copy import deepcopy
from bigchaindb.common.crypto import PrivateKey
from bigchaindb.common.transaction import Transaction
from .util import validate_transaction_model
tx = Transaction(Transaction.CREATE, asset_definition, [user_ffill], [user_cond])
expected = deepcopy(user_cond)
tx = Transaction(Transaction.CREATE, asset_definition, [user_input], [user_output])
expected = deepcopy(user_output)
expected.fulfillment.sign(str(tx).encode(), PrivateKey(user_priv))
tx.sign([user_priv])
assert tx.fulfillments[0].to_dict()['fulfillment'] == \
assert tx.inputs[0].to_dict()['fulfillment'] == \
expected.fulfillment.serialize_uri()
assert tx.fulfillments_valid() is True
assert tx.inputs_valid() is True
validate_transaction_model(tx)
def test_invoke_simple_signature_fulfillment_with_invalid_params(utx,
user_ffill):
user_input):
from bigchaindb.common.exceptions import KeypairMismatchException
with raises(KeypairMismatchException):
invalid_key_pair = {'wrong_pub_key': 'wrong_priv_key'}
utx._sign_simple_signature_fulfillment(user_ffill,
utx._sign_simple_signature_fulfillment(user_input,
0,
'somemessage',
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):
from bigchaindb.common.exceptions import KeypairMismatchException
with raises(KeypairMismatchException):
utx._sign_threshold_signature_fulfillment(user_user2_threshold_ffill,
utx._sign_threshold_signature_fulfillment(user_user2_threshold_input,
0,
'somemessage',
{user3_pub: user3_priv})
with raises(KeypairMismatchException):
user_user2_threshold_ffill.owners_before = ['somewrongvalue']
utx._sign_threshold_signature_fulfillment(user_user2_threshold_ffill,
user_user2_threshold_input.owners_before = ['somewrongvalue']
utx._sign_threshold_signature_fulfillment(user_user2_threshold_input,
0,
'somemessage',
None)
def test_validate_fulfillment_with_invalid_parameters(utx):
def test_validate_input_with_invalid_parameters(utx):
from bigchaindb.common.transaction import Transaction
input_conditions = [cond.fulfillment.condition_uri for cond
in utx.conditions]
input_conditions = [out.fulfillment.condition_uri for out in utx.outputs]
tx_dict = utx.to_dict()
tx_dict = Transaction._remove_signatures(tx_dict)
tx_serialized = Transaction._to_str(tx_dict)
assert utx._fulfillment_valid(utx.fulfillments[0],
tx_serialized,
input_conditions) is False
valid = utx._input_valid(utx.inputs[0], tx_serialized, input_conditions)
assert not valid
def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv,
asset_definition):
def test_validate_multiple_inputs(user_input, user_output, user_priv,
asset_definition):
from copy import deepcopy
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
tx = Transaction(Transaction.CREATE, asset_definition,
[user_ffill, deepcopy(user_ffill)],
[user_cond, deepcopy(user_cond)])
[user_input, deepcopy(user_input)],
[user_output, deepcopy(user_output)])
expected_first = deepcopy(tx)
expected_second = deepcopy(tx)
expected_first.fulfillments = [expected_first.fulfillments[0]]
expected_second.fulfillments = [expected_second.fulfillments[1]]
expected_first.inputs = [expected_first.inputs[0]]
expected_second.inputs = [expected_second.inputs[1]]
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))
expected_second_bytes = str(expected_second).encode()
expected_second.fulfillments[0].fulfillment.sign(expected_second_bytes,
PrivateKey(user_priv))
expected_second.inputs[0].fulfillment.sign(expected_second_bytes,
PrivateKey(user_priv))
tx.sign([user_priv])
assert tx.fulfillments[0].to_dict()['fulfillment'] == \
expected_first.fulfillments[0].fulfillment.serialize_uri()
assert tx.fulfillments[1].to_dict()['fulfillment'] == \
expected_second.fulfillments[0].fulfillment.serialize_uri()
assert tx.fulfillments_valid() is True
assert tx.inputs[0].to_dict()['fulfillment'] == \
expected_first.inputs[0].fulfillment.serialize_uri()
assert tx.inputs[1].to_dict()['fulfillment'] == \
expected_second.inputs[0].fulfillment.serialize_uri()
assert tx.inputs_valid() is True
validate_transaction_model(tx)
def test_validate_tx_threshold_create_signature(user_user2_threshold_ffill,
user_user2_threshold_cond,
def test_validate_tx_threshold_create_signature(user_user2_threshold_input,
user_user2_threshold_output,
user_pub,
user2_pub,
user_priv,
@ -590,96 +588,90 @@ def test_validate_tx_threshold_create_signature(user_user2_threshold_ffill,
from .util import validate_transaction_model
tx = Transaction(Transaction.CREATE, asset_definition,
[user_user2_threshold_ffill],
[user_user2_threshold_cond])
expected = deepcopy(user_user2_threshold_cond)
[user_user2_threshold_input],
[user_user2_threshold_output])
expected = deepcopy(user_user2_threshold_output)
expected.fulfillment.subconditions[0]['body'].sign(str(tx).encode(),
PrivateKey(user_priv))
expected.fulfillment.subconditions[1]['body'].sign(str(tx).encode(),
PrivateKey(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()
assert tx.fulfillments_valid() is True
assert tx.inputs_valid() is True
validate_transaction_model(tx)
def test_multiple_fulfillment_validation_of_transfer_tx(user_ffill, user_cond,
user_priv, user2_pub,
user2_priv, user3_pub,
user3_priv,
asset_definition):
def test_multiple_input_validation_of_transfer_tx(user_input, user_output,
user_priv, user2_pub,
user2_priv, user3_pub,
user3_priv,
asset_definition):
from copy import deepcopy
from bigchaindb.common.transaction import (Transaction, TransactionLink,
Fulfillment, Condition)
Input, Output)
from cryptoconditions import Ed25519Fulfillment
from .util import validate_transaction_model
tx = Transaction(Transaction.CREATE, asset_definition,
[user_ffill, deepcopy(user_ffill)],
[user_cond, deepcopy(user_cond)])
[user_input, deepcopy(user_input)],
[user_output, deepcopy(user_output)])
tx.sign([user_priv])
fulfillments = [Fulfillment(cond.fulfillment, cond.owners_after,
TransactionLink(tx.id, index))
for index, cond in enumerate(tx.conditions)]
conditions = [Condition(Ed25519Fulfillment(public_key=user3_pub),
[user3_pub]),
Condition(Ed25519Fulfillment(public_key=user3_pub),
[user3_pub])]
transfer_tx = Transaction('TRANSFER', {'id': tx.id},
fulfillments, conditions)
inputs = [Input(cond.fulfillment, cond.public_keys,
TransactionLink(tx.id, index))
for index, cond in enumerate(tx.outputs)]
outputs = [Output(Ed25519Fulfillment(public_key=user3_pub), [user3_pub]),
Output(Ed25519Fulfillment(public_key=user3_pub), [user3_pub])]
transfer_tx = Transaction('TRANSFER', {'id': tx.id}, inputs, outputs)
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)
def test_validate_fulfillments_of_transfer_tx_with_invalid_params(transfer_tx,
cond_uri,
utx,
user2_pub,
user_priv):
from bigchaindb.common.transaction import Condition
def test_validate_inputs_of_transfer_tx_with_invalid_params(
transfer_tx, cond_uri, utx, user2_pub, user_priv):
from bigchaindb.common.transaction import Output
from cryptoconditions import Ed25519Fulfillment
invalid_cond = Condition(Ed25519Fulfillment.from_uri('cf:0:'), ['invalid'])
assert transfer_tx.fulfillments_valid([invalid_cond]) is False
invalid_cond = utx.conditions[0]
invalid_cond.owners_after = 'invalid'
assert transfer_tx.fulfillments_valid([invalid_cond]) is True
invalid_out = Output(Ed25519Fulfillment.from_uri('cf:0:'), ['invalid'])
assert transfer_tx.inputs_valid([invalid_out]) is False
invalid_out = utx.outputs[0]
invalid_out.public_key = 'invalid'
assert transfer_tx.inputs_valid([invalid_out]) is True
with raises(TypeError):
assert transfer_tx.fulfillments_valid(None) is False
assert transfer_tx.inputs_valid(None) is False
with raises(AttributeError):
transfer_tx.fulfillments_valid('not a list')
transfer_tx.inputs_valid('not a list')
with raises(ValueError):
transfer_tx.fulfillments_valid([])
transfer_tx.inputs_valid([])
with raises(TypeError):
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 .util import validate_transaction_model
expected = {
'conditions': [user_cond.to_dict()],
'outputs': [user_output.to_dict()],
'metadata': data,
'asset': {
'data': data,
},
'fulfillments': [
'inputs': [
{
'owners_before': [
user_pub
],
'fulfillment': None,
'input': None
'fulfills': None
}
],
'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,
asset=data)
tx_dict = tx.to_dict()
tx_dict['fulfillments'][0]['fulfillment'] = None
tx_dict['inputs'][0]['fulfillment'] = None
tx_dict.pop('id')
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 = 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):
from bigchaindb.common.transaction import Transaction, Fulfillment
from bigchaindb.common.transaction import Transaction, Input
# a fulfillment for a create transaction with multiple `owners_before`
# is a fulfillment for an implicit threshold condition with
# weight = len(owners_before)
ffill = Fulfillment.generate([user_pub, user2_pub]).to_dict()
input = Input.generate([user_pub, user2_pub]).to_dict()
expected = {
'conditions': [user_cond.to_dict(), user2_cond.to_dict()],
'outputs': [user_output.to_dict(), user2_output.to_dict()],
'metadata': {
'message': 'hello'
},
'fulfillments': [ffill],
'inputs': [input],
'operation': 'CREATE',
'version': 1
}
@ -742,29 +734,29 @@ def test_validate_multiple_io_create_transaction(user_pub, user_priv,
[([user_pub], 1), ([user2_pub], 1)],
metadata={'message': 'hello'})
tx = tx.sign([user_priv, user2_priv])
assert tx.fulfillments_valid() is True
assert tx.inputs_valid() is True
validate_transaction_model(tx)
def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
user_user2_threshold_cond,
user_user2_threshold_ffill, data):
user_user2_threshold_output,
user_user2_threshold_input, data):
from bigchaindb.common.transaction import Transaction
expected = {
'conditions': [user_user2_threshold_cond.to_dict()],
'outputs': [user_user2_threshold_output.to_dict()],
'metadata': data,
'asset': {
'data': data,
},
'fulfillments': [
'inputs': [
{
'owners_before': [
user_pub,
],
'fulfillment': None,
'input': None
'fulfills': None,
},
],
'operation': 'CREATE',
@ -774,7 +766,7 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
metadata=data, asset=data)
tx_dict = tx.to_dict()
tx_dict.pop('id')
tx_dict['fulfillments'][0]['fulfillment'] = None
tx_dict['inputs'][0]['fulfillment'] = None
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)],
metadata=data)
tx = tx.sign([user_priv])
assert tx.fulfillments_valid() is True
assert tx.inputs_valid() is True
validate_transaction_model(tx)
@ -816,18 +808,18 @@ def test_create_create_transaction_with_invalid_parameters(user_pub):
asset='not a dict or none')
def test_conditions_to_inputs(tx):
ffills = tx.to_inputs([0])
assert len(ffills) == 1
ffill = ffills.pop()
assert ffill.fulfillment == tx.conditions[0].fulfillment
assert ffill.owners_before == tx.conditions[0].owners_after
assert ffill.tx_input.txid == tx.id
assert ffill.tx_input.cid == 0
def test_outputs_to_inputs(tx):
inputs = tx.to_inputs([0])
assert len(inputs) == 1
input = inputs.pop()
assert input.owners_before == tx.outputs[0].public_keys
assert input.fulfillment == tx.outputs[0].fulfillment
assert input.fulfills.txid == tx.id
assert input.fulfills.output == 0
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 bigchaindb.common.crypto import PrivateKey
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
expected = {
'conditions': [user2_cond.to_dict()],
'outputs': [user2_output.to_dict()],
'metadata': None,
'asset': {
'id': tx.id,
},
'fulfillments': [
'inputs': [
{
'owners_before': [
user_pub
],
'fulfillment': None,
'input': {
'fulfills': {
'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(),
PrivateKey(user_priv))
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
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)
def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
user2_pub, user2_priv,
user3_pub, user2_cond,
user3_pub, user2_output,
asset_definition):
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])
expected = {
'conditions': [user2_cond.to_dict(), user2_cond.to_dict()],
'outputs': [user2_output.to_dict(), user2_output.to_dict()],
'metadata': None,
'fulfillments': [
'inputs': [
{
'owners_before': [
user_pub
],
'fulfillment': None,
'input': {
'fulfills': {
'txid': tx.id,
'cid': 0
'output': 0
}
}, {
'owners_before': [
user2_pub
],
'fulfillment': None,
'input': {
'fulfills': {
'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)
transfer_tx = transfer_tx.sign([user_priv, user2_priv])
assert len(transfer_tx.fulfillments) == 2
assert len(transfer_tx.conditions) == 2
assert len(transfer_tx.inputs) == 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['fulfillments'][0]['fulfillment'] = None
transfer_tx['fulfillments'][1]['fulfillment'] = None
transfer_tx['inputs'][0]['fulfillment'] = None
transfer_tx['inputs'][1]['fulfillment'] = None
transfer_tx.pop('asset')
transfer_tx.pop('id')
@ -956,17 +948,17 @@ def test_create_transfer_with_invalid_parameters(tx, user_pub):
['not a string'])
def test_cant_add_empty_condition():
def test_cant_add_empty_output():
from bigchaindb.common.transaction import Transaction
tx = Transaction(Transaction.CREATE, None)
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
tx = Transaction(Transaction.CREATE, None)
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 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
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):
from cryptoconditions import Ed25519Fulfillment
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 import Bigchain
# Create a fulfillment for a non existing transaction
fulfillment = Fulfillment(Ed25519Fulfillment(public_key=user_pk),
[user_pk],
TransactionLink('somethingsomething', 0))
tx = Transaction.transfer([fulfillment], [([user_pk], 1)],
# Create an input for a non existing transaction
input = Input(Ed25519Fulfillment(public_key=user_pk),
[user_pk],
TransactionLink('somethingsomething', 0))
tx = Transaction.transfer([input], [([user_pk], 1)],
asset_id='mock_asset_link')
with pytest.raises(TransactionDoesNotExist):
@ -563,16 +563,16 @@ class TestTransactionValidation(object):
def test_create_operation_with_inputs(self, b, user_pk, create_tx):
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
create_tx.fulfillments[0].tx_input = TransactionLink('abc', 0)
create_tx.inputs[0].fulfills = TransactionLink('abc', 0)
with pytest.raises(ValueError) as excinfo:
b.validate_transaction(create_tx)
assert excinfo.value.args[0] == 'A CREATE operation has no inputs'
def test_transfer_operation_no_inputs(self, b, user_pk,
signed_transfer_tx):
signed_transfer_tx.fulfillments[0].tx_input = None
signed_transfer_tx.inputs[0].fulfills = None
with pytest.raises(ValueError) as excinfo:
b.validate_transaction(signed_transfer_tx)
@ -582,7 +582,7 @@ class TestTransactionValidation(object):
from bigchaindb.common.exceptions import TransactionDoesNotExist
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):
b.validate_transaction(signed_transfer_tx)
@ -598,7 +598,7 @@ class TestTransactionValidation(object):
tx = Transaction.create([pk], [([user_pk], 1)])
tx.operation = 'TRANSFER'
tx.asset = {'id': input_transaction.id}
tx.fulfillments[0].tx_input = input_tx
tx.inputs[0].fulfills = input_tx
with pytest.raises(InvalidSignature):
b.validate_transaction(tx)
@ -781,8 +781,8 @@ class TestMultipleInputs(object):
# validate transaction
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
assert len(tx.inputs) == 1
assert len(tx.outputs) == 1
def test_single_owner_before_multiple_owners_after_single_input(self, b,
user_sk,
@ -803,8 +803,8 @@ class TestMultipleInputs(object):
tx = tx.sign([user_sk])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
assert len(tx.inputs) == 1
assert len(tx.outputs) == 1
@pytest.mark.usefixtures('inputs')
def test_multiple_owners_before_single_owner_after_single_input(self, b,
@ -835,8 +835,8 @@ class TestMultipleInputs(object):
# validate transaction
assert b.is_valid_transaction(transfer_tx) == transfer_tx
assert len(transfer_tx.fulfillments) == 1
assert len(transfer_tx.conditions) == 1
assert len(transfer_tx.inputs) == 1
assert len(transfer_tx.outputs) == 1
@pytest.mark.usefixtures('inputs')
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])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
assert len(tx.inputs) == 1
assert len(tx.outputs) == 1
def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_pk):
from bigchaindb.common import crypto
@ -1025,8 +1025,8 @@ class TestMultipleInputs(object):
# check spents
input_txid = owned_inputs_user1.txid
input_cid = owned_inputs_user1.cid
spent_inputs_user1 = b.get_spent(input_txid, input_cid)
input_idx = owned_inputs_user1.output
spent_inputs_user1 = b.get_spent(input_txid, input_idx)
assert spent_inputs_user1 is None
# create a transaction and block
@ -1036,7 +1036,7 @@ class TestMultipleInputs(object):
block = b.create_block([tx])
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
def test_get_spent_single_tx_single_output_invalid_block(self, b,
@ -1062,8 +1062,8 @@ class TestMultipleInputs(object):
# check spents
input_txid = owned_inputs_user1.txid
input_cid = owned_inputs_user1.cid
spent_inputs_user1 = b.get_spent(input_txid, input_cid)
input_idx = owned_inputs_user1.output
spent_inputs_user1 = b.get_spent(input_txid, input_idx)
assert spent_inputs_user1 is None
# create a transaction and block
@ -1078,7 +1078,7 @@ class TestMultipleInputs(object):
b.write_vote(vote)
# NOTE: I have no idea why this line is here
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)
assert spent_inputs_user1 is None
@ -1103,7 +1103,7 @@ class TestMultipleInputs(object):
# check spents
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
tx_transfer = Transaction.transfer(tx_create.to_inputs()[:2],
@ -1115,12 +1115,12 @@ class TestMultipleInputs(object):
# check that used inputs are marked as spent
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
# check if remaining transaction that was unspent is also perceived
# 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):
from bigchaindb.common import crypto
@ -1143,7 +1143,7 @@ class TestMultipleInputs(object):
# check spents
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
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)
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)
assert validation == (False, 456, 10)

View File

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

View File

@ -37,8 +37,8 @@ def test_post_create_transaction_endpoint(b, client):
tx = tx.sign([user_priv])
res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict()))
assert res.json['fulfillments'][0]['owners_before'][0] == user_pub
assert res.json['conditions'][0]['owners_after'][0] == user_pub
assert res.json['inputs'][0]['owners_before'][0] == user_pub
assert res.json['outputs'][0]['public_keys'][0] == user_pub
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 = 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))
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()))
assert res.json['fulfillments'][0]['owners_before'][0] == user_pk
assert res.json['conditions'][0]['owners_after'][0] == user_pub
assert res.json['inputs'][0]['owners_before'][0] == user_pk
assert res.json['outputs'][0]['public_keys'][0] == user_pub
@pytest.mark.bdb