mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge pull request #925 from bigchaindb/inputs-outputs
Inputs & Outputs
This commit is contained in:
commit
5190e0a682
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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
@ -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
|
||||
|
@ -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
|
||||
|
@ -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; there’s 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; there’s 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).
|
||||
|
@ -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.
|
||||
|
@ -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',
|
||||
|
@ -1,126 +0,0 @@
|
||||
# Crypto-Conditions and Fulfillments
|
||||
|
||||
To create a transaction that transfers an asset to new owners, one must fulfill the asset’s 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 condition’s expiry time. After the expiry time, nobody can fulfill the condition. Another way to say this is that a timeout condition’s 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
|
@ -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
|
||||
|
129
docs/server/source/data-models/inputs-outputs.rst
Normal file
129
docs/server/source/data-models/inputs-outputs.rst
Normal 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 keys–it 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.
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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(),
|
||||
|
@ -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)
|
||||
|
||||
|
@ -12,7 +12,7 @@ class TestTransactionModel(object):
|
||||
tx.validate(b)
|
||||
|
||||
tx.operation = 'CREATE'
|
||||
tx.fulfillments = []
|
||||
tx.inputs = []
|
||||
with raises(ValueError):
|
||||
tx.validate(b)
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user