mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge remote-tracking branch 'origin/master' into feat/746/new-naming-convention-for-keys
This commit is contained in:
commit
eead7dbdda
@ -82,7 +82,7 @@ How? Let's split the command down into its components:
|
||||
- `install` tells pip to use the *install* action
|
||||
- `-e` installs a project in [editable mode](https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs)
|
||||
- `.` installs what's in the current directory
|
||||
- `[dev]` adds some [extra requirements](https://pythonhosted.org/setuptools/setuptools.html#declaring-extras-optional-features-with-their-own-dependencies) to the installation. (If you are curious, open `setup.py` and look for `dev` in the `extras_require` section.)
|
||||
- `[dev]` adds some [extra requirements](https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-extras-optional-features-with-their-own-dependencies) to the installation. (If you are curious, open `setup.py` and look for `dev` in the `extras_require` section.)
|
||||
|
||||
Aside: An alternative to `pip install -e .[dev]` is `python setup.py develop`.
|
||||
|
||||
|
@ -65,6 +65,14 @@ x = 'name: {}; score: {}'.format(name, n)
|
||||
we use the `format()` version. The [official Python documentation says](https://docs.python.org/2/library/stdtypes.html#str.format), "This method of string formatting is the new standard in Python 3, and should be preferred to the % formatting described in String Formatting Operations in new code."
|
||||
|
||||
|
||||
## Runnng the Flake8 Style Checker
|
||||
|
||||
We use [Flake8](http://flake8.pycqa.org/en/latest/index.html) to check our Python code style. Once you have it installed, you can run it using:
|
||||
```text
|
||||
flake8 --max-line-length 119 bigchaindb/
|
||||
```
|
||||
|
||||
|
||||
## Writing and Running (Python) Unit Tests
|
||||
|
||||
We write unit tests for our Python code using the [pytest](http://pytest.org/latest/) framework.
|
||||
|
23
Release_Process.md
Normal file
23
Release_Process.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Our Release Process
|
||||
|
||||
This is a summary of the steps we go through to release a new version of BigchainDB Server.
|
||||
|
||||
1. Update the `CHANGELOG.md` file
|
||||
2. Update the version numbers in `bigchaindb/version.py`. Note that we try to use [semantic versioning](http://semver.org/) (i.e. MAJOR.MINOR.PATCH)
|
||||
3. Go to the [bigchaindb/bigchaindb Releases page on GitHub](https://github.com/bigchaindb/bigchaindb/releases)
|
||||
and click the "Draft a new release" button
|
||||
4. Name the tag something like v0.7.0
|
||||
5. The target should be a specific commit: the one when the update of `bigchaindb/version.py` got merged into master
|
||||
6. The release title should be something like v0.7.0
|
||||
7. The description should be copied from the `CHANGELOG.md` file updated above
|
||||
8. Generate and send the latest `bigchaindb` package to PyPI. Dimi and Sylvain can do this, maybe others
|
||||
9. Login to readthedocs.org as a maintainer of the BigchainDB Server docs.
|
||||
Go to Admin --> Versions and under **Choose Active Versions**, make sure that the new version's tag is
|
||||
"Active" and "Public"
|
||||
|
||||
After the release:
|
||||
|
||||
1. Update `bigchaindb/version.py` again, to be something like 0.8.0.dev (with a dev on the end).
|
||||
This is so people reading the latest docs will know that they're for the latest (master branch)
|
||||
version of BigchainDB Server, not the docs at the time of the most recent release (which are also
|
||||
available).
|
@ -1,6 +1,5 @@
|
||||
"""Implementation of the `bigchaindb` command,
|
||||
which is one of the commands in the BigchainDB
|
||||
command-line interface.
|
||||
the command-line interface (CLI) for BigchainDB Server.
|
||||
"""
|
||||
|
||||
import os
|
||||
@ -194,7 +193,7 @@ def _run_load(tx_left, stats):
|
||||
b = bigchaindb.Bigchain()
|
||||
|
||||
while True:
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
b.write_transaction(tx)
|
||||
|
||||
|
@ -14,5 +14,6 @@ def generate_key_pair():
|
||||
private_key, public_key = crypto.ed25519_generate_key_pair()
|
||||
return private_key.decode(), public_key.decode()
|
||||
|
||||
|
||||
PrivateKey = crypto.Ed25519SigningKey
|
||||
PublicKey = crypto.Ed25519VerifyingKey
|
||||
|
@ -69,9 +69,9 @@ class CyclicBlockchainError(Exception):
|
||||
"""Raised when there is a cycle in the blockchain"""
|
||||
|
||||
|
||||
class FulfillmentNotInValidBlock(Exception):
|
||||
"""Raised when a transaction depends on an invalid or undecided
|
||||
fulfillment"""
|
||||
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"""
|
||||
|
||||
|
||||
class AssetIdMismatch(Exception):
|
||||
|
@ -3,13 +3,13 @@ from functools import reduce
|
||||
from uuid import uuid4
|
||||
|
||||
from cryptoconditions import (Fulfillment as CCFulfillment,
|
||||
ThresholdSha256Fulfillment, Ed25519Fulfillment,
|
||||
PreimageSha256Fulfillment)
|
||||
ThresholdSha256Fulfillment, Ed25519Fulfillment)
|
||||
from cryptoconditions.exceptions import ParsingError
|
||||
|
||||
from bigchaindb.common.crypto import PrivateKey, hash_data
|
||||
from bigchaindb.common.exceptions import (KeypairMismatchException,
|
||||
InvalidHash, InvalidSignature)
|
||||
InvalidHash, InvalidSignature,
|
||||
AmountError, AssetIdMismatch)
|
||||
from bigchaindb.common.util import serialize, gen_timestamp
|
||||
|
||||
|
||||
@ -96,6 +96,14 @@ class Fulfillment(object):
|
||||
ffill['fid'] = fid
|
||||
return ffill
|
||||
|
||||
@classmethod
|
||||
def generate(cls, owners_before):
|
||||
# TODO: write docstring
|
||||
# The amount here does not really matter. It is only use on the
|
||||
# condition data model but here we only care about the fulfillment
|
||||
condition = Condition.generate(owners_before, 1)
|
||||
return cls(condition.fulfillment, condition.owners_after)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, ffill):
|
||||
"""Transforms a Python dictionary to a Fulfillment object.
|
||||
@ -265,7 +273,7 @@ class Condition(object):
|
||||
return cond
|
||||
|
||||
@classmethod
|
||||
def generate(cls, owners_after):
|
||||
def generate(cls, owners_after, amount):
|
||||
"""Generates a Condition from a specifically formed tuple or list.
|
||||
|
||||
Note:
|
||||
@ -275,34 +283,24 @@ class Condition(object):
|
||||
|
||||
[(address|condition)*, [(address|condition)*, ...], ...]
|
||||
|
||||
If however, the thresholds of individual threshold conditions
|
||||
to be created have to be set specifically, a tuple of the
|
||||
following structure is necessary:
|
||||
|
||||
([(address|condition)*,
|
||||
([(address|condition)*, ...], subthreshold),
|
||||
...], threshold)
|
||||
|
||||
Args:
|
||||
owners_after (:obj:`list` of :obj:`str`|tuple): The users that
|
||||
should be able to fulfill the Condition that is being
|
||||
created.
|
||||
owners_after (:obj:`list` of :obj:`str`): The public key of
|
||||
the users that should be able to fulfill the Condition
|
||||
that is being created.
|
||||
amount (:obj:`int`): The amount locked by the condition.
|
||||
|
||||
Returns:
|
||||
A Condition that can be used in a Transaction.
|
||||
|
||||
Returns:
|
||||
Raises:
|
||||
TypeError: If `owners_after` is not an instance of `list`.
|
||||
TypeError: If `owners_after` is an empty list.
|
||||
ValueError: If `owners_after` is an empty list.
|
||||
"""
|
||||
# TODO: We probably want to remove the tuple logic for weights here
|
||||
# again:
|
||||
# github.com/bigchaindb/bigchaindb/issues/730#issuecomment-255144756
|
||||
if isinstance(owners_after, tuple):
|
||||
owners_after, threshold = owners_after
|
||||
else:
|
||||
threshold = len(owners_after)
|
||||
|
||||
threshold = len(owners_after)
|
||||
if not isinstance(amount, int):
|
||||
raise TypeError('`amount` must be a int')
|
||||
if amount < 1:
|
||||
raise AmountError('`amount` needs to be greater than zero')
|
||||
if not isinstance(owners_after, list):
|
||||
raise TypeError('`owners_after` must be an instance of list')
|
||||
if len(owners_after) == 0:
|
||||
@ -313,12 +311,12 @@ class Condition(object):
|
||||
ffill = Ed25519Fulfillment(public_key=owners_after[0])
|
||||
except TypeError:
|
||||
ffill = owners_after[0]
|
||||
return cls(ffill, owners_after)
|
||||
return cls(ffill, owners_after, amount=amount)
|
||||
else:
|
||||
initial_cond = ThresholdSha256Fulfillment(threshold=threshold)
|
||||
threshold_cond = reduce(cls._gen_condition, owners_after,
|
||||
initial_cond)
|
||||
return cls(threshold_cond, owners_after)
|
||||
return cls(threshold_cond, owners_after, amount=amount)
|
||||
|
||||
@classmethod
|
||||
def _gen_condition(cls, initial, current):
|
||||
@ -338,14 +336,11 @@ class Condition(object):
|
||||
Returns:
|
||||
:class:`cryptoconditions.ThresholdSha256Fulfillment`:
|
||||
"""
|
||||
if isinstance(current, tuple):
|
||||
owners_after, threshold = current
|
||||
else:
|
||||
owners_after = current
|
||||
try:
|
||||
threshold = len(owners_after)
|
||||
except TypeError:
|
||||
threshold = None
|
||||
owners_after = current
|
||||
try:
|
||||
threshold = len(owners_after)
|
||||
except TypeError:
|
||||
threshold = None
|
||||
|
||||
if isinstance(owners_after, list) and len(owners_after) > 1:
|
||||
ffill = ThresholdSha256Fulfillment(threshold=threshold)
|
||||
@ -420,7 +415,7 @@ class Asset(object):
|
||||
self.updatable = updatable
|
||||
self.refillable = refillable
|
||||
|
||||
self._validate_asset()
|
||||
self.validate_asset()
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
@ -463,7 +458,38 @@ class Asset(object):
|
||||
"""Generates a unqiue uuid for an Asset"""
|
||||
return str(uuid4())
|
||||
|
||||
def _validate_asset(self):
|
||||
@staticmethod
|
||||
def get_asset_id(transactions):
|
||||
"""Get the asset id from a list of transaction ids.
|
||||
|
||||
This is useful when we want to check if the multiple inputs of a
|
||||
transaction are related to the same asset id.
|
||||
|
||||
Args:
|
||||
transactions (:obj:`list` of :class:`~bigchaindb.common.
|
||||
transaction.Transaction`): list of transaction usually inputs
|
||||
that should have a matching asset_id
|
||||
|
||||
Returns:
|
||||
str: uuid of the asset.
|
||||
|
||||
Raises:
|
||||
AssetIdMismatch: If the inputs are related to different assets.
|
||||
"""
|
||||
|
||||
if not isinstance(transactions, list):
|
||||
transactions = [transactions]
|
||||
|
||||
# create a set of asset_ids
|
||||
asset_ids = {tx.asset.data_id for tx in transactions}
|
||||
|
||||
# check that all the transasctions have the same asset_id
|
||||
if len(asset_ids) > 1:
|
||||
raise AssetIdMismatch(('All inputs of all transactions passed'
|
||||
' need to have the same asset id'))
|
||||
return asset_ids.pop()
|
||||
|
||||
def validate_asset(self, amount=None):
|
||||
"""Validates the asset"""
|
||||
if self.data is not None and not isinstance(self.data, dict):
|
||||
raise TypeError('`data` must be a dict instance or None')
|
||||
@ -474,6 +500,77 @@ class Asset(object):
|
||||
if not isinstance(self.updatable, bool):
|
||||
raise TypeError('`updatable` must be a boolean')
|
||||
|
||||
if self.refillable:
|
||||
raise NotImplementedError('Refillable assets are not yet'
|
||||
' implemented')
|
||||
if self.updatable:
|
||||
raise NotImplementedError('Updatable assets are not yet'
|
||||
' implemented')
|
||||
|
||||
# If the amount is supplied we can perform extra validations to
|
||||
# the asset
|
||||
if amount is not None:
|
||||
if not isinstance(amount, int):
|
||||
raise TypeError('`amount` must be an int')
|
||||
|
||||
if self.divisible is False and amount != 1:
|
||||
raise AmountError('non divisible assets always have'
|
||||
' amount equal to one')
|
||||
|
||||
# Since refillable assets are not yet implemented this should
|
||||
# raise and exception
|
||||
if self.divisible is True and amount < 2:
|
||||
raise AmountError('divisible assets must have an amount'
|
||||
' greater than one')
|
||||
|
||||
|
||||
class AssetLink(Asset):
|
||||
"""An object for unidirectional linking to a Asset.
|
||||
"""
|
||||
|
||||
def __init__(self, data_id=None):
|
||||
"""Used to point to a specific Asset.
|
||||
|
||||
Args:
|
||||
data_id (str): A Asset to link to.
|
||||
"""
|
||||
self.data_id = data_id
|
||||
|
||||
def __bool__(self):
|
||||
return self.data_id is not None
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, AssetLink) and \
|
||||
self.to_dict() == other.to_dict()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, link):
|
||||
"""Transforms a Python dictionary to a AssetLink object.
|
||||
|
||||
Args:
|
||||
link (dict): The link to be transformed.
|
||||
|
||||
Returns:
|
||||
:class:`~bigchaindb.common.transaction.AssetLink`
|
||||
"""
|
||||
try:
|
||||
return cls(link['id'])
|
||||
except TypeError:
|
||||
return cls()
|
||||
|
||||
def to_dict(self):
|
||||
"""Transforms the object to a Python dictionary.
|
||||
|
||||
Returns:
|
||||
(dict|None): The link as an alternative serialization format.
|
||||
"""
|
||||
if self.data_id is None:
|
||||
return None
|
||||
else:
|
||||
return {
|
||||
'id': self.data_id
|
||||
}
|
||||
|
||||
|
||||
class Metadata(object):
|
||||
"""Metadata is used to store a dictionary and its hash in a Transaction."""
|
||||
@ -612,9 +709,17 @@ class Transaction(object):
|
||||
self.fulfillments = fulfillments if fulfillments else []
|
||||
self.metadata = metadata
|
||||
|
||||
# validate asset
|
||||
# we know that each transaction relates to a single asset
|
||||
# we can sum the amount of all the conditions
|
||||
# for transactions other then CREATE we only have an id so there is
|
||||
# nothing we can validate
|
||||
if self.operation == self.CREATE:
|
||||
amount = sum([condition.amount for condition in self.conditions])
|
||||
self.asset.validate_asset(amount=amount)
|
||||
|
||||
@classmethod
|
||||
def create(cls, owners_before, owners_after, metadata=None, asset=None,
|
||||
secret=None, time_expire=None):
|
||||
def create(cls, owners_before, owners_after, metadata=None, asset=None):
|
||||
"""A simple way to generate a `CREATE` transaction.
|
||||
|
||||
Note:
|
||||
@ -622,7 +727,6 @@ class Transaction(object):
|
||||
use cases:
|
||||
- Ed25519
|
||||
- ThresholdSha256
|
||||
- PreimageSha256.
|
||||
|
||||
Additionally, it provides support for the following BigchainDB
|
||||
use cases:
|
||||
@ -637,10 +741,6 @@ class Transaction(object):
|
||||
Transaction.
|
||||
asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset
|
||||
to be created in this Transaction.
|
||||
secret (binarystr, optional): A secret string to create a hash-
|
||||
lock Condition.
|
||||
time_expire (int, optional): The UNIX time a Transaction is
|
||||
valid.
|
||||
|
||||
Returns:
|
||||
:class:`~bigchaindb.common.transaction.Transaction`
|
||||
@ -649,54 +749,28 @@ class Transaction(object):
|
||||
raise TypeError('`owners_before` must be a list instance')
|
||||
if not isinstance(owners_after, list):
|
||||
raise TypeError('`owners_after` must be a list instance')
|
||||
if len(owners_before) == 0:
|
||||
raise ValueError('`owners_before` list cannot be empty')
|
||||
if len(owners_after) == 0:
|
||||
raise ValueError('`owners_after` list cannot be empty')
|
||||
|
||||
metadata = Metadata(metadata)
|
||||
if len(owners_before) == len(owners_after) and len(owners_after) == 1:
|
||||
# NOTE: Standard case, one owner before, one after.
|
||||
# NOTE: For this case its sufficient to use the same
|
||||
# fulfillment for the fulfillment and condition.
|
||||
ffill = Ed25519Fulfillment(public_key=owners_before[0])
|
||||
ffill_tx = Fulfillment(ffill, owners_before)
|
||||
cond_tx = Condition.generate(owners_after)
|
||||
return cls(cls.CREATE, asset, [ffill_tx], [cond_tx], metadata)
|
||||
fulfillments = []
|
||||
conditions = []
|
||||
|
||||
elif len(owners_before) == len(owners_after) and len(owners_after) > 1:
|
||||
raise NotImplementedError('Multiple inputs and outputs not'
|
||||
'available for CREATE')
|
||||
# NOTE: Multiple inputs and outputs case. Currently not supported.
|
||||
ffills = [Fulfillment(Ed25519Fulfillment(public_key=owner_before),
|
||||
[owner_before])
|
||||
for owner_before in owners_before]
|
||||
conds = [Condition.generate(owners) for owners in owners_after]
|
||||
return cls(cls.CREATE, asset, ffills, conds, metadata)
|
||||
# generate_conditions
|
||||
for owner_after in owners_after:
|
||||
if not isinstance(owner_after, tuple) or len(owner_after) != 2:
|
||||
raise ValueError(('Each `owner_after` in the list must be a'
|
||||
' tuple of `([<list of public keys>],'
|
||||
' <amount>)`'))
|
||||
pub_keys, amount = owner_after
|
||||
conditions.append(Condition.generate(pub_keys, amount))
|
||||
|
||||
elif len(owners_before) == 1 and len(owners_after) > 1:
|
||||
# NOTE: Multiple owners case
|
||||
cond_tx = Condition.generate(owners_after)
|
||||
ffill = Ed25519Fulfillment(public_key=owners_before[0])
|
||||
ffill_tx = Fulfillment(ffill, owners_before)
|
||||
return cls(cls.CREATE, asset, [ffill_tx], [cond_tx], metadata)
|
||||
# generate fulfillments
|
||||
fulfillments.append(Fulfillment.generate(owners_before))
|
||||
|
||||
elif (len(owners_before) == 1 and len(owners_after) == 0 and
|
||||
secret is not None):
|
||||
# NOTE: Hashlock condition case
|
||||
hashlock = PreimageSha256Fulfillment(preimage=secret)
|
||||
cond_tx = Condition(hashlock.condition_uri)
|
||||
ffill = Ed25519Fulfillment(public_key=owners_before[0])
|
||||
ffill_tx = Fulfillment(ffill, owners_before)
|
||||
return cls(cls.CREATE, asset, [ffill_tx], [cond_tx], metadata)
|
||||
|
||||
elif (len(owners_before) > 0 and len(owners_after) == 0 and
|
||||
time_expire is not None):
|
||||
raise NotImplementedError('Timeout conditions will be implemented '
|
||||
'later')
|
||||
|
||||
elif (len(owners_before) > 0 and len(owners_after) == 0 and
|
||||
secret is None):
|
||||
raise ValueError('Define a secret to create a hashlock condition')
|
||||
|
||||
else:
|
||||
raise ValueError("These are not the cases you're looking for ;)")
|
||||
return cls(cls.CREATE, asset, fulfillments, conditions, metadata)
|
||||
|
||||
@classmethod
|
||||
def transfer(cls, inputs, owners_after, asset, metadata=None):
|
||||
@ -743,17 +817,17 @@ class Transaction(object):
|
||||
raise ValueError('`inputs` must contain at least one item')
|
||||
if not isinstance(owners_after, list):
|
||||
raise TypeError('`owners_after` must be a list instance')
|
||||
if len(owners_after) == 0:
|
||||
raise ValueError('`owners_after` list cannot be empty')
|
||||
|
||||
# NOTE: See doc strings `Note` for description.
|
||||
if len(inputs) == len(owners_after):
|
||||
if len(owners_after) == 1:
|
||||
conditions = [Condition.generate(owners_after)]
|
||||
elif len(owners_after) > 1:
|
||||
conditions = [Condition.generate(owners) for owners
|
||||
in owners_after]
|
||||
else:
|
||||
raise ValueError("`inputs` and `owners_after`'s count must be the "
|
||||
"same")
|
||||
conditions = []
|
||||
for owner_after in owners_after:
|
||||
if not isinstance(owner_after, tuple) or len(owner_after) != 2:
|
||||
raise ValueError(('Each `owner_after` in the list must be a'
|
||||
' tuple of `([<list of public keys>],'
|
||||
' <amount>)`'))
|
||||
pub_keys, amount = owner_after
|
||||
conditions.append(Condition.generate(pub_keys, amount))
|
||||
|
||||
metadata = Metadata(metadata)
|
||||
inputs = deepcopy(inputs)
|
||||
@ -786,20 +860,14 @@ class Transaction(object):
|
||||
:obj:`list` of :class:`~bigchaindb.common.transaction.
|
||||
Fulfillment`
|
||||
"""
|
||||
inputs = []
|
||||
if condition_indices is None or len(condition_indices) == 0:
|
||||
# NOTE: If no condition indices are passed, we just assume to
|
||||
# take all conditions as inputs.
|
||||
condition_indices = [index for index, _
|
||||
in enumerate(self.conditions)]
|
||||
|
||||
for cid in condition_indices:
|
||||
input_cond = self.conditions[cid]
|
||||
ffill = Fulfillment(input_cond.fulfillment,
|
||||
input_cond.owners_after,
|
||||
TransactionLink(self.id, cid))
|
||||
inputs.append(ffill)
|
||||
return inputs
|
||||
# NOTE: If no condition indices are passed, we just assume to
|
||||
# take all conditions as inputs.
|
||||
return [
|
||||
Fulfillment(self.conditions[cid].fulfillment,
|
||||
self.conditions[cid].owners_after,
|
||||
TransactionLink(self.id, cid))
|
||||
for cid in condition_indices or range(len(self.conditions))
|
||||
]
|
||||
|
||||
def add_fulfillment(self, fulfillment):
|
||||
"""Adds a Fulfillment to a Transaction's list of Fulfillments.
|
||||
@ -868,13 +936,12 @@ class Transaction(object):
|
||||
key_pairs = {gen_public_key(PrivateKey(private_key)):
|
||||
PrivateKey(private_key) for private_key in private_keys}
|
||||
|
||||
zippedIO = enumerate(zip(self.fulfillments, self.conditions))
|
||||
for index, (fulfillment, condition) in zippedIO:
|
||||
for index, fulfillment in enumerate(self.fulfillments):
|
||||
# NOTE: We clone the current transaction but only add the condition
|
||||
# and fulfillment we're currently working on plus all
|
||||
# previously signed ones.
|
||||
tx_partial = Transaction(self.operation, self.asset, [fulfillment],
|
||||
[condition], self.metadata,
|
||||
self.conditions, self.metadata,
|
||||
self.timestamp, self.version)
|
||||
|
||||
tx_partial_dict = tx_partial.to_dict()
|
||||
@ -1031,14 +1098,13 @@ class Transaction(object):
|
||||
"""
|
||||
input_condition_uris_count = len(input_condition_uris)
|
||||
fulfillments_count = len(self.fulfillments)
|
||||
conditions_count = len(self.conditions)
|
||||
|
||||
def gen_tx(fulfillment, condition, input_condition_uri=None):
|
||||
"""Splits multiple IO Transactions into partial single IO
|
||||
Transactions.
|
||||
"""
|
||||
tx = Transaction(self.operation, self.asset, [fulfillment],
|
||||
[condition], self.metadata, self.timestamp,
|
||||
self.conditions, self.metadata, self.timestamp,
|
||||
self.version)
|
||||
tx_dict = tx.to_dict()
|
||||
tx_dict = Transaction._remove_signatures(tx_dict)
|
||||
@ -1049,14 +1115,13 @@ class Transaction(object):
|
||||
tx_serialized,
|
||||
input_condition_uri)
|
||||
|
||||
if not fulfillments_count == conditions_count == \
|
||||
input_condition_uris_count:
|
||||
raise ValueError('Fulfillments, conditions and '
|
||||
if not fulfillments_count == input_condition_uris_count:
|
||||
raise ValueError('Fulfillments and '
|
||||
'input_condition_uris must have the same count')
|
||||
else:
|
||||
partial_transactions = map(gen_tx, self.fulfillments,
|
||||
self.conditions, input_condition_uris)
|
||||
return all(partial_transactions)
|
||||
|
||||
partial_transactions = map(gen_tx, self.fulfillments,
|
||||
self.conditions, input_condition_uris)
|
||||
return all(partial_transactions)
|
||||
|
||||
@staticmethod
|
||||
def _fulfillment_valid(fulfillment, operation, tx_serialized,
|
||||
@ -1214,7 +1279,10 @@ class Transaction(object):
|
||||
conditions = [Condition.from_dict(condition) for condition
|
||||
in tx['conditions']]
|
||||
metadata = Metadata.from_dict(tx['metadata'])
|
||||
asset = Asset.from_dict(tx['asset'])
|
||||
if tx['operation'] in [cls.CREATE, cls.GENESIS]:
|
||||
asset = Asset.from_dict(tx['asset'])
|
||||
else:
|
||||
asset = AssetLink.from_dict(tx['asset'])
|
||||
|
||||
return cls(tx['operation'], asset, fulfillments, conditions,
|
||||
metadata, tx['timestamp'], tx_body['version'])
|
||||
|
@ -6,7 +6,7 @@ from time import time
|
||||
from itertools import compress
|
||||
from bigchaindb.common import crypto, exceptions
|
||||
from bigchaindb.common.util import gen_timestamp, serialize
|
||||
from bigchaindb.common.transaction import TransactionLink
|
||||
from bigchaindb.common.transaction import TransactionLink, Asset
|
||||
|
||||
import bigchaindb
|
||||
|
||||
@ -182,12 +182,35 @@ class Bigchain(object):
|
||||
|
||||
try:
|
||||
return self.validate_transaction(transaction)
|
||||
except (ValueError, exceptions.OperationError, exceptions.TransactionDoesNotExist,
|
||||
except (ValueError, exceptions.OperationError,
|
||||
exceptions.TransactionDoesNotExist,
|
||||
exceptions.TransactionOwnerError, exceptions.DoubleSpend,
|
||||
exceptions.InvalidHash, exceptions.InvalidSignature,
|
||||
exceptions.FulfillmentNotInValidBlock):
|
||||
exceptions.TransactionNotInValidBlock, exceptions.AmountError):
|
||||
return False
|
||||
|
||||
def get_block(self, block_id, include_status=False):
|
||||
"""Get the block with the specified `block_id` (and optionally its status)
|
||||
|
||||
Returns the block corresponding to `block_id` or None if no match is
|
||||
found.
|
||||
|
||||
Args:
|
||||
block_id (str): transaction id of the transaction to get
|
||||
include_status (bool): also return the status of the block
|
||||
the return value is then a tuple: (block, status)
|
||||
"""
|
||||
block = self.backend.get_block(block_id)
|
||||
status = None
|
||||
|
||||
if include_status:
|
||||
if block:
|
||||
status = self.block_election_status(block_id,
|
||||
block['block']['voters'])
|
||||
return block, status
|
||||
else:
|
||||
return block
|
||||
|
||||
def get_transaction(self, txid, include_status=False):
|
||||
"""Get the transaction with the specified `txid` (and optionally its status)
|
||||
|
||||
@ -343,6 +366,21 @@ class Bigchain(object):
|
||||
cursor = self.backend.get_transactions_by_asset_id(asset_id)
|
||||
return [Transaction.from_dict(tx) for tx in cursor]
|
||||
|
||||
def get_asset_by_id(self, asset_id):
|
||||
"""Returns the asset associated with an asset_id.
|
||||
|
||||
Args:
|
||||
asset_id (str): The asset id.
|
||||
|
||||
Returns:
|
||||
:class:`~bigchaindb.common.transaction.Asset` if the asset
|
||||
exists else None.
|
||||
"""
|
||||
cursor = self.backend.get_asset_by_id(asset_id)
|
||||
cursor = list(cursor)
|
||||
if cursor:
|
||||
return Asset.from_dict(cursor[0]['transaction']['asset'])
|
||||
|
||||
def get_spent(self, txid, cid):
|
||||
"""Check if a `txid` was already used as an input.
|
||||
|
||||
@ -511,7 +549,7 @@ class Bigchain(object):
|
||||
"""Prepare a genesis block."""
|
||||
|
||||
metadata = {'message': 'Hello World from the BigchainDB'}
|
||||
transaction = Transaction.create([self.me], [self.me],
|
||||
transaction = Transaction.create([self.me], [([self.me], 1)],
|
||||
metadata=metadata)
|
||||
|
||||
# NOTE: The transaction model doesn't expose an API to generate a
|
||||
|
@ -178,7 +178,27 @@ class RethinkDBBackend:
|
||||
r.table('bigchain', read_mode=self.read_mode)
|
||||
.get_all(asset_id, index='asset_id')
|
||||
.concat_map(lambda block: block['block']['transactions'])
|
||||
.filter(lambda transaction: transaction['transaction']['asset']['id'] == asset_id))
|
||||
.filter(lambda transaction:
|
||||
transaction['transaction']['asset']['id'] == asset_id))
|
||||
|
||||
def get_asset_by_id(self, asset_id):
|
||||
"""Returns the asset associated with an asset_id.
|
||||
|
||||
Args:
|
||||
asset_id (str): The asset id.
|
||||
|
||||
Returns:
|
||||
Returns a rethinkdb cursor.
|
||||
"""
|
||||
return self.connection.run(
|
||||
r.table('bigchain', read_mode=self.read_mode)
|
||||
.get_all(asset_id, index='asset_id')
|
||||
.concat_map(lambda block: block['block']['transactions'])
|
||||
.filter(lambda transaction:
|
||||
transaction['transaction']['asset']['id'] == asset_id)
|
||||
.filter(lambda transaction:
|
||||
transaction['transaction']['operation'] == 'CREATE')
|
||||
.pluck({'transaction': 'asset'}))
|
||||
|
||||
def get_spent(self, transaction_id, condition_id):
|
||||
"""Check if a `txid` was already used as an input.
|
||||
@ -258,6 +278,17 @@ class RethinkDBBackend:
|
||||
r.table('bigchain')
|
||||
.insert(r.json(block), durability=durability))
|
||||
|
||||
def get_block(self, block_id):
|
||||
"""Get a block from the bigchain table
|
||||
|
||||
Args:
|
||||
block_id (str): block id of the block to get
|
||||
|
||||
Returns:
|
||||
block (dict): the block or `None`
|
||||
"""
|
||||
return self.connection.run(r.table('bigchain').get(block_id))
|
||||
|
||||
def has_transaction(self, transaction_id):
|
||||
"""Check if a transaction exists in the bigchain table.
|
||||
|
||||
@ -282,6 +313,17 @@ class RethinkDBBackend:
|
||||
r.table('bigchain', read_mode=self.read_mode)
|
||||
.count())
|
||||
|
||||
def count_backlog(self):
|
||||
"""Count the number of transactions in the backlog table.
|
||||
|
||||
Returns:
|
||||
The number of transactions in the backlog.
|
||||
"""
|
||||
|
||||
return self.connection.run(
|
||||
r.table('backlog', read_mode=self.read_mode)
|
||||
.count())
|
||||
|
||||
def write_vote(self, vote):
|
||||
"""Write a vote to the votes table.
|
||||
|
||||
@ -295,6 +337,17 @@ class RethinkDBBackend:
|
||||
r.table('votes')
|
||||
.insert(vote))
|
||||
|
||||
def get_genesis_block(self):
|
||||
"""Get the genesis block
|
||||
|
||||
Returns:
|
||||
The genesis block
|
||||
"""
|
||||
return self.connection.run(
|
||||
r.table('bigchain', read_mode=self.read_mode)
|
||||
.filter(util.is_genesis_block)
|
||||
.nth(0))
|
||||
|
||||
def get_last_voted_block(self, node_pubkey):
|
||||
"""Get the last voted block for a specific node.
|
||||
|
||||
@ -319,10 +372,7 @@ class RethinkDBBackend:
|
||||
|
||||
except r.ReqlNonExistenceError:
|
||||
# return last vote if last vote exists else return Genesis block
|
||||
return self.connection.run(
|
||||
r.table('bigchain', read_mode=self.read_mode)
|
||||
.filter(util.is_genesis_block)
|
||||
.nth(0))
|
||||
return self.get_genesis_block()
|
||||
|
||||
# Now the fun starts. Since the resolution of timestamp is a second,
|
||||
# we might have more than one vote per timestamp. If this is the case
|
||||
|
@ -2,42 +2,12 @@ from bigchaindb.common.crypto import hash_data, PublicKey, PrivateKey
|
||||
from bigchaindb.common.exceptions import (InvalidHash, InvalidSignature,
|
||||
OperationError, DoubleSpend,
|
||||
TransactionDoesNotExist,
|
||||
FulfillmentNotInValidBlock,
|
||||
AssetIdMismatch)
|
||||
TransactionNotInValidBlock,
|
||||
AssetIdMismatch, AmountError)
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
from bigchaindb.common.util import gen_timestamp, serialize
|
||||
|
||||
|
||||
class Asset(Asset):
|
||||
@staticmethod
|
||||
def get_asset_id(transactions):
|
||||
"""Get the asset id from a list of transaction ids.
|
||||
|
||||
This is useful when we want to check if the multiple inputs of a transaction
|
||||
are related to the same asset id.
|
||||
|
||||
Args:
|
||||
transactions (list): list of transaction usually inputs that should have a matching asset_id
|
||||
|
||||
Returns:
|
||||
str: uuid of the asset.
|
||||
|
||||
Raises:
|
||||
AssetIdMismatch: If the inputs are related to different assets.
|
||||
"""
|
||||
|
||||
if not isinstance(transactions, list):
|
||||
transactions = [transactions]
|
||||
|
||||
# create a set of asset_ids
|
||||
asset_ids = {tx.asset.data_id for tx in transactions}
|
||||
|
||||
# check that all the transasctions have the same asset_id
|
||||
if len(asset_ids) > 1:
|
||||
raise AssetIdMismatch("All inputs of a transaction need to have the same asset id.")
|
||||
return asset_ids.pop()
|
||||
|
||||
|
||||
class Transaction(Transaction):
|
||||
def validate(self, bigchain):
|
||||
"""Validate a transaction.
|
||||
@ -54,6 +24,8 @@ class Transaction(Transaction):
|
||||
OperationError: if the transaction operation is not supported
|
||||
TransactionDoesNotExist: if the input of the transaction is not
|
||||
found
|
||||
TransactionNotInValidBlock: if the input of the transaction is not
|
||||
in a valid block
|
||||
TransactionOwnerError: if the new transaction is using an input it
|
||||
doesn't own
|
||||
DoubleSpend: if the transaction is a double spend
|
||||
@ -71,7 +43,8 @@ class Transaction(Transaction):
|
||||
if inputs_defined:
|
||||
raise ValueError('A CREATE operation has no inputs')
|
||||
# validate asset
|
||||
self.asset._validate_asset()
|
||||
amount = sum([condition.amount for condition in self.conditions])
|
||||
self.asset.validate_asset(amount=amount)
|
||||
elif self.operation == Transaction.TRANSFER:
|
||||
if not inputs_defined:
|
||||
raise ValueError('Only `CREATE` transactions can have null '
|
||||
@ -79,6 +52,7 @@ class Transaction(Transaction):
|
||||
# check inputs
|
||||
# store the inputs so that we can check if the asset ids match
|
||||
input_txs = []
|
||||
input_amount = 0
|
||||
for ffill in self.fulfillments:
|
||||
input_txid = ffill.tx_input.txid
|
||||
input_cid = ffill.tx_input.cid
|
||||
@ -90,7 +64,7 @@ class Transaction(Transaction):
|
||||
.format(input_txid))
|
||||
|
||||
if status != bigchain.TX_VALID:
|
||||
raise FulfillmentNotInValidBlock(
|
||||
raise TransactionNotInValidBlock(
|
||||
'input `{}` does not exist in a valid block'.format(
|
||||
input_txid))
|
||||
|
||||
@ -101,11 +75,34 @@ class Transaction(Transaction):
|
||||
|
||||
input_conditions.append(input_tx.conditions[input_cid])
|
||||
input_txs.append(input_tx)
|
||||
if input_tx.conditions[input_cid].amount < 1:
|
||||
raise AmountError('`amount` needs to be greater than zero')
|
||||
input_amount += input_tx.conditions[input_cid].amount
|
||||
|
||||
# validate asset id
|
||||
asset_id = Asset.get_asset_id(input_txs)
|
||||
if asset_id != self.asset.data_id:
|
||||
raise AssetIdMismatch('The asset id of the input does not match the asset id of the transaction')
|
||||
raise AssetIdMismatch(('The asset id of the input does not'
|
||||
' match the asset id of the'
|
||||
' transaction'))
|
||||
|
||||
# get the asset creation to see if its divisible or not
|
||||
asset = bigchain.get_asset_by_id(asset_id)
|
||||
# validate the asset
|
||||
asset.validate_asset(amount=input_amount)
|
||||
# validate the amounts
|
||||
output_amount = 0
|
||||
for condition in self.conditions:
|
||||
if condition.amount < 1:
|
||||
raise AmountError('`amount` needs to be greater than zero')
|
||||
output_amount += condition.amount
|
||||
|
||||
if output_amount != input_amount:
|
||||
raise AmountError(('The amount used in the inputs `{}`'
|
||||
' needs to be same as the amount used'
|
||||
' in the outputs `{}`')
|
||||
.format(input_amount, output_amount))
|
||||
|
||||
else:
|
||||
allowed_operations = ', '.join(Transaction.ALLOWED_OPERATIONS)
|
||||
raise TypeError('`operation`: `{}` must be either {}.'
|
||||
@ -118,8 +115,36 @@ class Transaction(Transaction):
|
||||
|
||||
|
||||
class Block(object):
|
||||
"""Bundle a list of Transactions in a Block. Nodes vote on its validity.
|
||||
|
||||
Attributes:
|
||||
transaction (:obj:`list` of :class:`~.Transaction`):
|
||||
Transactions to be included in the Block.
|
||||
node_pubkey (str): The public key of the node creating the
|
||||
Block.
|
||||
timestamp (str): The Unix time a Block was created.
|
||||
voters (:obj:`list` of :obj:`str`): A list of a federation
|
||||
nodes' public keys supposed to vote on the Block.
|
||||
signature (str): A cryptographic signature ensuring the
|
||||
integrity and validity of the creator of a Block.
|
||||
"""
|
||||
|
||||
def __init__(self, transactions=None, node_pubkey=None, timestamp=None,
|
||||
voters=None, signature=None):
|
||||
"""The Block model is mainly used for (de)serialization and integrity
|
||||
checking.
|
||||
|
||||
Args:
|
||||
transaction (:obj:`list` of :class:`~.Transaction`):
|
||||
Transactions to be included in the Block.
|
||||
node_pubkey (str): The public key of the node creating the
|
||||
Block.
|
||||
timestamp (str): The Unix time a Block was created.
|
||||
voters (:obj:`list` of :obj:`str`): A list of a federation
|
||||
nodes' public keys supposed to vote on the Block.
|
||||
signature (str): A cryptographic signature ensuring the
|
||||
integrity and validity of the creator of a Block.
|
||||
"""
|
||||
if transactions is not None and not isinstance(transactions, list):
|
||||
raise TypeError('`transactions` must be a list instance or None')
|
||||
else:
|
||||
@ -146,18 +171,20 @@ class Block(object):
|
||||
return self.to_dict() == other
|
||||
|
||||
def validate(self, bigchain):
|
||||
"""Validate a block.
|
||||
"""Validate the Block.
|
||||
|
||||
Args:
|
||||
bigchain (Bigchain): an instantiated bigchaindb.Bigchain object.
|
||||
bigchain (:class:`~bigchaindb.Bigchain`): An instantiated Bigchain
|
||||
object.
|
||||
|
||||
Returns:
|
||||
block (Block): The block as a `Block` object if it is valid.
|
||||
Else it raises an appropriate exception describing
|
||||
the reason of invalidity.
|
||||
:class:`~.Block`: If valid, return a `Block` object. Else an
|
||||
appropriate exception describing the reason of invalidity is
|
||||
raised.
|
||||
|
||||
Raises:
|
||||
OperationError: if a non-federation node signed the block.
|
||||
OperationError: If a non-federation node signed the Block.
|
||||
InvalidSignature: If a Block's signature is invalid.
|
||||
"""
|
||||
|
||||
# First, make sure this node hasn't already voted on this block
|
||||
@ -182,6 +209,15 @@ class Block(object):
|
||||
return self
|
||||
|
||||
def sign(self, private_key):
|
||||
"""Create a signature for the Block and overwrite `self.signature`.
|
||||
|
||||
Args:
|
||||
private_key (str): A private key corresponding to
|
||||
`self.node_pubkey`.
|
||||
|
||||
Returns:
|
||||
:class:`~.Block`
|
||||
"""
|
||||
block_body = self.to_dict()
|
||||
block_serialized = serialize(block_body['block'])
|
||||
private_key = PrivateKey(private_key)
|
||||
@ -189,8 +225,13 @@ class Block(object):
|
||||
return self
|
||||
|
||||
def is_signature_valid(self):
|
||||
"""Check the validity of a Block's signature.
|
||||
|
||||
Returns:
|
||||
bool: Stating the validity of the Block's signature.
|
||||
"""
|
||||
block = self.to_dict()['block']
|
||||
# cc only accepts bytesting messages
|
||||
# cc only accepts bytestring messages
|
||||
block_serialized = serialize(block).encode()
|
||||
public_key = PublicKey(block['node_pubkey'])
|
||||
try:
|
||||
@ -202,6 +243,21 @@ class Block(object):
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, block_body):
|
||||
"""Transform a Python dictionary to a Block object.
|
||||
|
||||
Args:
|
||||
block_body (dict): A block dictionary to be transformed.
|
||||
|
||||
Returns:
|
||||
:class:`~Block`
|
||||
|
||||
Raises:
|
||||
InvalidHash: If the block's id is not corresponding to its
|
||||
data.
|
||||
InvalidSignature: If the block's signature is not corresponding
|
||||
to it's data or `node_pubkey`.
|
||||
"""
|
||||
# TODO: Reuse `is_signature_valid` method here.
|
||||
block = block_body['block']
|
||||
block_serialized = serialize(block)
|
||||
block_id = hash_data(block_serialized)
|
||||
@ -237,6 +293,14 @@ class Block(object):
|
||||
return self.to_dict()['id']
|
||||
|
||||
def to_dict(self):
|
||||
"""Transform the Block to a Python dictionary.
|
||||
|
||||
Returns:
|
||||
dict: The Block as a dict.
|
||||
|
||||
Raises:
|
||||
OperationError: If the Block doesn't contain any transactions.
|
||||
"""
|
||||
if len(self.transactions) == 0:
|
||||
raise OperationError('Empty block creation is not allowed')
|
||||
|
||||
|
@ -40,10 +40,11 @@ class Vote:
|
||||
self.validity = {}
|
||||
|
||||
self.invalid_dummy_tx = Transaction.create([self.bigchain.me],
|
||||
[self.bigchain.me])
|
||||
[([self.bigchain.me], 1)])
|
||||
|
||||
def validate_block(self, block):
|
||||
if not self.bigchain.has_previous_vote(block['id'], block['block']['voters']):
|
||||
if not self.bigchain.has_previous_vote(block['id'],
|
||||
block['block']['voters']):
|
||||
try:
|
||||
block = Block.from_dict(block)
|
||||
except (exceptions.InvalidHash, exceptions.InvalidSignature):
|
||||
|
@ -49,19 +49,22 @@ class StandaloneApplication(gunicorn.app.base.BaseApplication):
|
||||
return self.application
|
||||
|
||||
|
||||
def create_app(settings):
|
||||
def create_app(*, debug=False, threads=4):
|
||||
"""Return an instance of the Flask application.
|
||||
|
||||
Args:
|
||||
debug (bool): a flag to activate the debug mode for the app
|
||||
(default: False).
|
||||
threads (int): number of threads to use
|
||||
Return:
|
||||
an instance of the Flask application.
|
||||
"""
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
app.debug = settings.get('debug', False)
|
||||
app.debug = debug
|
||||
|
||||
app.config['bigchain_pool'] = util.pool(Bigchain, size=settings.get('threads', 4))
|
||||
app.config['bigchain_pool'] = util.pool(Bigchain, size=threads)
|
||||
app.config['monitor'] = Monitor()
|
||||
|
||||
app.register_blueprint(info_views, url_prefix='/')
|
||||
@ -88,6 +91,7 @@ def create_server(settings):
|
||||
if not settings.get('threads'):
|
||||
settings['threads'] = (multiprocessing.cpu_count() * 2) + 1
|
||||
|
||||
app = create_app(settings)
|
||||
app = create_app(debug=settings.get('debug', False),
|
||||
threads=settings['threads'])
|
||||
standalone = StandaloneApplication(app, settings)
|
||||
return standalone
|
||||
|
@ -111,6 +111,7 @@ class TransactionListApi(Resource):
|
||||
|
||||
return tx
|
||||
|
||||
|
||||
transaction_api.add_resource(TransactionApi,
|
||||
'/transactions/<string:tx_id>',
|
||||
strict_slashes=False)
|
||||
|
@ -58,6 +58,9 @@ At a high level, one can communicate with a BigchainDB cluster (set of nodes) us
|
||||
<div class="buttondiv">
|
||||
<a class="button" href="http://docs.bigchaindb.com/projects/py-driver/en/latest/index.html">Python Driver Docs</a>
|
||||
</div>
|
||||
<div class="buttondiv">
|
||||
<a class="button" href="https://docs.bigchaindb.com/projects/cli/en/latest/">Command Line Transaction Tool</a>
|
||||
</div>
|
||||
<div class="buttondiv">
|
||||
<a class="button" href="http://docs.bigchaindb.com/projects/server/en/latest/index.html">Server Docs</a>
|
||||
</div>
|
||||
|
@ -3,21 +3,35 @@ The HTTP Client-Server API
|
||||
|
||||
.. note::
|
||||
|
||||
The HTTP client-server API is currently quite rudimentary. For example, there is no ability to do complex queries using the HTTP API. We plan to add querying capabilities in the future.
|
||||
The HTTP client-server API is currently quite rudimentary. For example,
|
||||
there is no ability to do complex queries using the HTTP API. We plan to add
|
||||
querying capabilities in the future.
|
||||
|
||||
When you start Bigchaindb using `bigchaindb start`, an HTTP API is exposed at the address stored in the BigchainDB node configuration settings. The default is:
|
||||
When you start Bigchaindb using `bigchaindb start`, an HTTP API is exposed at
|
||||
the address stored in the BigchainDB node configuration settings. The default
|
||||
is:
|
||||
|
||||
`http://localhost:9984/api/v1/ <http://localhost:9984/api/v1/>`_
|
||||
|
||||
but that address can be changed by changing the "API endpoint" configuration setting (e.g. in a local config file). There's more information about setting the API endpoint in :doc:`the section about BigchainDB Configuration Settings <../server-reference/configuration>`.
|
||||
but that address can be changed by changing the "API endpoint" configuration
|
||||
setting (e.g. in a local config file). There's more information about setting
|
||||
the API endpoint in :doc:`the section about BigchainDB Configuration Settings
|
||||
<../server-reference/configuration>`.
|
||||
|
||||
There are other configuration settings related to the web server (serving the HTTP API). In particular, the default is for the web server socket to bind to ``localhost:9984`` but that can be changed (e.g. to ``0.0.0.0:9984``). For more details, see the "server" settings ("bind", "workers" and "threads") in :doc:`the section about BigchainDB Configuration Settings <../server-reference/configuration>`.
|
||||
There are other configuration settings related to the web server (serving the
|
||||
HTTP API). In particular, the default is for the web server socket to bind to
|
||||
``localhost:9984`` but that can be changed (e.g. to ``0.0.0.0:9984``). For more
|
||||
details, see the "server" settings ("bind", "workers" and "threads") in
|
||||
:doc:`the section about BigchainDB Configuration Settings
|
||||
<../server-reference/configuration>`.
|
||||
|
||||
|
||||
API Root
|
||||
--------
|
||||
|
||||
If you send an HTTP GET request to e.g. ``http://localhost:9984`` (with no ``/api/v1/`` on the end), then you should get an HTTP response with something like the following in the body:
|
||||
If you send an HTTP GET request to e.g. ``http://localhost:9984`` (with no
|
||||
``/api/v1/`` on the end), then you should get an HTTP response with something
|
||||
like the following in the body:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@ -39,8 +53,13 @@ POST /transactions/
|
||||
.. http:post:: /transactions/
|
||||
|
||||
Push a new transaction.
|
||||
|
||||
Note: The posted transaction should be valid `transaction <https://bigchaindb.readthedocs.io/en/latest/data-models/transaction-model.html>`_. The steps to build a valid transaction are beyond the scope of this page. One would normally use a driver such as the `BigchainDB Python Driver <https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>`_ to build a valid transaction.
|
||||
|
||||
Note: The posted transaction should be valid `transaction
|
||||
<https://bigchaindb.readthedocs.io/en/latest/data-models/transaction-model.html>`_.
|
||||
The steps to build a valid transaction are beyond the scope of this page.
|
||||
One would normally use a driver such as the `BigchainDB Python Driver
|
||||
<https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>`_ to
|
||||
build a valid transaction.
|
||||
|
||||
**Example request**:
|
||||
|
||||
@ -158,9 +177,11 @@ GET /transactions/{tx_id}/status
|
||||
|
||||
.. http:get:: /transactions/{tx_id}/status
|
||||
|
||||
Get the status of the transaction with the ID ``tx_id``, if a transaction with that ``tx_id`` exists.
|
||||
Get the status of the transaction with the ID ``tx_id``, if a transaction
|
||||
with that ``tx_id`` exists.
|
||||
|
||||
The possible status values are ``backlog``, ``undecided``, ``valid`` or ``invalid``.
|
||||
The possible status values are ``backlog``, ``undecided``, ``valid`` or
|
||||
``invalid``.
|
||||
|
||||
:param tx_id: transaction ID
|
||||
:type tx_id: hex string
|
||||
@ -194,7 +215,8 @@ GET /transactions/{tx_id}
|
||||
|
||||
Get the transaction with the ID ``tx_id``.
|
||||
|
||||
This endpoint returns only a transaction from a ``VALID`` or ``UNDECIDED`` block on ``bigchain``, if exists.
|
||||
This endpoint returns only a transaction from a ``VALID`` or ``UNDECIDED``
|
||||
block on ``bigchain``, if exists.
|
||||
|
||||
:param tx_id: transaction ID
|
||||
:type tx_id: hex string
|
||||
@ -260,4 +282,46 @@ GET /transactions/{tx_id}
|
||||
}
|
||||
|
||||
:statuscode 200: A transaction with that ID was found.
|
||||
:statuscode 404: A transaction with that ID was not found.
|
||||
:statuscode 404: A transaction with that ID was not found.
|
||||
|
||||
|
||||
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
|
||||
(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
|
||||
the server, this will result in the server returning a 200 OK HTTP status
|
||||
code and an empty list in the response's body.
|
||||
|
||||
:param owner_after: A public key, able to validly spend an output of a transaction, assuming the user also has the corresponding private key.
|
||||
:type owner_after: base58 encoded string
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /unspents?owner_after=1AAAbbb...ccc HTTP/1.1
|
||||
Host: example.com
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
'../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/0',
|
||||
'../transactions/2d431073e1477f3073a4693ac7ff9be5634751de1b8abaa1f4e19548ef0b4b0e/conditions/1'
|
||||
]
|
||||
|
||||
:statuscode 200: A list of outputs were found and returned in the body of the response.
|
||||
:statuscode 400: The request wasn't understood by the server, e.g. the ``owner_after`` querystring was not included in the request.
|
||||
|
@ -1,10 +1,17 @@
|
||||
Drivers & Clients
|
||||
=================
|
||||
|
||||
Currently, the only language-native driver is written in the Python language.
|
||||
|
||||
We also provide the Transaction CLI to be able to script the building of
|
||||
transactions. You may be able to wrap this tool inside the language of
|
||||
your choice, and then use the HTTP API directly to post transactions.
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
http-client-server-api
|
||||
python-driver
|
||||
The Python Driver <https://docs.bigchaindb.com/projects/py-driver/en/latest/index.html>
|
||||
Transaction CLI <https://docs.bigchaindb.com/projects/cli/en/latest/>
|
||||
example-apps
|
||||
|
@ -1,7 +0,0 @@
|
||||
# The Python Driver
|
||||
|
||||
The BigchainDB Python Driver is a Python wrapper around the [HTTP Client-Server API](http-client-server-api.html). A developer can use it to develop a Python app that communicates with one or more BigchainDB clusters.
|
||||
|
||||
The BigchainDB Python Driver documentation is at:
|
||||
|
||||
[http://docs.bigchaindb.com/projects/py-driver/en/latest/index.html](http://docs.bigchaindb.com/projects/py-driver/en/latest/index.html)
|
@ -1,5 +1,7 @@
|
||||
import pytest
|
||||
from ..db.conftest import inputs
|
||||
from unittest.mock import patch
|
||||
|
||||
from ..db.conftest import inputs # noqa
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
@ -9,7 +11,7 @@ def test_asset_transfer(b, user_pk, user_sk):
|
||||
tx_input = b.get_owned_ids(user_pk).pop()
|
||||
tx_create = b.get_transaction(tx_input.txid)
|
||||
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [user_pk],
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)],
|
||||
tx_create.asset)
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||
|
||||
@ -18,61 +20,40 @@ def test_asset_transfer(b, user_pk, user_sk):
|
||||
|
||||
|
||||
def test_validate_bad_asset_creation(b, user_pk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.models import Transaction, Asset
|
||||
|
||||
# `divisible` needs to be a boolean
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx.asset.divisible = 1
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with pytest.raises(TypeError):
|
||||
tx_signed.validate(b)
|
||||
|
||||
# `refillable` needs to be a boolean
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx.asset.refillable = 1
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with pytest.raises(TypeError):
|
||||
b.validate_transaction(tx_signed)
|
||||
|
||||
# `updatable` needs to be a boolean
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx.asset.updatable = 1
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with pytest.raises(TypeError):
|
||||
b.validate_transaction(tx_signed)
|
||||
|
||||
# `data` needs to be a dictionary
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx.asset.data = 'a'
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx_signed = tx.sign([b.me_private])
|
||||
with pytest.raises(TypeError):
|
||||
b.validate_transaction(tx_signed)
|
||||
|
||||
# TODO: Check where to test for the amount
|
||||
"""
|
||||
tx = b.create_transaction(b.me, user_pk, None, 'CREATE')
|
||||
tx['transaction']['conditions'][0]['amount'] = 'a'
|
||||
tx['id'] = get_hash_data(tx['transaction'])
|
||||
tx_signed = b.sign_transaction(tx, b.me_private)
|
||||
with pytest.raises(TypeError):
|
||||
b.validate_transaction(tx_signed)
|
||||
|
||||
tx = b.create_transaction(b.me, user_pk, None, 'CREATE')
|
||||
tx['transaction']['conditions'][0]['amount'] = 2
|
||||
tx['transaction']['asset'].update({'divisible': False})
|
||||
tx['id'] = get_hash_data(tx['transaction'])
|
||||
tx_signed = b.sign_transaction(tx, b.me_private)
|
||||
with pytest.raises(AmountError):
|
||||
b.validate_transaction(tx_signed)
|
||||
|
||||
tx = b.create_transaction(b.me, user_pk, None, 'CREATE')
|
||||
tx['transaction']['conditions'][0]['amount'] = 0
|
||||
tx['id'] = get_hash_data(tx['transaction'])
|
||||
tx_signed = b.sign_transaction(tx, b.me_private)
|
||||
with pytest.raises(AmountError):
|
||||
b.validate_transaction(tx_signed)
|
||||
"""
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_validate_transfer_asset_id_mismatch(b, user_pk, user_sk):
|
||||
@ -81,7 +62,7 @@ def test_validate_transfer_asset_id_mismatch(b, user_pk, user_sk):
|
||||
|
||||
tx_create = b.get_owned_ids(user_pk).pop()
|
||||
tx_create = b.get_transaction(tx_create.txid)
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [user_pk],
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)],
|
||||
tx_create.asset)
|
||||
tx_transfer.asset.data_id = 'aaa'
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||
@ -92,7 +73,7 @@ def test_validate_transfer_asset_id_mismatch(b, user_pk, user_sk):
|
||||
def test_get_asset_id_create_transaction(b, user_pk):
|
||||
from bigchaindb.models import Transaction, Asset
|
||||
|
||||
tx_create = Transaction.create([b.me], [user_pk])
|
||||
tx_create = Transaction.create([b.me], [([user_pk], 1)])
|
||||
asset_id = Asset.get_asset_id(tx_create)
|
||||
|
||||
assert asset_id == tx_create.asset.data_id
|
||||
@ -105,7 +86,7 @@ def test_get_asset_id_transfer_transaction(b, user_pk, user_sk):
|
||||
tx_create = b.get_owned_ids(user_pk).pop()
|
||||
tx_create = b.get_transaction(tx_create.txid)
|
||||
# create a transfer transaction
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [user_pk],
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)],
|
||||
tx_create.asset)
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||
# create a block
|
||||
@ -123,8 +104,8 @@ def test_asset_id_mismatch(b, user_pk):
|
||||
from bigchaindb.models import Transaction, Asset
|
||||
from bigchaindb.common.exceptions import AssetIdMismatch
|
||||
|
||||
tx1 = Transaction.create([b.me], [user_pk])
|
||||
tx2 = Transaction.create([b.me], [user_pk])
|
||||
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx2 = Transaction.create([b.me], [([user_pk], 1)])
|
||||
|
||||
with pytest.raises(AssetIdMismatch):
|
||||
Asset.get_asset_id([tx1, tx2])
|
||||
@ -144,7 +125,7 @@ def test_get_txs_by_asset_id(b, user_pk, user_sk):
|
||||
assert txs[0].asset.data_id == asset_id
|
||||
|
||||
# create a transfer transaction
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [user_pk],
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_pk], 1)],
|
||||
tx_create.asset)
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||
# create the block
|
||||
@ -161,3 +142,73 @@ def test_get_txs_by_asset_id(b, user_pk, user_sk):
|
||||
assert tx_transfer.id in [t.id for t in txs]
|
||||
assert asset_id == txs[0].asset.data_id
|
||||
assert asset_id == txs[1].asset.data_id
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_get_asset_by_id(b, user_vk, user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
tx_create = b.get_owned_ids(user_vk).pop()
|
||||
tx_create = b.get_transaction(tx_create.txid)
|
||||
asset_id = tx_create.asset.data_id
|
||||
|
||||
# create a transfer transaction
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([user_vk], 1)],
|
||||
tx_create.asset)
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||
# create the block
|
||||
block = b.create_block([tx_transfer_signed])
|
||||
b.write_block(block, durability='hard')
|
||||
# vote the block valid
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
txs = b.get_txs_by_asset_id(asset_id)
|
||||
assert len(txs) == 2
|
||||
|
||||
asset = b.get_asset_by_id(asset_id)
|
||||
assert asset == tx_create.asset
|
||||
|
||||
|
||||
def test_create_invalid_divisible_asset(b, user_vk, user_sk):
|
||||
from bigchaindb.models import Transaction, Asset
|
||||
from bigchaindb.common.exceptions import AmountError
|
||||
|
||||
# non divisible assets cannot have amount > 1
|
||||
# Transaction.__init__ should raise an exception
|
||||
asset = Asset(divisible=False)
|
||||
with pytest.raises(AmountError):
|
||||
Transaction.create([user_vk], [([user_vk], 2)], asset=asset)
|
||||
|
||||
# divisible assets need to have an amount > 1
|
||||
# Transaction.__init__ should raise an exception
|
||||
asset = Asset(divisible=True)
|
||||
with pytest.raises(AmountError):
|
||||
Transaction.create([user_vk], [([user_vk], 1)], asset=asset)
|
||||
|
||||
# even if a transaction is badly constructed the server should raise the
|
||||
# exception
|
||||
asset = Asset(divisible=False)
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction.create([user_vk], [([user_vk], 2)], asset=asset)
|
||||
tx_signed = tx.sign([user_sk])
|
||||
with pytest.raises(AmountError):
|
||||
tx_signed.validate(b)
|
||||
assert b.is_valid_transaction(tx_signed) is False
|
||||
|
||||
asset = Asset(divisible=True)
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction.create([user_vk], [([user_vk], 1)], asset=asset)
|
||||
tx_signed = tx.sign([user_sk])
|
||||
with pytest.raises(AmountError):
|
||||
tx_signed.validate(b)
|
||||
assert b.is_valid_transaction(tx_signed) is False
|
||||
|
||||
|
||||
def test_create_valid_divisible_asset(b, user_vk, user_sk):
|
||||
from bigchaindb.models import Transaction, Asset
|
||||
|
||||
asset = Asset(divisible=True)
|
||||
tx = Transaction.create([user_vk], [([user_vk], 2)], asset=asset)
|
||||
tx_signed = tx.sign([user_sk])
|
||||
assert b.is_valid_transaction(tx_signed)
|
||||
|
779
tests/assets/test_divisible_assets.py
Normal file
779
tests/assets/test_divisible_assets.py
Normal file
@ -0,0 +1,779 @@
|
||||
import pytest
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from ..db.conftest import inputs # noqa
|
||||
|
||||
|
||||
# CREATE divisible asset
|
||||
# Single input
|
||||
# Single owners_before
|
||||
# Single output
|
||||
# Single owners_after
|
||||
def test_single_in_single_own_single_out_single_own_create(b, user_vk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
asset = Asset(divisible=True)
|
||||
tx = Transaction.create([b.me], [([user_vk], 100)], asset=asset)
|
||||
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
|
||||
|
||||
|
||||
# CREATE divisible asset
|
||||
# Single input
|
||||
# Single owners_before
|
||||
# Multiple outputs
|
||||
# Single owners_after per output
|
||||
def test_single_in_single_own_multiple_out_single_own_create(b, user_vk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
asset = Asset(divisible=True)
|
||||
tx = Transaction.create([b.me], [([user_vk], 50), ([user_vk], 50)],
|
||||
asset=asset)
|
||||
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
|
||||
|
||||
|
||||
# CREATE divisible asset
|
||||
# Single input
|
||||
# Single owners_before
|
||||
# Single output
|
||||
# Multiple owners_after
|
||||
def test_single_in_single_own_single_out_multiple_own_create(b, user_vk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
asset = Asset(divisible=True)
|
||||
tx = Transaction.create([b.me], [([user_vk, user_vk], 100)], asset=asset)
|
||||
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
|
||||
|
||||
condition = tx_signed.conditions[0].to_dict()
|
||||
assert 'subfulfillments' in condition['condition']['details']
|
||||
assert len(condition['condition']['details']['subfulfillments']) == 2
|
||||
|
||||
assert len(tx_signed.fulfillments) == 1
|
||||
|
||||
|
||||
# CREATE divisible asset
|
||||
# Single input
|
||||
# Single owners_before
|
||||
# Multiple outputs
|
||||
# Mix: one output with a single owners_after, one output with multiple
|
||||
# owners_after
|
||||
def test_single_in_single_own_multiple_out_mix_own_create(b, user_vk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
asset = Asset(divisible=True)
|
||||
tx = Transaction.create([b.me],
|
||||
[([user_vk], 50), ([user_vk, user_vk], 50)],
|
||||
asset=asset)
|
||||
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
|
||||
|
||||
condition_cid1 = tx_signed.conditions[1].to_dict()
|
||||
assert 'subfulfillments' in condition_cid1['condition']['details']
|
||||
assert len(condition_cid1['condition']['details']['subfulfillments']) == 2
|
||||
|
||||
assert len(tx_signed.fulfillments) == 1
|
||||
|
||||
|
||||
# CREATE divisible asset
|
||||
# Single input
|
||||
# Multiple owners_before
|
||||
# Output combinations already tested above
|
||||
def test_single_in_multiple_own_single_out_single_own_create(b, user_vk,
|
||||
user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
asset = Asset(divisible=True)
|
||||
tx = Transaction.create([b.me, user_vk], [([user_vk], 100)], asset=asset)
|
||||
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
|
||||
|
||||
ffill = tx_signed.fulfillments[0].fulfillment.to_dict()
|
||||
assert 'subfulfillments' in ffill
|
||||
assert len(ffill['subfulfillments']) == 2
|
||||
|
||||
|
||||
# TRANSFER divisible asset
|
||||
# Single input
|
||||
# Single owners_before
|
||||
# Single output
|
||||
# Single owners_after
|
||||
# TODO: I don't really need inputs. But I need the database to be setup or
|
||||
# else there will be no genesis block and b.get_last_voted_block will
|
||||
# fail.
|
||||
# Is there a better way of doing this?
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_single_in_single_own_single_out_single_own_transfer(b, user_vk,
|
||||
user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# CREATE divisible asset
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me], [([user_vk], 100)], asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# TRANSFER
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)],
|
||||
asset=tx_create.asset)
|
||||
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
|
||||
|
||||
|
||||
# TRANSFER divisible asset
|
||||
# Single input
|
||||
# Single owners_before
|
||||
# Multiple output
|
||||
# Single owners_after
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_single_in_single_own_multiple_out_single_own_transfer(b, user_vk,
|
||||
user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# CREATE divisible asset
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me], [([user_vk], 100)], asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# TRANSFER
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(),
|
||||
[([b.me], 50), ([b.me], 50)],
|
||||
asset=tx_create.asset)
|
||||
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
|
||||
|
||||
|
||||
# TRANSFER divisible asset
|
||||
# Single input
|
||||
# Single owners_before
|
||||
# Single output
|
||||
# Multiple owners_after
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_single_in_single_own_single_out_multiple_own_transfer(b, user_vk,
|
||||
user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# CREATE divisible asset
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me], [([user_vk], 100)], asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# TRANSFER
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(),
|
||||
[([b.me, b.me], 100)],
|
||||
asset=tx_create.asset)
|
||||
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
|
||||
|
||||
condition = tx_transfer_signed.conditions[0].to_dict()
|
||||
assert 'subfulfillments' in condition['condition']['details']
|
||||
assert len(condition['condition']['details']['subfulfillments']) == 2
|
||||
|
||||
assert len(tx_transfer_signed.fulfillments) == 1
|
||||
|
||||
|
||||
# TRANSFER divisible asset
|
||||
# Single input
|
||||
# Single owners_before
|
||||
# Multiple outputs
|
||||
# Mix: one output with a single owners_after, one output with multiple
|
||||
# owners_after
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_single_in_single_own_multiple_out_mix_own_transfer(b, user_vk,
|
||||
user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# CREATE divisible asset
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me], [([user_vk], 100)], asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# TRANSFER
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(),
|
||||
[([b.me], 50), ([b.me, b.me], 50)],
|
||||
asset=tx_create.asset)
|
||||
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
|
||||
|
||||
condition_cid1 = tx_transfer_signed.conditions[1].to_dict()
|
||||
assert 'subfulfillments' in condition_cid1['condition']['details']
|
||||
assert len(condition_cid1['condition']['details']['subfulfillments']) == 2
|
||||
|
||||
assert len(tx_transfer_signed.fulfillments) == 1
|
||||
|
||||
|
||||
# TRANSFER divisible asset
|
||||
# Single input
|
||||
# Multiple owners_before
|
||||
# Single output
|
||||
# Single owners_after
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_single_in_multiple_own_single_out_single_own_transfer(b, user_vk,
|
||||
user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# CREATE divisible asset
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me], [([b.me, user_vk], 100)],
|
||||
asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# TRANSFER
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)],
|
||||
asset=tx_create.asset)
|
||||
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
|
||||
|
||||
ffill = tx_transfer_signed.fulfillments[0].fulfillment.to_dict()
|
||||
assert 'subfulfillments' in ffill
|
||||
assert len(ffill['subfulfillments']) == 2
|
||||
|
||||
|
||||
# TRANSFER divisible asset
|
||||
# Multiple inputs
|
||||
# Single owners_before per input
|
||||
# Single output
|
||||
# Single owners_after
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_multiple_in_single_own_single_out_single_own_transfer(b, user_vk,
|
||||
user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# CREATE divisible asset
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me], [([user_vk], 50), ([user_vk], 50)],
|
||||
asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# TRANSFER
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)],
|
||||
asset=tx_create.asset)
|
||||
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
|
||||
|
||||
|
||||
# TRANSFER divisible asset
|
||||
# Multiple inputs
|
||||
# Multiple owners_before per input
|
||||
# Single output
|
||||
# Single owners_after
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_multiple_in_multiple_own_single_out_single_own_transfer(b, user_vk,
|
||||
user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# CREATE divisible asset
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me],
|
||||
[([user_vk, b.me], 50),
|
||||
([user_vk, b.me], 50)],
|
||||
asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# TRANSFER
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)],
|
||||
asset=tx_create.asset)
|
||||
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
|
||||
|
||||
ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict()
|
||||
ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict()
|
||||
assert 'subfulfillments' in ffill_fid0
|
||||
assert 'subfulfillments' in ffill_fid1
|
||||
assert len(ffill_fid0['subfulfillments']) == 2
|
||||
assert len(ffill_fid1['subfulfillments']) == 2
|
||||
|
||||
|
||||
# TRANSFER divisible asset
|
||||
# Multiple inputs
|
||||
# Mix: one input with a single owners_before, one input with multiple
|
||||
# owners_before
|
||||
# Single output
|
||||
# Single owners_after
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_muiltiple_in_mix_own_multiple_out_single_own_transfer(b, user_vk,
|
||||
user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# CREATE divisible asset
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me],
|
||||
[([user_vk], 50),
|
||||
([user_vk, b.me], 50)],
|
||||
asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# TRANSFER
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)],
|
||||
asset=tx_create.asset)
|
||||
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
|
||||
|
||||
ffill_fid0 = tx_transfer_signed.fulfillments[0].fulfillment.to_dict()
|
||||
ffill_fid1 = tx_transfer_signed.fulfillments[1].fulfillment.to_dict()
|
||||
assert 'subfulfillments' not in ffill_fid0
|
||||
assert 'subfulfillments' in ffill_fid1
|
||||
assert len(ffill_fid1['subfulfillments']) == 2
|
||||
|
||||
|
||||
# TRANSFER divisible asset
|
||||
# Multiple inputs
|
||||
# Mix: one input with a single owners_before, one input with multiple
|
||||
# owners_before
|
||||
# Multiple outputs
|
||||
# Mix: one output with a single owners_after, one output with multiple
|
||||
# owners_after
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_muiltiple_in_mix_own_multiple_out_mix_own_transfer(b, user_vk,
|
||||
user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# CREATE divisible asset
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me],
|
||||
[([user_vk], 50),
|
||||
([user_vk, b.me], 50)],
|
||||
asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# TRANSFER
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(),
|
||||
[([b.me], 50), ([b.me, user_vk], 50)],
|
||||
asset=tx_create.asset)
|
||||
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
|
||||
|
||||
cond_cid0 = tx_transfer_signed.conditions[0].to_dict()
|
||||
cond_cid1 = tx_transfer_signed.conditions[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()
|
||||
assert 'subfulfillments' not in ffill_fid0
|
||||
assert 'subfulfillments' in ffill_fid1
|
||||
assert len(ffill_fid1['subfulfillments']) == 2
|
||||
|
||||
|
||||
# TRANSFER divisible asset
|
||||
# Multiple inputs from different transactions
|
||||
# Single owners_before
|
||||
# Single output
|
||||
# Single owners_after
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_multiple_in_different_transactions(b, user_vk, user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# CREATE divisible asset
|
||||
# `b` creates a divisible asset and assigns 50 shares to `b` and
|
||||
# 50 shares to `user_vk`
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me],
|
||||
[([user_vk], 50),
|
||||
([b.me], 50)],
|
||||
asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# TRANSFER divisible asset
|
||||
# `b` transfers its 50 shares to `user_vk`
|
||||
# after this transaction `user_vk` will have a total of 100 shares
|
||||
# split across two different transactions
|
||||
tx_transfer1 = Transaction.transfer(tx_create.to_inputs([1]),
|
||||
[([user_vk], 50)],
|
||||
asset=tx_create.asset)
|
||||
tx_transfer1_signed = tx_transfer1.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_transfer1_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# TRANSFER
|
||||
# `user_vk` combines two different transaction with 50 shares each and
|
||||
# transfers a total of 100 shares back to `b`
|
||||
tx_transfer2 = Transaction.transfer(tx_create.to_inputs([0]) +
|
||||
tx_transfer1.to_inputs([0]),
|
||||
[([b.me], 100)],
|
||||
asset=tx_create.asset)
|
||||
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
|
||||
|
||||
fid0_input = tx_transfer2_signed.fulfillments[0].to_dict()['input']['txid']
|
||||
fid1_input = tx_transfer2_signed.fulfillments[1].to_dict()['input']['txid']
|
||||
assert fid0_input == tx_create.id
|
||||
assert fid1_input == tx_transfer1.id
|
||||
|
||||
|
||||
# In a TRANSFER transaction of a divisible asset the amount being spent in the
|
||||
# inputs needs to match the amount being sent in the outputs.
|
||||
# In other words `amount_in_inputs - amount_in_outputs == 0`
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_amount_error_transfer(b, user_vk, user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
from bigchaindb.common.exceptions import AmountError
|
||||
|
||||
# CREATE divisible asset
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me], [([user_vk], 100)], asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# TRANSFER
|
||||
# output amount less than input amount
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 50)],
|
||||
asset=tx_create.asset)
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||
with pytest.raises(AmountError):
|
||||
tx_transfer_signed.validate(b)
|
||||
|
||||
# TRANSFER
|
||||
# output amount greater than input amount
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 101)],
|
||||
asset=tx_create.asset)
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||
with pytest.raises(AmountError):
|
||||
tx_transfer_signed.validate(b)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason='Figure out how to handle this case')
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_threshold_same_public_key(b, user_vk, user_sk):
|
||||
# If we try to fulfill a threshold condition where each subcondition has
|
||||
# the same key get_subcondition_from_vk will always return the first
|
||||
# subcondition. This means that only the 1st subfulfillment will be
|
||||
# generated
|
||||
# Creating threshold conditions with the same key does not make sense but
|
||||
# that does not mean that the code shouldn't work.
|
||||
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# CREATE divisible asset
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me], [([user_vk, user_vk], 100)],
|
||||
asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# TRANSFER
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 100)],
|
||||
asset=tx_create.asset)
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk, user_sk])
|
||||
|
||||
assert tx_transfer_signed.validate(b) == tx_transfer_signed
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_sum_amount(b, user_vk, user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# CREATE divisible asset with 3 outputs with amount 1
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me],
|
||||
[([user_vk], 1),
|
||||
([user_vk], 1),
|
||||
([user_vk], 1)],
|
||||
asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# create a transfer transaction with one output and check if the amount
|
||||
# is 3
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([b.me], 3)],
|
||||
asset=tx_create.asset)
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_divide(b, user_vk, user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# CREATE divisible asset with 1 output with amount 3
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me], [([user_vk], 3)],
|
||||
asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# create a transfer transaction with 3 outputs and check if the amount
|
||||
# of each output is 1
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(),
|
||||
[([b.me], 1), ([b.me], 1), ([b.me], 1)],
|
||||
asset=tx_create.asset)
|
||||
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
|
||||
|
||||
|
||||
# Check that negative inputs are caught when creating a TRANSFER transaction
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_non_positive_amounts_on_transfer(b, user_vk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
from bigchaindb.common.exceptions import AmountError
|
||||
|
||||
# CREATE divisible asset with 1 output with amount 3
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me], [([user_vk], 3)],
|
||||
asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
with pytest.raises(AmountError):
|
||||
Transaction.transfer(tx_create.to_inputs(),
|
||||
[([b.me], 4), ([b.me], -1)],
|
||||
asset=tx_create.asset)
|
||||
|
||||
|
||||
# Check that negative inputs are caught when validating a TRANSFER transaction
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_non_positive_amounts_on_transfer_validate(b, user_vk, user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
from bigchaindb.common.exceptions import AmountError
|
||||
|
||||
# CREATE divisible asset with 1 output with amount 3
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me], [([user_vk], 3)],
|
||||
asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
# create block
|
||||
block = b.create_block([tx_create_signed])
|
||||
assert block.validate(b) == block
|
||||
b.write_block(block, durability='hard')
|
||||
# vote
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# create a transfer transaction with 3 outputs and check if the amount
|
||||
# of each output is 1
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(),
|
||||
[([b.me], 4), ([b.me], 1)],
|
||||
asset=tx_create.asset)
|
||||
tx_transfer.conditions[1].amount = -1
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||
|
||||
with pytest.raises(AmountError):
|
||||
tx_transfer_signed.validate(b)
|
||||
|
||||
|
||||
# Check that negative inputs are caught when creating a CREATE transaction
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_non_positive_amounts_on_create(b, user_vk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
from bigchaindb.common.exceptions import AmountError
|
||||
|
||||
# CREATE divisible asset with 1 output with amount 3
|
||||
asset = Asset(divisible=True)
|
||||
with pytest.raises(AmountError):
|
||||
Transaction.create([b.me], [([user_vk], -3)],
|
||||
asset=asset)
|
||||
|
||||
|
||||
# Check that negative inputs are caught when validating a CREATE transaction
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_non_positive_amounts_on_create_validate(b, user_vk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
from bigchaindb.common.exceptions import AmountError
|
||||
|
||||
# CREATE divisible asset with 1 output with amount 3
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me], [([user_vk], 3)],
|
||||
asset=asset)
|
||||
tx_create.conditions[0].amount = -3
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
|
||||
with pytest.raises(AmountError):
|
||||
tx_create_signed.validate(b)
|
@ -22,6 +22,7 @@ def test_asset_creation_with_data(data):
|
||||
def test_asset_invalid_asset_initialization():
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# check types
|
||||
with raises(TypeError):
|
||||
Asset(data='some wrong type')
|
||||
with raises(TypeError):
|
||||
@ -31,6 +32,12 @@ def test_asset_invalid_asset_initialization():
|
||||
with raises(TypeError):
|
||||
Asset(updatable=1)
|
||||
|
||||
# check for features that are not yet implemented
|
||||
with raises(NotImplementedError):
|
||||
Asset(updatable=True)
|
||||
with raises(NotImplementedError):
|
||||
Asset(refillable=True)
|
||||
|
||||
|
||||
def test_invalid_asset_comparison(data, data_id):
|
||||
from bigchaindb.common.transaction import Asset
|
||||
@ -69,12 +76,17 @@ def test_asset_deserialization(data, data_id):
|
||||
|
||||
def test_validate_asset():
|
||||
from bigchaindb.common.transaction import Asset
|
||||
from bigchaindb.common.exceptions import AmountError
|
||||
|
||||
# test amount errors
|
||||
asset = Asset(divisible=False)
|
||||
with raises(AmountError):
|
||||
asset.validate_asset(amount=2)
|
||||
|
||||
asset = Asset(divisible=True)
|
||||
with raises(AmountError):
|
||||
asset.validate_asset(amount=1)
|
||||
|
||||
asset = Asset()
|
||||
with raises(TypeError):
|
||||
Asset(divisible=1)
|
||||
with raises(TypeError):
|
||||
Asset(refillable=1)
|
||||
with raises(TypeError):
|
||||
Asset(updatable=1)
|
||||
with raises(TypeError):
|
||||
Asset(data='we need more lemon pledge')
|
||||
asset.validate_asset(amount='a')
|
||||
|
@ -1,4 +1,5 @@
|
||||
from pytest import raises, mark
|
||||
from pytest import raises
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
def test_fulfillment_serialization(ffill_uri, user_pub):
|
||||
@ -166,29 +167,7 @@ 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]])
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
|
||||
def test_generate_conditions_split_half_recursive_custom_threshold(user_pub,
|
||||
user2_pub,
|
||||
user3_pub):
|
||||
from bigchaindb.common.transaction import Condition
|
||||
from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment
|
||||
|
||||
expected_simple1 = Ed25519Fulfillment(public_key=user_pub)
|
||||
expected_simple2 = Ed25519Fulfillment(public_key=user2_pub)
|
||||
expected_simple3 = Ed25519Fulfillment(public_key=user3_pub)
|
||||
|
||||
expected = ThresholdSha256Fulfillment(threshold=1)
|
||||
expected.add_subfulfillment(expected_simple1)
|
||||
expected_threshold = ThresholdSha256Fulfillment(threshold=1)
|
||||
expected_threshold.add_subfulfillment(expected_simple2)
|
||||
expected_threshold.add_subfulfillment(expected_simple3)
|
||||
expected.add_subfulfillment(expected_threshold)
|
||||
|
||||
cond = Condition.generate(([user_pub, ([user2_pub, expected_simple3], 1)],
|
||||
1))
|
||||
cond = Condition.generate([user_pub, [user2_pub, expected_simple3]], 1)
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
|
||||
@ -208,7 +187,7 @@ 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])
|
||||
cond = Condition.generate([[expected_simple2, user3_pub], user_pub], 1)
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
|
||||
@ -225,7 +204,7 @@ 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])
|
||||
cond = Condition.generate([user_pub, user2_pub, expected_simple3], 1)
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
|
||||
@ -234,7 +213,7 @@ def test_generate_conditions_single_owner(user_pub):
|
||||
from cryptoconditions import Ed25519Fulfillment
|
||||
|
||||
expected = Ed25519Fulfillment(public_key=user_pub)
|
||||
cond = Condition.generate([user_pub])
|
||||
cond = Condition.generate([user_pub], 1)
|
||||
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
@ -244,48 +223,23 @@ def test_generate_conditions_single_owner_with_condition(user_pub):
|
||||
from cryptoconditions import Ed25519Fulfillment
|
||||
|
||||
expected = Ed25519Fulfillment(public_key=user_pub)
|
||||
cond = Condition.generate([expected])
|
||||
cond = Condition.generate([expected], 1)
|
||||
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
|
||||
# TODO FOR CC: see skip reason
|
||||
@mark.skip(reason='threshold(hashlock).to_dict() exposes secret')
|
||||
def test_generate_threshold_condition_with_hashlock(user_pub, user2_pub,
|
||||
user3_pub):
|
||||
from bigchaindb.common.transaction import Condition
|
||||
from cryptoconditions import (PreimageSha256Fulfillment,
|
||||
Ed25519Fulfillment,
|
||||
ThresholdSha256Fulfillment)
|
||||
|
||||
secret = b'much secret, wow'
|
||||
hashlock = PreimageSha256Fulfillment(preimage=secret)
|
||||
|
||||
expected_simple1 = Ed25519Fulfillment(public_key=user_pub)
|
||||
expected_simple3 = Ed25519Fulfillment(public_key=user3_pub)
|
||||
|
||||
expected = ThresholdSha256Fulfillment(threshold=2)
|
||||
expected_sub = ThresholdSha256Fulfillment(threshold=2)
|
||||
expected_sub.add_subfulfillment(expected_simple1)
|
||||
expected_sub.add_subfulfillment(hashlock)
|
||||
expected.add_subfulfillment(expected_simple3)
|
||||
|
||||
cond = Condition.generate([[user_pub, hashlock], expected_simple3])
|
||||
assert cond.fulfillment.to_dict() == expected.to_dict()
|
||||
|
||||
|
||||
def test_generate_conditions_invalid_parameters(user_pub, user2_pub,
|
||||
user3_pub):
|
||||
from bigchaindb.common.transaction import Condition
|
||||
|
||||
with raises(ValueError):
|
||||
Condition.generate([])
|
||||
Condition.generate([], 1)
|
||||
with raises(TypeError):
|
||||
Condition.generate('not a list')
|
||||
Condition.generate('not a list', 1)
|
||||
with raises(ValueError):
|
||||
Condition.generate([[user_pub, [user2_pub, [user3_pub]]]])
|
||||
Condition.generate([[user_pub, [user2_pub, [user3_pub]]]], 1)
|
||||
with raises(ValueError):
|
||||
Condition.generate([[user_pub]])
|
||||
Condition.generate([[user_pub]], 1)
|
||||
|
||||
|
||||
def test_invalid_transaction_initialization():
|
||||
@ -321,7 +275,8 @@ def test_invalid_transaction_initialization():
|
||||
def test_create_default_asset_on_tx_initialization():
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction(Transaction.CREATE, None)
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, None)
|
||||
expected = Asset()
|
||||
asset = tx.asset
|
||||
|
||||
@ -510,10 +465,72 @@ def test_cast_transaction_link_to_boolean():
|
||||
assert bool(TransactionLink(False, False)) is True
|
||||
|
||||
|
||||
def test_asset_link_serialization():
|
||||
from bigchaindb.common.transaction import AssetLink
|
||||
|
||||
data_id = 'a asset id'
|
||||
expected = {
|
||||
'id': data_id,
|
||||
}
|
||||
asset_link = AssetLink(data_id)
|
||||
|
||||
assert asset_link.to_dict() == expected
|
||||
|
||||
|
||||
def test_asset_link_serialization_with_empty_payload():
|
||||
from bigchaindb.common.transaction import AssetLink
|
||||
|
||||
expected = None
|
||||
asset_link = AssetLink()
|
||||
|
||||
assert asset_link.to_dict() == expected
|
||||
|
||||
|
||||
def test_asset_link_deserialization():
|
||||
from bigchaindb.common.transaction import AssetLink
|
||||
|
||||
data_id = 'a asset id'
|
||||
expected = AssetLink(data_id)
|
||||
asset_link = {
|
||||
'id': data_id
|
||||
}
|
||||
asset_link = AssetLink.from_dict(asset_link)
|
||||
|
||||
assert asset_link == expected
|
||||
|
||||
|
||||
def test_asset_link_deserialization_with_empty_payload():
|
||||
from bigchaindb.common.transaction import AssetLink
|
||||
|
||||
expected = AssetLink()
|
||||
asset_link = AssetLink.from_dict(None)
|
||||
|
||||
assert asset_link == expected
|
||||
|
||||
|
||||
def test_cast_asset_link_to_boolean():
|
||||
from bigchaindb.common.transaction import AssetLink
|
||||
|
||||
assert bool(AssetLink()) is False
|
||||
assert bool(AssetLink('a')) is True
|
||||
assert bool(AssetLink(False)) is True
|
||||
|
||||
|
||||
def test_eq_asset_link():
|
||||
from bigchaindb.common.transaction import AssetLink
|
||||
|
||||
asset_id_1 = 'asset_1'
|
||||
asset_id_2 = 'asset_2'
|
||||
|
||||
assert AssetLink(asset_id_1) == AssetLink(asset_id_1)
|
||||
assert AssetLink(asset_id_1) != AssetLink(asset_id_2)
|
||||
|
||||
|
||||
def test_add_fulfillment_to_tx(user_ffill):
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction(Transaction.CREATE, Asset(), [], [])
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, Asset(), [], [])
|
||||
tx.add_fulfillment(user_ffill)
|
||||
|
||||
assert len(tx.fulfillments) == 1
|
||||
@ -522,7 +539,8 @@ def test_add_fulfillment_to_tx(user_ffill):
|
||||
def test_add_fulfillment_to_tx_with_invalid_parameters():
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction(Transaction.CREATE, Asset())
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, Asset())
|
||||
with raises(TypeError):
|
||||
tx.add_fulfillment('somewronginput')
|
||||
|
||||
@ -530,7 +548,8 @@ def test_add_fulfillment_to_tx_with_invalid_parameters():
|
||||
def test_add_condition_to_tx(user_cond):
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction(Transaction.CREATE, Asset())
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, Asset())
|
||||
tx.add_condition(user_cond)
|
||||
|
||||
assert len(tx.conditions) == 1
|
||||
@ -539,7 +558,8 @@ def test_add_condition_to_tx(user_cond):
|
||||
def test_add_condition_to_tx_with_invalid_parameters():
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction(Transaction.CREATE, Asset(), [], [])
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, Asset(), [], [])
|
||||
with raises(TypeError):
|
||||
tx.add_condition('somewronginput')
|
||||
|
||||
@ -614,16 +634,14 @@ def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv):
|
||||
from bigchaindb.common.crypto import PrivateKey
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction(Transaction.CREATE, Asset(),
|
||||
tx = Transaction(Transaction.CREATE, Asset(divisible=True),
|
||||
[user_ffill, deepcopy(user_ffill)],
|
||||
[user_ffill, deepcopy(user_cond)])
|
||||
[user_cond, deepcopy(user_cond)])
|
||||
|
||||
expected_first = deepcopy(tx)
|
||||
expected_second = deepcopy(tx)
|
||||
expected_first.fulfillments = [expected_first.fulfillments[0]]
|
||||
expected_first.conditions = [expected_first.conditions[0]]
|
||||
expected_second.fulfillments = [expected_second.fulfillments[1]]
|
||||
expected_second.conditions = [expected_second.conditions[1]]
|
||||
|
||||
expected_first_bytes = str(expected_first).encode()
|
||||
expected_first.fulfillments[0].fulfillment.sign(expected_first_bytes,
|
||||
@ -674,7 +692,7 @@ def test_multiple_fulfillment_validation_of_transfer_tx(user_ffill, user_cond,
|
||||
Fulfillment, Condition, Asset)
|
||||
from cryptoconditions import Ed25519Fulfillment
|
||||
|
||||
tx = Transaction(Transaction.CREATE, Asset(),
|
||||
tx = Transaction(Transaction.CREATE, Asset(divisible=True),
|
||||
[user_ffill, deepcopy(user_ffill)],
|
||||
[user_cond, deepcopy(user_cond)])
|
||||
tx.sign([user_priv])
|
||||
@ -715,10 +733,6 @@ def test_validate_fulfillments_of_transfer_tx_with_invalid_params(transfer_tx,
|
||||
with raises(TypeError):
|
||||
transfer_tx.operation = "Operation that doesn't exist"
|
||||
transfer_tx.fulfillments_valid([utx.conditions[0]])
|
||||
with raises(ValueError):
|
||||
tx = utx.sign([user_priv])
|
||||
tx.conditions = []
|
||||
tx.fulfillments_valid()
|
||||
|
||||
|
||||
def test_create_create_transaction_single_io(user_cond, user_pub, data,
|
||||
@ -754,7 +768,8 @@ def test_create_create_transaction_single_io(user_cond, user_pub, data,
|
||||
}
|
||||
|
||||
asset = Asset(data, data_id)
|
||||
tx = Transaction.create([user_pub], [user_pub], data, asset).to_dict()
|
||||
tx = Transaction.create([user_pub], [([user_pub], 1)],
|
||||
data, asset).to_dict()
|
||||
tx.pop('id')
|
||||
tx['transaction']['metadata'].pop('id')
|
||||
tx['transaction'].pop('timestamp')
|
||||
@ -766,17 +781,20 @@ def test_create_create_transaction_single_io(user_cond, user_pub, data,
|
||||
def test_validate_single_io_create_transaction(user_pub, user_priv, data):
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction.create([user_pub], [user_pub], data, Asset())
|
||||
tx = Transaction.create([user_pub], [([user_pub], 1)], data, Asset())
|
||||
tx = tx.sign([user_priv])
|
||||
assert tx.fulfillments_valid() is True
|
||||
|
||||
|
||||
@mark.skip(reason='Multiple inputs and outputs in CREATE not supported')
|
||||
# TODO: Add digital assets
|
||||
def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub,
|
||||
user2_pub):
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
from bigchaindb.common.transaction import Transaction, Asset, Fulfillment
|
||||
|
||||
# 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()
|
||||
ffill.update({'fid': 0})
|
||||
expected = {
|
||||
'transaction': {
|
||||
'conditions': [user_cond.to_dict(0), user2_cond.to_dict(1)],
|
||||
@ -785,45 +803,32 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub,
|
||||
'message': 'hello'
|
||||
}
|
||||
},
|
||||
'fulfillments': [
|
||||
{
|
||||
'owners_before': [
|
||||
user_pub,
|
||||
],
|
||||
'fid': 0,
|
||||
'fulfillment': None,
|
||||
'input': None
|
||||
},
|
||||
{
|
||||
'owners_before': [
|
||||
user2_pub,
|
||||
],
|
||||
'fid': 1,
|
||||
'fulfillment': None,
|
||||
'input': None
|
||||
}
|
||||
],
|
||||
'fulfillments': [ffill],
|
||||
'operation': 'CREATE',
|
||||
},
|
||||
'version': 1
|
||||
}
|
||||
tx = Transaction.create([user_pub, user2_pub], [user_pub, user2_pub],
|
||||
{'message': 'hello'}).to_dict()
|
||||
asset = Asset(divisible=True)
|
||||
tx = Transaction.create([user_pub, user2_pub],
|
||||
[([user_pub], 1), ([user2_pub], 1)],
|
||||
asset=asset,
|
||||
metadata={'message': 'hello'}).to_dict()
|
||||
tx.pop('id')
|
||||
tx['transaction']['metadata'].pop('id')
|
||||
tx['transaction'].pop('timestamp')
|
||||
tx['transaction'].pop('asset')
|
||||
|
||||
assert tx == expected
|
||||
|
||||
|
||||
@mark.skip(reason='Multiple inputs and outputs in CREATE not supported')
|
||||
# TODO: Add digital assets
|
||||
def test_validate_multiple_io_create_transaction(user_pub, user_priv,
|
||||
user2_pub, user2_priv):
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction.create([user_pub, user2_pub], [user_pub, user2_pub],
|
||||
{'message': 'hello'})
|
||||
tx = Transaction.create([user_pub, user2_pub],
|
||||
[([user_pub], 1), ([user2_pub], 1)],
|
||||
metadata={'message': 'hello'},
|
||||
asset=Asset(divisible=True))
|
||||
tx = tx.sign([user_priv, user2_priv])
|
||||
assert tx.fulfillments_valid() is True
|
||||
|
||||
@ -862,7 +867,8 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub,
|
||||
'version': 1
|
||||
}
|
||||
asset = Asset(data, data_id)
|
||||
tx = Transaction.create([user_pub], [user_pub, user2_pub], data, asset)
|
||||
tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)],
|
||||
data, asset)
|
||||
tx_dict = tx.to_dict()
|
||||
tx_dict.pop('id')
|
||||
tx_dict['transaction']['metadata'].pop('id')
|
||||
@ -876,80 +882,27 @@ def test_validate_threshold_create_transaction(user_pub, user_priv, user2_pub,
|
||||
data):
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction.create([user_pub], [user_pub, user2_pub], data, Asset())
|
||||
tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)],
|
||||
data, Asset())
|
||||
tx = tx.sign([user_priv])
|
||||
assert tx.fulfillments_valid() is True
|
||||
|
||||
|
||||
def test_create_create_transaction_hashlock(user_pub, data, data_id):
|
||||
from cryptoconditions import PreimageSha256Fulfillment
|
||||
from bigchaindb.common.transaction import Transaction, Condition, Asset
|
||||
|
||||
secret = b'much secret, wow'
|
||||
hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri
|
||||
cond = Condition(hashlock)
|
||||
|
||||
expected = {
|
||||
'transaction': {
|
||||
'conditions': [cond.to_dict(0)],
|
||||
'metadata': {
|
||||
'data': data,
|
||||
},
|
||||
'asset': {
|
||||
'id': data_id,
|
||||
'divisible': False,
|
||||
'updatable': False,
|
||||
'refillable': False,
|
||||
'data': data,
|
||||
},
|
||||
'fulfillments': [
|
||||
{
|
||||
'owners_before': [
|
||||
user_pub,
|
||||
],
|
||||
'fid': 0,
|
||||
'fulfillment': None,
|
||||
'input': None
|
||||
},
|
||||
],
|
||||
'operation': 'CREATE',
|
||||
},
|
||||
'version': 1
|
||||
}
|
||||
|
||||
asset = Asset(data, data_id)
|
||||
tx = Transaction.create([user_pub], [], data, asset, secret).to_dict()
|
||||
tx.pop('id')
|
||||
tx['transaction']['metadata'].pop('id')
|
||||
tx['transaction'].pop('timestamp')
|
||||
tx['transaction']['fulfillments'][0]['fulfillment'] = None
|
||||
|
||||
assert tx == expected
|
||||
|
||||
|
||||
def test_validate_hashlock_create_transaction(user_pub, user_priv, data):
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx = Transaction.create([user_pub], [], data, Asset(), b'much secret, wow')
|
||||
tx = tx.sign([user_priv])
|
||||
assert tx.fulfillments_valid() is True
|
||||
|
||||
|
||||
def test_create_create_transaction_with_invalid_parameters():
|
||||
def test_create_create_transaction_with_invalid_parameters(user_pub):
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
|
||||
with raises(TypeError):
|
||||
Transaction.create('not a list')
|
||||
with raises(TypeError):
|
||||
Transaction.create([], 'not a list')
|
||||
with raises(NotImplementedError):
|
||||
Transaction.create(['a', 'b'], ['c', 'd'])
|
||||
with raises(NotImplementedError):
|
||||
Transaction.create(['a'], [], time_expire=123)
|
||||
with raises(ValueError):
|
||||
Transaction.create(['a'], [], secret=None)
|
||||
Transaction.create([], [user_pub])
|
||||
with raises(ValueError):
|
||||
Transaction.create([], [], secret='wow, much secret')
|
||||
Transaction.create([user_pub], [])
|
||||
with raises(ValueError):
|
||||
Transaction.create([user_pub], [user_pub])
|
||||
with raises(ValueError):
|
||||
Transaction.create([user_pub], [([user_pub],)])
|
||||
|
||||
|
||||
def test_conditions_to_inputs(tx):
|
||||
@ -995,7 +948,7 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
|
||||
}
|
||||
inputs = tx.to_inputs([0])
|
||||
asset = Asset(None, data_id)
|
||||
transfer_tx = Transaction.transfer(inputs, [user2_pub], asset=asset)
|
||||
transfer_tx = Transaction.transfer(inputs, [([user2_pub], 1)], asset=asset)
|
||||
transfer_tx = transfer_tx.sign([user_priv])
|
||||
transfer_tx = transfer_tx.to_dict()
|
||||
transfer_tx_body = transfer_tx['transaction']
|
||||
@ -1014,16 +967,15 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub,
|
||||
assert transfer_tx.fulfillments_valid([tx.conditions[0]]) is True
|
||||
|
||||
|
||||
@mark.skip(reason='FIXME: When divisible assets land')
|
||||
def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
|
||||
user2_pub, user2_priv,
|
||||
user3_pub, user2_cond):
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
tx1 = Transaction.create([user_pub], [user_pub], {'message': 'hello'})
|
||||
tx1 = tx1.sign([user_priv])
|
||||
tx2 = Transaction.create([user2_pub], [user2_pub], {'message': 'hello'})
|
||||
tx2 = tx2.sign([user2_priv])
|
||||
asset = Asset(divisible=True)
|
||||
tx = Transaction.create([user_pub], [([user_pub], 1), ([user2_pub], 1)],
|
||||
asset=asset, metadata={'message': 'hello'})
|
||||
tx = tx.sign([user_priv])
|
||||
|
||||
expected = {
|
||||
'transaction': {
|
||||
@ -1037,7 +989,7 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
|
||||
'fid': 0,
|
||||
'fulfillment': None,
|
||||
'input': {
|
||||
'txid': tx1.id,
|
||||
'txid': tx.id,
|
||||
'cid': 0
|
||||
}
|
||||
}, {
|
||||
@ -1047,8 +999,8 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
|
||||
'fid': 1,
|
||||
'fulfillment': None,
|
||||
'input': {
|
||||
'txid': tx2.id,
|
||||
'cid': 0
|
||||
'txid': tx.id,
|
||||
'cid': 1
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -1056,30 +1008,28 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv,
|
||||
},
|
||||
'version': 1
|
||||
}
|
||||
tx1_inputs = tx1.to_inputs()
|
||||
tx2_inputs = tx2.to_inputs()
|
||||
tx_inputs = tx1_inputs + tx2_inputs
|
||||
|
||||
transfer_tx = Transaction.transfer(tx_inputs, [[user2_pub], [user2_pub]])
|
||||
transfer_tx = Transaction.transfer(tx.to_inputs(),
|
||||
[([user2_pub], 1), ([user2_pub], 1)],
|
||||
asset=tx.asset)
|
||||
transfer_tx = transfer_tx.sign([user_priv, user2_priv])
|
||||
transfer_tx = transfer_tx
|
||||
|
||||
assert len(transfer_tx.fulfillments) == 2
|
||||
assert len(transfer_tx.conditions) == 2
|
||||
|
||||
combined_conditions = tx1.conditions + tx2.conditions
|
||||
assert transfer_tx.fulfillments_valid(combined_conditions) is True
|
||||
assert transfer_tx.fulfillments_valid(tx.conditions) is True
|
||||
|
||||
transfer_tx = transfer_tx.to_dict()
|
||||
transfer_tx['transaction']['fulfillments'][0]['fulfillment'] = None
|
||||
transfer_tx['transaction']['fulfillments'][1]['fulfillment'] = None
|
||||
transfer_tx['transaction'].pop('timestamp')
|
||||
transfer_tx.pop('id')
|
||||
transfer_tx['transaction'].pop('asset')
|
||||
|
||||
assert expected == transfer_tx
|
||||
|
||||
|
||||
def test_create_transfer_with_invalid_parameters():
|
||||
def test_create_transfer_with_invalid_parameters(user_pub):
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
with raises(TypeError):
|
||||
@ -1090,17 +1040,25 @@ def test_create_transfer_with_invalid_parameters():
|
||||
Transaction.transfer(['fulfillment'], {}, Asset())
|
||||
with raises(ValueError):
|
||||
Transaction.transfer(['fulfillment'], [], Asset())
|
||||
with raises(ValueError):
|
||||
Transaction.transfer(['fulfillment'], [user_pub], Asset())
|
||||
with raises(ValueError):
|
||||
Transaction.transfer(['fulfillment'], [([user_pub],)], Asset())
|
||||
|
||||
|
||||
def test_cant_add_empty_condition():
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
tx = Transaction(Transaction.CREATE, None)
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, None)
|
||||
with raises(TypeError):
|
||||
tx.add_condition(None)
|
||||
|
||||
|
||||
def test_cant_add_empty_fulfillment():
|
||||
from bigchaindb.common.transaction import Transaction
|
||||
tx = Transaction(Transaction.CREATE, None)
|
||||
from bigchaindb.common.transaction import Transaction, Asset
|
||||
|
||||
with patch.object(Asset, 'validate_asset', return_value=None):
|
||||
tx = Transaction(Transaction.CREATE, None)
|
||||
with raises(TypeError):
|
||||
tx.add_fulfillment(None)
|
||||
|
@ -72,7 +72,7 @@ def b(request, node_config):
|
||||
@pytest.fixture
|
||||
def create_tx(b, user_pk):
|
||||
from bigchaindb.models import Transaction
|
||||
return Transaction.create([b.me], [user_pk])
|
||||
return Transaction.create([b.me], [([user_pk], 1)])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -84,5 +84,5 @@ def signed_create_tx(b, create_tx):
|
||||
def signed_transfer_tx(signed_create_tx, user_pk, user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
inputs = signed_create_tx.to_inputs()
|
||||
tx = Transaction.transfer(inputs, [user_pk], signed_create_tx.asset)
|
||||
tx = Transaction.transfer(inputs, [([user_pk], 1)], signed_create_tx.asset)
|
||||
return tx.sign([user_sk])
|
||||
|
@ -84,7 +84,7 @@ def inputs(user_pk):
|
||||
prev_block_id = g.id
|
||||
for block in range(4):
|
||||
transactions = [
|
||||
Transaction.create([b.me], [user_pk]).sign([b.me_private])
|
||||
Transaction.create([b.me], [([user_pk], 1)]).sign([b.me_private])
|
||||
for i in range(10)
|
||||
]
|
||||
block = b.create_block(transactions)
|
||||
|
@ -15,7 +15,7 @@ def dummy_tx():
|
||||
import bigchaindb
|
||||
from bigchaindb.models import Transaction
|
||||
b = bigchaindb.Bigchain()
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
return tx
|
||||
|
||||
@ -37,7 +37,7 @@ class TestBigchainApi(object):
|
||||
|
||||
b.create_genesis_block()
|
||||
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
monkeypatch.setattr('time.time', lambda: 1)
|
||||
block1 = b.create_block([tx])
|
||||
@ -60,7 +60,7 @@ class TestBigchainApi(object):
|
||||
|
||||
b.create_genesis_block()
|
||||
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
block1 = b.create_block([tx])
|
||||
|
||||
@ -74,7 +74,7 @@ class TestBigchainApi(object):
|
||||
|
||||
b.create_genesis_block()
|
||||
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
|
||||
monkeypatch.setattr('time.time', lambda: 1)
|
||||
@ -99,7 +99,7 @@ class TestBigchainApi(object):
|
||||
|
||||
b.create_genesis_block()
|
||||
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
|
||||
monkeypatch.setattr('time.time', lambda: 1)
|
||||
@ -107,13 +107,15 @@ class TestBigchainApi(object):
|
||||
b.write_block(block1, durability='hard')
|
||||
|
||||
monkeypatch.setattr('time.time', lambda: 2)
|
||||
transfer_tx = Transaction.transfer(tx.to_inputs(), [b.me], tx.asset)
|
||||
transfer_tx = Transaction.transfer(tx.to_inputs(), [([b.me], 1)],
|
||||
tx.asset)
|
||||
transfer_tx = transfer_tx.sign([b.me_private])
|
||||
block2 = b.create_block([transfer_tx])
|
||||
b.write_block(block2, durability='hard')
|
||||
|
||||
monkeypatch.setattr('time.time', lambda: 3)
|
||||
transfer_tx2 = Transaction.transfer(tx.to_inputs(), [b.me], tx.asset)
|
||||
transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([b.me], 1)],
|
||||
tx.asset)
|
||||
transfer_tx2 = transfer_tx2.sign([b.me_private])
|
||||
block3 = b.create_block([transfer_tx2])
|
||||
b.write_block(block3, durability='hard')
|
||||
@ -133,7 +135,7 @@ class TestBigchainApi(object):
|
||||
|
||||
b.create_genesis_block()
|
||||
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
|
||||
monkeypatch.setattr('time.time', lambda: 1)
|
||||
@ -159,13 +161,13 @@ class TestBigchainApi(object):
|
||||
b.create_genesis_block()
|
||||
|
||||
monkeypatch.setattr('time.time', lambda: 1)
|
||||
tx1 = Transaction.create([b.me], [b.me])
|
||||
tx1 = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx1 = tx1.sign([b.me_private])
|
||||
block1 = b.create_block([tx1])
|
||||
b.write_block(block1, durability='hard')
|
||||
|
||||
monkeypatch.setattr('time.time', lambda: 2)
|
||||
tx2 = Transaction.create([b.me], [b.me])
|
||||
tx2 = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx2 = tx2.sign([b.me_private])
|
||||
block2 = b.create_block([tx2])
|
||||
b.write_block(block2, durability='hard')
|
||||
@ -185,7 +187,7 @@ class TestBigchainApi(object):
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
metadata = {'msg': 'Hello BigchainDB!'}
|
||||
tx = Transaction.create([b.me], [user_pk], metadata=metadata)
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)], metadata=metadata)
|
||||
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
@ -205,7 +207,7 @@ class TestBigchainApi(object):
|
||||
input_tx = b.get_owned_ids(user_pk).pop()
|
||||
input_tx = b.get_transaction(input_tx.txid)
|
||||
inputs = input_tx.to_inputs()
|
||||
tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
|
||||
tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset)
|
||||
tx = tx.sign([user_sk])
|
||||
response = b.write_transaction(tx)
|
||||
|
||||
@ -223,7 +225,7 @@ class TestBigchainApi(object):
|
||||
input_tx = b.get_owned_ids(user_pk).pop()
|
||||
input_tx = b.get_transaction(input_tx.txid)
|
||||
inputs = input_tx.to_inputs()
|
||||
tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
|
||||
tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset)
|
||||
tx = tx.sign([user_sk])
|
||||
b.write_transaction(tx)
|
||||
|
||||
@ -243,7 +245,7 @@ class TestBigchainApi(object):
|
||||
input_tx = b.get_owned_ids(user_pk).pop()
|
||||
input_tx = b.get_transaction(input_tx.txid)
|
||||
inputs = input_tx.to_inputs()
|
||||
tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
|
||||
tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset)
|
||||
tx = tx.sign([user_sk])
|
||||
# There's no need to b.write_transaction(tx) to the backlog
|
||||
|
||||
@ -267,7 +269,7 @@ class TestBigchainApi(object):
|
||||
input_tx = b.get_owned_ids(user_pk).pop()
|
||||
input_tx = b.get_transaction(input_tx.txid)
|
||||
inputs = input_tx.to_inputs()
|
||||
tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
|
||||
tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset)
|
||||
tx = tx.sign([user_sk])
|
||||
|
||||
# Make sure there's a copy of tx in the backlog
|
||||
@ -369,6 +371,15 @@ class TestBigchainApi(object):
|
||||
|
||||
assert excinfo.value.args[0] == 'Empty block creation is not allowed'
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_get_block_by_id(self, b):
|
||||
new_block = dummy_block()
|
||||
b.write_block(new_block, durability='hard')
|
||||
|
||||
assert b.get_block(new_block.id) == new_block.to_dict()
|
||||
block, status = b.get_block(new_block.id, include_status=True)
|
||||
assert status == b.BLOCK_UNDECIDED
|
||||
|
||||
def test_get_last_voted_block_returns_genesis_if_no_votes_has_been_casted(self, b):
|
||||
import rethinkdb as r
|
||||
from bigchaindb import util
|
||||
@ -528,7 +539,7 @@ class TestBigchainApi(object):
|
||||
input_tx = b.get_owned_ids(user_pk).pop()
|
||||
input_tx = b.get_transaction(input_tx.txid)
|
||||
inputs = input_tx.to_inputs()
|
||||
tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
|
||||
tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset)
|
||||
tx = tx.sign([user_sk])
|
||||
b.write_transaction(tx)
|
||||
|
||||
@ -554,7 +565,7 @@ class TestBigchainApi(object):
|
||||
input_tx = b.get_owned_ids(user_pk).pop()
|
||||
input_tx = b.get_transaction(input_tx.txid)
|
||||
inputs = input_tx.to_inputs()
|
||||
tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
|
||||
tx = Transaction.transfer(inputs, [([user_pk], 1)], input_tx.asset)
|
||||
tx = tx.sign([user_sk])
|
||||
b.write_transaction(tx)
|
||||
|
||||
@ -578,11 +589,21 @@ class TestBigchainApi(object):
|
||||
fulfillment = Fulfillment(Ed25519Fulfillment(public_key=user_pk),
|
||||
[user_pk],
|
||||
TransactionLink('somethingsomething', 0))
|
||||
tx = Transaction.transfer([fulfillment], [user_pk], Asset())
|
||||
tx = Transaction.transfer([fulfillment], [([user_pk], 1)], Asset())
|
||||
|
||||
with pytest.raises(TransactionDoesNotExist) as excinfo:
|
||||
with pytest.raises(TransactionDoesNotExist):
|
||||
tx.validate(Bigchain())
|
||||
|
||||
def test_count_backlog(self, b, user_vk):
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
for _ in range(4):
|
||||
tx = Transaction.create([b.me],
|
||||
[([user_vk], 1)]).sign([b.me_private])
|
||||
b.write_transaction(tx)
|
||||
|
||||
assert b.backend.count_backlog() == 4
|
||||
|
||||
|
||||
class TestTransactionValidation(object):
|
||||
def test_create_operation_with_inputs(self, b, user_pk, create_tx):
|
||||
@ -620,7 +641,7 @@ class TestTransactionValidation(object):
|
||||
input_tx = b.get_owned_ids(user_pk).pop()
|
||||
input_transaction = b.get_transaction(input_tx.txid)
|
||||
sk, pk = generate_key_pair()
|
||||
tx = Transaction.create([pk], [user_pk])
|
||||
tx = Transaction.create([pk], [([user_pk], 1)])
|
||||
tx.operation = 'TRANSFER'
|
||||
tx.asset = input_transaction.asset
|
||||
tx.fulfillments[0].tx_input = input_tx
|
||||
@ -664,7 +685,8 @@ class TestTransactionValidation(object):
|
||||
input_tx = b.get_owned_ids(user_pk).pop()
|
||||
input_tx = b.get_transaction(input_tx.txid)
|
||||
inputs = input_tx.to_inputs()
|
||||
transfer_tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
|
||||
transfer_tx = Transaction.transfer(inputs, [([user_pk], 1)],
|
||||
input_tx.asset)
|
||||
transfer_tx = transfer_tx.sign([user_sk])
|
||||
|
||||
assert transfer_tx == b.validate_transaction(transfer_tx)
|
||||
@ -679,16 +701,17 @@ class TestTransactionValidation(object):
|
||||
assert transfer_tx == b.validate_transaction(transfer_tx)
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_fulfillment_not_in_valid_block(self, b, user_pk, user_sk):
|
||||
def test_transaction_not_in_valid_block(self, b, user_pk, user_sk):
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.exceptions import FulfillmentNotInValidBlock
|
||||
from bigchaindb.common.exceptions import TransactionNotInValidBlock
|
||||
|
||||
input_tx = b.get_owned_ids(user_pk).pop()
|
||||
input_tx = b.get_transaction(input_tx.txid)
|
||||
inputs = input_tx.to_inputs()
|
||||
|
||||
# create a transaction that's valid but not in a voted valid block
|
||||
transfer_tx = Transaction.transfer(inputs, [user_pk], input_tx.asset)
|
||||
transfer_tx = Transaction.transfer(inputs, [([user_pk], 1)],
|
||||
input_tx.asset)
|
||||
transfer_tx = transfer_tx.sign([user_sk])
|
||||
|
||||
assert transfer_tx == b.validate_transaction(transfer_tx)
|
||||
@ -698,11 +721,12 @@ class TestTransactionValidation(object):
|
||||
b.write_block(block, durability='hard')
|
||||
|
||||
# create transaction with the undecided input
|
||||
tx_invalid = Transaction.transfer(transfer_tx.to_inputs(), [user_pk],
|
||||
transfer_tx.asset)
|
||||
tx_invalid = Transaction.transfer(transfer_tx.to_inputs(),
|
||||
[([user_pk], 1)],
|
||||
transfer_tx.asset)
|
||||
tx_invalid = tx_invalid.sign([user_sk])
|
||||
|
||||
with pytest.raises(FulfillmentNotInValidBlock):
|
||||
with pytest.raises(TransactionNotInValidBlock):
|
||||
b.validate_transaction(tx_invalid)
|
||||
|
||||
|
||||
@ -797,7 +821,7 @@ class TestMultipleInputs(object):
|
||||
tx_link = b.get_owned_ids(user_pk).pop()
|
||||
input_tx = b.get_transaction(tx_link.txid)
|
||||
inputs = input_tx.to_inputs()
|
||||
tx = Transaction.transfer(inputs, [user2_pk], input_tx.asset)
|
||||
tx = Transaction.transfer(inputs, [([user2_pk], 1)], input_tx.asset)
|
||||
tx = tx.sign([user_sk])
|
||||
|
||||
# validate transaction
|
||||
@ -805,69 +829,6 @@ class TestMultipleInputs(object):
|
||||
assert len(tx.fulfillments) == 1
|
||||
assert len(tx.conditions) == 1
|
||||
|
||||
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
|
||||
'same asset. Remove this after implementing ',
|
||||
'multiple assets'))
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_transfer_single_owners_multiple_inputs(self, b, user_sk, user_pk):
|
||||
from bigchaindb.common import crypto
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
user2_sk, user2_pk = crypto.generate_key_pair()
|
||||
|
||||
# get inputs
|
||||
owned_inputs = b.get_owned_ids(user_pk)
|
||||
input_txs = [b.get_transaction(tx_link.txid) for tx_link
|
||||
in owned_inputs]
|
||||
inputs = sum([input_tx.to_inputs() for input_tx in input_txs], [])
|
||||
tx = Transaction.transfer(inputs, len(inputs) * [[user_pk]])
|
||||
tx = tx.sign([user_sk])
|
||||
assert b.validate_transaction(tx) == tx
|
||||
assert len(tx.fulfillments) == len(inputs)
|
||||
assert len(tx.conditions) == len(inputs)
|
||||
|
||||
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
|
||||
'same asset. Remove this after implementing ',
|
||||
'multiple assets'))
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_transfer_single_owners_single_input_from_multiple_outputs(self, b,
|
||||
user_sk,
|
||||
user_pk):
|
||||
from bigchaindb.common import crypto
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
user2_sk, user2_pk = crypto.generate_key_pair()
|
||||
|
||||
# get inputs
|
||||
owned_inputs = b.get_owned_ids(user_pk)
|
||||
input_txs = [b.get_transaction(tx_link.txid) for tx_link
|
||||
in owned_inputs]
|
||||
inputs = sum([input_tx.to_inputs() for input_tx in input_txs], [])
|
||||
tx = Transaction.transfer(inputs, len(inputs) * [[user2_pk]])
|
||||
tx = tx.sign([user_sk])
|
||||
|
||||
# create block with the transaction
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
|
||||
# vote block valid
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# get inputs from user2
|
||||
owned_inputs = b.get_owned_ids(user2_pk)
|
||||
assert len(owned_inputs) == len(inputs)
|
||||
|
||||
# create a transaction with a single input from a multiple output transaction
|
||||
tx_link = owned_inputs.pop()
|
||||
inputs = b.get_transaction(tx_link.txid).to_inputs([0])
|
||||
tx = Transaction.transfer(inputs, [user_pk])
|
||||
tx = tx.sign([user2_sk])
|
||||
|
||||
assert b.is_valid_transaction(tx) == tx
|
||||
assert len(tx.fulfillments) == 1
|
||||
assert len(tx.conditions) == 1
|
||||
|
||||
def test_single_owner_before_multiple_owners_after_single_input(self, b,
|
||||
user_sk,
|
||||
user_pk,
|
||||
@ -881,47 +842,14 @@ class TestMultipleInputs(object):
|
||||
owned_inputs = b.get_owned_ids(user_pk)
|
||||
tx_link = owned_inputs.pop()
|
||||
input_tx = b.get_transaction(tx_link.txid)
|
||||
tx = Transaction.transfer(input_tx.to_inputs(), [[user2_pk, user3_pk]], input_tx.asset)
|
||||
tx = Transaction.transfer(input_tx.to_inputs(),
|
||||
[([user2_pk, user3_pk], 1)], input_tx.asset)
|
||||
tx = tx.sign([user_sk])
|
||||
|
||||
assert b.is_valid_transaction(tx) == tx
|
||||
assert len(tx.fulfillments) == 1
|
||||
assert len(tx.conditions) == 1
|
||||
|
||||
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
|
||||
'same asset. Remove this after implementing ',
|
||||
'multiple assets'))
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_single_owner_before_multiple_owners_after_multiple_inputs(self, b,
|
||||
user_sk,
|
||||
user_pk):
|
||||
from bigchaindb.common import crypto
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
user2_sk, user2_pk = crypto.generate_key_pair()
|
||||
user3_sk, user3_pk = crypto.generate_key_pair()
|
||||
|
||||
owned_inputs = b.get_owned_ids(user_pk)
|
||||
input_txs = [b.get_transaction(tx_link.txid) for tx_link
|
||||
in owned_inputs]
|
||||
inputs = sum([input_tx.to_inputs() for input_tx in input_txs], [])
|
||||
|
||||
tx = Transaction.transfer(inputs, len(inputs) * [[user2_pk, user3_pk]])
|
||||
tx = tx.sign([user_sk])
|
||||
|
||||
# create block with the transaction
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
|
||||
# vote block valid
|
||||
vote = b.vote(block.id, b.get_last_voted_block().id, True)
|
||||
b.write_vote(vote)
|
||||
|
||||
# validate transaction
|
||||
assert b.is_valid_transaction(tx) == tx
|
||||
assert len(tx.fulfillments) == len(inputs)
|
||||
assert len(tx.conditions) == len(inputs)
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_multiple_owners_before_single_owner_after_single_input(self, b,
|
||||
user_sk,
|
||||
@ -932,7 +860,7 @@ class TestMultipleInputs(object):
|
||||
user2_sk, user2_pk = crypto.generate_key_pair()
|
||||
user3_sk, user3_pk = crypto.generate_key_pair()
|
||||
|
||||
tx = Transaction.create([b.me], [user_pk, user2_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk, user2_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
@ -945,7 +873,8 @@ class TestMultipleInputs(object):
|
||||
input_tx = b.get_transaction(owned_input.txid)
|
||||
inputs = input_tx.to_inputs()
|
||||
|
||||
transfer_tx = Transaction.transfer(inputs, [user3_pk], input_tx.asset)
|
||||
transfer_tx = Transaction.transfer(inputs, [([user3_pk], 1)],
|
||||
input_tx.asset)
|
||||
transfer_tx = transfer_tx.sign([user_sk, user2_sk])
|
||||
|
||||
# validate transaction
|
||||
@ -953,29 +882,6 @@ class TestMultipleInputs(object):
|
||||
assert len(transfer_tx.fulfillments) == 1
|
||||
assert len(transfer_tx.conditions) == 1
|
||||
|
||||
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
|
||||
'same asset. Remove this after implementing ',
|
||||
'multiple assets'))
|
||||
@pytest.mark.usefixtures('inputs_shared')
|
||||
def test_multiple_owners_before_single_owner_after_multiple_inputs(self, b,
|
||||
user_sk, user_pk, user2_pk, user2_sk):
|
||||
from bigchaindb.common import crypto
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
# create a new users
|
||||
user3_sk, user3_pk = crypto.generate_key_pair()
|
||||
|
||||
tx_links = b.get_owned_ids(user_pk)
|
||||
inputs = sum([b.get_transaction(tx_link.txid).to_inputs() for tx_link
|
||||
in tx_links], [])
|
||||
|
||||
tx = Transaction.transfer(inputs, len(inputs) * [[user3_pk]])
|
||||
tx = tx.sign([user_sk, user2_sk])
|
||||
|
||||
assert b.is_valid_transaction(tx) == tx
|
||||
assert len(tx.fulfillments) == len(inputs)
|
||||
assert len(tx.conditions) == len(inputs)
|
||||
|
||||
@pytest.mark.usefixtures('inputs')
|
||||
def test_multiple_owners_before_multiple_owners_after_single_input(self, b,
|
||||
user_sk,
|
||||
@ -987,7 +893,7 @@ class TestMultipleInputs(object):
|
||||
user3_sk, user3_pk = crypto.generate_key_pair()
|
||||
user4_sk, user4_pk = crypto.generate_key_pair()
|
||||
|
||||
tx = Transaction.create([b.me], [user_pk, user2_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk, user2_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
@ -1000,38 +906,14 @@ class TestMultipleInputs(object):
|
||||
tx_link = b.get_owned_ids(user_pk).pop()
|
||||
tx_input = b.get_transaction(tx_link.txid)
|
||||
|
||||
tx = Transaction.transfer(tx_input.to_inputs(), [[user3_pk, user4_pk]], tx_input.asset)
|
||||
tx = Transaction.transfer(tx_input.to_inputs(),
|
||||
[([user3_pk, user4_pk], 1)], tx_input.asset)
|
||||
tx = tx.sign([user_sk, user2_sk])
|
||||
|
||||
assert b.is_valid_transaction(tx) == tx
|
||||
assert len(tx.fulfillments) == 1
|
||||
assert len(tx.conditions) == 1
|
||||
|
||||
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
|
||||
'same asset. Remove this after implementing ',
|
||||
'multiple assets'))
|
||||
@pytest.mark.usefixtures('inputs_shared')
|
||||
def test_multiple_owners_before_multiple_owners_after_multiple_inputs(self, b,
|
||||
user_sk, user_pk,
|
||||
user2_sk, user2_pk):
|
||||
from bigchaindb.common import crypto
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
# create a new users
|
||||
user3_sk, user3_pk = crypto.generate_key_pair()
|
||||
user4_sk, user4_pk = crypto.generate_key_pair()
|
||||
|
||||
tx_links = b.get_owned_ids(user_pk)
|
||||
inputs = sum([b.get_transaction(tx_link.txid).to_inputs() for tx_link
|
||||
in tx_links], [])
|
||||
|
||||
tx = Transaction.transfer(inputs, len(inputs) * [[user3_pk, user4_pk]])
|
||||
tx = tx.sign([user_sk, user2_sk])
|
||||
|
||||
assert b.is_valid_transaction(tx) == tx
|
||||
assert len(tx.fulfillments) == len(inputs)
|
||||
assert len(tx.conditions) == len(inputs)
|
||||
|
||||
def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_pk):
|
||||
from bigchaindb.common import crypto
|
||||
from bigchaindb.common.transaction import TransactionLink
|
||||
@ -1039,7 +921,7 @@ class TestMultipleInputs(object):
|
||||
|
||||
user2_sk, user2_pk = crypto.generate_key_pair()
|
||||
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
@ -1049,7 +931,7 @@ class TestMultipleInputs(object):
|
||||
assert owned_inputs_user1 == [TransactionLink(tx.id, 0)]
|
||||
assert owned_inputs_user2 == []
|
||||
|
||||
tx = Transaction.transfer(tx.to_inputs(), [user2_pk], tx.asset)
|
||||
tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], tx.asset)
|
||||
tx = tx.sign([user_sk])
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
@ -1069,7 +951,7 @@ class TestMultipleInputs(object):
|
||||
genesis = b.create_genesis_block()
|
||||
user2_sk, user2_pk = crypto.generate_key_pair()
|
||||
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
@ -1085,7 +967,8 @@ class TestMultipleInputs(object):
|
||||
|
||||
# NOTE: The transaction itself is valid, still will mark the block
|
||||
# as invalid to mock the behavior.
|
||||
tx_invalid = Transaction.transfer(tx.to_inputs(), [user2_pk], tx.asset)
|
||||
tx_invalid = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)],
|
||||
tx.asset)
|
||||
tx_invalid = tx_invalid.sign([user_sk])
|
||||
block = b.create_block([tx_invalid])
|
||||
b.write_block(block, durability='hard')
|
||||
@ -1101,47 +984,45 @@ class TestMultipleInputs(object):
|
||||
assert owned_inputs_user1 == [TransactionLink(tx.id, 0)]
|
||||
assert owned_inputs_user2 == []
|
||||
|
||||
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
|
||||
'same asset. Remove this after implementing ',
|
||||
'multiple assets'))
|
||||
def test_get_owned_ids_single_tx_multiple_outputs(self, b, user_sk,
|
||||
user_pk):
|
||||
import random
|
||||
from bigchaindb.common import crypto
|
||||
from bigchaindb.common.transaction import TransactionLink
|
||||
from bigchaindb.common.transaction import TransactionLink, Asset
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
user2_sk, user2_pk = crypto.generate_key_pair()
|
||||
|
||||
transactions = []
|
||||
for i in range(2):
|
||||
payload = {'somedata': random.randint(0, 255)}
|
||||
tx = Transaction.create([b.me], [user_pk], payload)
|
||||
tx = tx.sign([b.me_private])
|
||||
transactions.append(tx)
|
||||
block = b.create_block(transactions)
|
||||
# create divisible asset
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me],
|
||||
[([user_pk], 1), ([user_pk], 1)],
|
||||
asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
block = b.create_block([tx_create_signed])
|
||||
b.write_block(block, durability='hard')
|
||||
|
||||
# get input
|
||||
owned_inputs_user1 = b.get_owned_ids(user_pk)
|
||||
owned_inputs_user2 = b.get_owned_ids(user2_pk)
|
||||
|
||||
expected_owned_inputs_user1 = [TransactionLink(tx.id, 0) for tx
|
||||
in transactions]
|
||||
expected_owned_inputs_user1 = [TransactionLink(tx_create.id, 0),
|
||||
TransactionLink(tx_create.id, 1)]
|
||||
assert owned_inputs_user1 == expected_owned_inputs_user1
|
||||
assert owned_inputs_user2 == []
|
||||
|
||||
inputs = sum([tx.to_inputs() for tx in transactions], [])
|
||||
tx = Transaction.transfer(inputs, len(inputs) * [[user2_pk]])
|
||||
tx = tx.sign([user_sk])
|
||||
block = b.create_block([tx])
|
||||
# transfer divisible asset divided in two outputs
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs(),
|
||||
[([user2_pk], 1), ([user2_pk], 1)],
|
||||
asset=tx_create.asset)
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||
block = b.create_block([tx_transfer_signed])
|
||||
b.write_block(block, durability='hard')
|
||||
|
||||
owned_inputs_user1 = b.get_owned_ids(user_pk)
|
||||
owned_inputs_user2 = b.get_owned_ids(user2_pk)
|
||||
assert owned_inputs_user1 == []
|
||||
assert owned_inputs_user2 == [TransactionLink(tx.id, 0),
|
||||
TransactionLink(tx.id, 1)]
|
||||
assert owned_inputs_user2 == [TransactionLink(tx_transfer.id, 0),
|
||||
TransactionLink(tx_transfer.id, 1)]
|
||||
|
||||
def test_get_owned_ids_multiple_owners(self, b, user_sk, user_pk):
|
||||
from bigchaindb.common import crypto
|
||||
@ -1151,7 +1032,7 @@ class TestMultipleInputs(object):
|
||||
user2_sk, user2_pk = crypto.generate_key_pair()
|
||||
user3_sk, user3_pk = crypto.generate_key_pair()
|
||||
|
||||
tx = Transaction.create([b.me], [user_pk, user2_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk, user2_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
@ -1163,7 +1044,7 @@ class TestMultipleInputs(object):
|
||||
assert owned_inputs_user1 == owned_inputs_user2
|
||||
assert owned_inputs_user1 == expected_owned_inputs_user1
|
||||
|
||||
tx = Transaction.transfer(tx.to_inputs(), [user3_pk], tx.asset)
|
||||
tx = Transaction.transfer(tx.to_inputs(), [([user3_pk], 1)], tx.asset)
|
||||
tx = tx.sign([user_sk, user2_sk])
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
@ -1179,7 +1060,7 @@ class TestMultipleInputs(object):
|
||||
|
||||
user2_sk, user2_pk = crypto.generate_key_pair()
|
||||
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
@ -1193,7 +1074,7 @@ class TestMultipleInputs(object):
|
||||
assert spent_inputs_user1 is None
|
||||
|
||||
# create a transaction and block
|
||||
tx = Transaction.transfer(tx.to_inputs(), [user2_pk], tx.asset)
|
||||
tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], tx.asset)
|
||||
tx = tx.sign([user_sk])
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
@ -1210,7 +1091,7 @@ class TestMultipleInputs(object):
|
||||
# create a new users
|
||||
user2_sk, user2_pk = crypto.generate_key_pair()
|
||||
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
@ -1228,7 +1109,7 @@ class TestMultipleInputs(object):
|
||||
assert spent_inputs_user1 is None
|
||||
|
||||
# create a transaction and block
|
||||
tx = Transaction.transfer(tx.to_inputs(), [user2_pk], tx.asset)
|
||||
tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], tx.asset)
|
||||
tx = tx.sign([user_sk])
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
@ -1243,24 +1124,23 @@ class TestMultipleInputs(object):
|
||||
# Now there should be no spents (the block is invalid)
|
||||
assert spent_inputs_user1 is None
|
||||
|
||||
@pytest.mark.skipif(reason=('Multiple inputs are only allowed for the '
|
||||
'same asset. Remove this after implementing ',
|
||||
'multiple assets'))
|
||||
def test_get_spent_single_tx_multiple_outputs(self, b, user_sk, user_pk):
|
||||
import random
|
||||
from bigchaindb.common import crypto
|
||||
from bigchaindb.models import Transaction
|
||||
from bigchaindb.common.transaction import Asset
|
||||
|
||||
# create a new users
|
||||
user2_sk, user2_pk = crypto.generate_key_pair()
|
||||
|
||||
transactions = []
|
||||
for i in range(3):
|
||||
payload = {'somedata': random.randint(0, 255)}
|
||||
tx = Transaction.create([b.me], [user_pk], payload)
|
||||
tx = tx.sign([b.me_private])
|
||||
transactions.append(tx)
|
||||
block = b.create_block(transactions)
|
||||
# create a divisible asset with 3 outputs
|
||||
asset = Asset(divisible=True)
|
||||
tx_create = Transaction.create([b.me],
|
||||
[([user_pk], 1),
|
||||
([user_pk], 1),
|
||||
([user_pk], 1)],
|
||||
asset=asset)
|
||||
tx_create_signed = tx_create.sign([b.me_private])
|
||||
block = b.create_block([tx_create_signed])
|
||||
b.write_block(block, durability='hard')
|
||||
|
||||
owned_inputs_user1 = b.get_owned_ids(user_pk)
|
||||
@ -1269,22 +1149,22 @@ class TestMultipleInputs(object):
|
||||
for input_tx in owned_inputs_user1:
|
||||
assert b.get_spent(input_tx.txid, input_tx.cid) is None
|
||||
|
||||
# select inputs to use
|
||||
inputs = sum([tx.to_inputs() for tx in transactions[:2]], [])
|
||||
|
||||
# create a transaction and block
|
||||
tx = Transaction.transfer(inputs, len(inputs) * [[user2_pk]])
|
||||
tx = tx.sign([user_sk])
|
||||
block = b.create_block([tx])
|
||||
# transfer the first 2 inputs
|
||||
tx_transfer = Transaction.transfer(tx_create.to_inputs()[:2],
|
||||
[([user2_pk], 1), ([user2_pk], 1)],
|
||||
asset=tx_create.asset)
|
||||
tx_transfer_signed = tx_transfer.sign([user_sk])
|
||||
block = b.create_block([tx_transfer_signed])
|
||||
b.write_block(block, durability='hard')
|
||||
|
||||
# check that used inputs are marked as spent
|
||||
for ffill in inputs:
|
||||
assert b.get_spent(ffill.tx_input.txid, ffill.tx_input.cid) == tx
|
||||
for ffill in tx_create.to_inputs()[:2]:
|
||||
spent_tx = b.get_spent(ffill.tx_input.txid, ffill.tx_input.cid)
|
||||
assert spent_tx == tx_transfer_signed
|
||||
|
||||
# check if remaining transaction that was unspent is also perceived
|
||||
# spendable by BigchainDB
|
||||
assert b.get_spent(transactions[2].id, 0) is None
|
||||
assert b.get_spent(tx_create.to_inputs()[2].tx_input.txid, 2) is None
|
||||
|
||||
def test_get_spent_multiple_owners(self, b, user_sk, user_pk):
|
||||
import random
|
||||
@ -1297,7 +1177,8 @@ class TestMultipleInputs(object):
|
||||
transactions = []
|
||||
for i in range(3):
|
||||
payload = {'somedata': random.randint(0, 255)}
|
||||
tx = Transaction.create([b.me], [user_pk, user2_pk], payload)
|
||||
tx = Transaction.create([b.me], [([user_pk, user2_pk], 1)],
|
||||
payload)
|
||||
tx = tx.sign([b.me_private])
|
||||
transactions.append(tx)
|
||||
block = b.create_block(transactions)
|
||||
@ -1310,7 +1191,8 @@ class TestMultipleInputs(object):
|
||||
assert b.get_spent(input_tx.txid, input_tx.cid) is None
|
||||
|
||||
# create a transaction
|
||||
tx = Transaction.transfer(transactions[0].to_inputs(), [user3_pk], transactions[0].asset)
|
||||
tx = Transaction.transfer(transactions[0].to_inputs(),
|
||||
[([user3_pk], 1)], transactions[0].asset)
|
||||
tx = tx.sign([user_sk, user2_sk])
|
||||
block = b.create_block([tx])
|
||||
b.write_block(block, durability='hard')
|
||||
|
@ -45,7 +45,7 @@ def test_create_block(b, user_pk):
|
||||
block_maker = BlockPipeline()
|
||||
|
||||
for i in range(100):
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
block_maker.create(tx)
|
||||
|
||||
@ -63,7 +63,7 @@ def test_write_block(b, user_pk):
|
||||
|
||||
txs = []
|
||||
for i in range(100):
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
txs.append(tx)
|
||||
|
||||
@ -82,7 +82,7 @@ def test_duplicate_transaction(b, user_pk):
|
||||
|
||||
txs = []
|
||||
for i in range(10):
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
txs.append(tx)
|
||||
|
||||
@ -109,7 +109,7 @@ def test_delete_tx(b, user_pk):
|
||||
from bigchaindb.pipelines.block import BlockPipeline
|
||||
block_maker = BlockPipeline()
|
||||
for i in range(100):
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
block_maker.create(tx)
|
||||
# make sure the tx appears in the backlog
|
||||
@ -138,7 +138,8 @@ def test_prefeed(b, user_pk):
|
||||
from bigchaindb.pipelines.block import initial
|
||||
|
||||
for i in range(100):
|
||||
tx = Transaction.create([b.me], [user_pk], {'msg': random.random()})
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)],
|
||||
{'msg': random.random()})
|
||||
tx = tx.sign([b.me_private])
|
||||
b.write_transaction(tx)
|
||||
|
||||
@ -167,7 +168,8 @@ def test_full_pipeline(b, user_pk):
|
||||
|
||||
count_assigned_to_me = 0
|
||||
for i in range(100):
|
||||
tx = Transaction.create([b.me], [user_pk], {'msg': random.random()})
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)],
|
||||
{'msg': random.random()})
|
||||
tx = tx.sign([b.me_private]).to_dict()
|
||||
assignee = random.choice([b.me, 'aaa', 'bbb', 'ccc'])
|
||||
tx['assignee'] = assignee
|
||||
|
@ -15,7 +15,7 @@ def test_check_for_quorum_invalid(b, user_pk):
|
||||
e = election.Election()
|
||||
|
||||
# create blocks with transactions
|
||||
tx1 = Transaction.create([b.me], [user_pk])
|
||||
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
||||
test_block = b.create_block([tx1])
|
||||
|
||||
# simulate a federation with four voters
|
||||
@ -44,7 +44,7 @@ def test_check_for_quorum_invalid_prev_node(b, user_pk):
|
||||
e = election.Election()
|
||||
|
||||
# create blocks with transactions
|
||||
tx1 = Transaction.create([b.me], [user_pk])
|
||||
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
||||
test_block = b.create_block([tx1])
|
||||
|
||||
# simulate a federation with four voters
|
||||
@ -74,7 +74,7 @@ def test_check_for_quorum_valid(b, user_pk):
|
||||
e = election.Election()
|
||||
|
||||
# create blocks with transactions
|
||||
tx1 = Transaction.create([b.me], [user_pk])
|
||||
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
||||
test_block = b.create_block([tx1])
|
||||
|
||||
# simulate a federation with four voters
|
||||
@ -103,7 +103,7 @@ def test_check_requeue_transaction(b, user_pk):
|
||||
e = election.Election()
|
||||
|
||||
# create blocks with transactions
|
||||
tx1 = Transaction.create([b.me], [user_pk])
|
||||
tx1 = Transaction.create([b.me], [([user_pk], 1)])
|
||||
test_block = b.create_block([tx1])
|
||||
|
||||
e.requeue_transactions(test_block)
|
||||
@ -131,7 +131,8 @@ def test_full_pipeline(b, user_pk):
|
||||
# write two blocks
|
||||
txs = []
|
||||
for i in range(100):
|
||||
tx = Transaction.create([b.me], [user_pk], {'msg': random.random()})
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)],
|
||||
{'msg': random.random()})
|
||||
tx = tx.sign([b.me_private])
|
||||
txs.append(tx)
|
||||
|
||||
@ -140,7 +141,8 @@ def test_full_pipeline(b, user_pk):
|
||||
|
||||
txs = []
|
||||
for i in range(100):
|
||||
tx = Transaction.create([b.me], [user_pk], {'msg': random.random()})
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)],
|
||||
{'msg': random.random()})
|
||||
tx = tx.sign([b.me_private])
|
||||
txs.append(tx)
|
||||
|
||||
|
@ -10,7 +10,7 @@ import os
|
||||
|
||||
def test_get_stale(b, user_pk):
|
||||
from bigchaindb.models import Transaction
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
b.write_transaction(tx, durability='hard')
|
||||
|
||||
@ -27,7 +27,7 @@ def test_get_stale(b, user_pk):
|
||||
def test_reassign_transactions(b, user_pk):
|
||||
from bigchaindb.models import Transaction
|
||||
# test with single node
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
b.write_transaction(tx, durability='hard')
|
||||
|
||||
@ -36,7 +36,7 @@ def test_reassign_transactions(b, user_pk):
|
||||
stm.reassign_transactions(tx.to_dict())
|
||||
|
||||
# test with federation
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
b.write_transaction(tx, durability='hard')
|
||||
|
||||
@ -51,7 +51,7 @@ def test_reassign_transactions(b, user_pk):
|
||||
assert reassigned_tx['assignee'] != tx['assignee']
|
||||
|
||||
# test with node not in federation
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx = tx.sign([b.me_private]).to_dict()
|
||||
tx.update({'assignee': 'lol'})
|
||||
tx.update({'assignment_timestamp': time.time()})
|
||||
@ -85,7 +85,7 @@ def test_full_pipeline(monkeypatch, user_pk):
|
||||
monkeypatch.setattr('time.time', lambda: 1)
|
||||
|
||||
for i in range(100):
|
||||
tx = Transaction.create([b.me], [user_pk])
|
||||
tx = Transaction.create([b.me], [([user_pk], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
original_txc.append(tx.to_dict())
|
||||
|
||||
|
@ -8,7 +8,7 @@ from multipipes import Pipe, Pipeline
|
||||
|
||||
def dummy_tx(b):
|
||||
from bigchaindb.models import Transaction
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
return tx
|
||||
|
||||
@ -130,7 +130,7 @@ def test_vote_validate_transaction(b):
|
||||
assert validation == (True, 123, 1)
|
||||
|
||||
# NOTE: Submit unsigned transaction to `validate_tx` yields `False`.
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
validation = vote_obj.validate_tx(tx, 456, 10)
|
||||
assert validation == (False, 456, 10)
|
||||
|
||||
@ -224,7 +224,7 @@ def test_valid_block_voting_with_create_transaction(b, monkeypatch):
|
||||
|
||||
# create a `CREATE` transaction
|
||||
test_user_priv, test_user_pub = crypto.generate_key_pair()
|
||||
tx = Transaction.create([b.me], [test_user_pub])
|
||||
tx = Transaction.create([b.me], [([test_user_pub], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
|
||||
monkeypatch.setattr('time.time', lambda: 1)
|
||||
@ -265,7 +265,7 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b):
|
||||
|
||||
# create a `CREATE` transaction
|
||||
test_user_priv, test_user_pub = crypto.generate_key_pair()
|
||||
tx = Transaction.create([b.me], [test_user_pub])
|
||||
tx = Transaction.create([b.me], [([test_user_pub], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
|
||||
monkeypatch.setattr('time.time', lambda: 1)
|
||||
@ -274,7 +274,8 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b):
|
||||
|
||||
# create a `TRANSFER` transaction
|
||||
test_user2_priv, test_user2_pub = crypto.generate_key_pair()
|
||||
tx2 = Transaction.transfer(tx.to_inputs(), [test_user2_pub], tx.asset)
|
||||
tx2 = Transaction.transfer(tx.to_inputs(), [([test_user2_pub], 1)],
|
||||
tx.asset)
|
||||
tx2 = tx2.sign([test_user_priv])
|
||||
|
||||
monkeypatch.setattr('time.time', lambda: 2)
|
||||
@ -338,7 +339,7 @@ def test_unsigned_tx_in_block_voting(monkeypatch, b, user_pk):
|
||||
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
|
||||
|
||||
# NOTE: `tx` is invalid, because it wasn't signed.
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
block = b.create_block([tx])
|
||||
|
||||
inpipe.put(block.to_dict())
|
||||
@ -375,7 +376,7 @@ def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_pk):
|
||||
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
|
||||
|
||||
# NOTE: `tx` is invalid, because its id is not corresponding to its content
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
block = b.create_block([tx]).to_dict()
|
||||
block['block']['transactions'][0]['id'] = 'an invalid tx id'
|
||||
@ -414,7 +415,7 @@ def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_pk):
|
||||
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
|
||||
|
||||
# NOTE: `tx` is invalid, because its content is not corresponding to its id
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx = tx.sign([b.me_private])
|
||||
block = b.create_block([tx]).to_dict()
|
||||
block['block']['transactions'][0]['id'] = 'an invalid tx id'
|
||||
|
@ -5,7 +5,7 @@ class TestTransactionModel(object):
|
||||
def test_validating_an_invalid_transaction(self, b):
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
tx.operation = 'something invalid'
|
||||
|
||||
with raises(TypeError):
|
||||
@ -41,7 +41,7 @@ class TestBlockModel(object):
|
||||
from bigchaindb.common.util import gen_timestamp, serialize
|
||||
from bigchaindb.models import Block, Transaction
|
||||
|
||||
transactions = [Transaction.create([b.me], [b.me])]
|
||||
transactions = [Transaction.create([b.me], [([b.me], 1)])]
|
||||
timestamp = gen_timestamp()
|
||||
voters = ['Qaaa', 'Qbbb']
|
||||
expected_block = {
|
||||
@ -73,7 +73,7 @@ class TestBlockModel(object):
|
||||
from bigchaindb.common.util import gen_timestamp, serialize
|
||||
from bigchaindb.models import Block, Transaction
|
||||
|
||||
transactions = [Transaction.create([b.me], [b.me])]
|
||||
transactions = [Transaction.create([b.me], [([b.me], 1)])]
|
||||
timestamp = gen_timestamp()
|
||||
voters = ['Qaaa', 'Qbbb']
|
||||
expected = Block(transactions, b.me, timestamp, voters)
|
||||
@ -113,7 +113,7 @@ class TestBlockModel(object):
|
||||
from bigchaindb.common.util import gen_timestamp, serialize
|
||||
from bigchaindb.models import Block, Transaction
|
||||
|
||||
transactions = [Transaction.create([b.me], [b.me])]
|
||||
transactions = [Transaction.create([b.me], [([b.me], 1)])]
|
||||
timestamp = gen_timestamp()
|
||||
voters = ['Qaaa', 'Qbbb']
|
||||
|
||||
@ -136,7 +136,7 @@ class TestBlockModel(object):
|
||||
def test_compare_blocks(self, b):
|
||||
from bigchaindb.models import Block, Transaction
|
||||
|
||||
transactions = [Transaction.create([b.me], [b.me])]
|
||||
transactions = [Transaction.create([b.me], [([b.me], 1)])]
|
||||
|
||||
assert Block() != 'invalid comparison'
|
||||
assert Block(transactions) == Block(transactions)
|
||||
@ -146,7 +146,7 @@ class TestBlockModel(object):
|
||||
from bigchaindb.common.util import gen_timestamp, serialize
|
||||
from bigchaindb.models import Block, Transaction
|
||||
|
||||
transactions = [Transaction.create([b.me], [b.me])]
|
||||
transactions = [Transaction.create([b.me], [([b.me], 1)])]
|
||||
timestamp = gen_timestamp()
|
||||
voters = ['Qaaa', 'Qbbb']
|
||||
expected_block = {
|
||||
@ -168,7 +168,7 @@ class TestBlockModel(object):
|
||||
from unittest.mock import Mock
|
||||
from bigchaindb.models import Transaction
|
||||
|
||||
tx = Transaction.create([b.me], [b.me])
|
||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||
block = b.create_block([tx])
|
||||
|
||||
has_previous_vote = Mock()
|
||||
|
@ -25,7 +25,7 @@ def app(request, node_config):
|
||||
restore_config(request, node_config)
|
||||
|
||||
from bigchaindb.web import server
|
||||
app = server.create_app({'debug': True})
|
||||
app = server.create_app(debug=True)
|
||||
return app
|
||||
|
||||
|
||||
|
5
tests/web/test_info.py
Normal file
5
tests/web/test_info.py
Normal file
@ -0,0 +1,5 @@
|
||||
def test_api_endpoint_shows_basic_info(client):
|
||||
from bigchaindb import version
|
||||
res = client.get('/')
|
||||
assert res.json['software'] == 'BigchainDB'
|
||||
assert res.json['version'] == version.__version__
|
@ -25,18 +25,11 @@ def test_get_transaction_returns_404_if_not_found(client):
|
||||
assert res.status_code == 404
|
||||
|
||||
|
||||
def test_api_endpoint_shows_basic_info(client):
|
||||
from bigchaindb import version
|
||||
res = client.get('/')
|
||||
assert res.json['software'] == 'BigchainDB'
|
||||
assert res.json['version'] == version.__version__
|
||||
|
||||
|
||||
def test_post_create_transaction_endpoint(b, client):
|
||||
from bigchaindb.models import Transaction
|
||||
user_priv, user_pub = crypto.generate_key_pair()
|
||||
|
||||
tx = Transaction.create([user_pub], [user_pub])
|
||||
tx = Transaction.create([user_pub], [([user_pub], 1)])
|
||||
tx = tx.sign([user_priv])
|
||||
|
||||
res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict()))
|
||||
@ -48,7 +41,7 @@ def test_post_create_transaction_with_invalid_id(b, client):
|
||||
from bigchaindb.models import Transaction
|
||||
user_priv, user_pub = crypto.generate_key_pair()
|
||||
|
||||
tx = Transaction.create([user_pub], [user_pub])
|
||||
tx = Transaction.create([user_pub], [([user_pub], 1)])
|
||||
tx = tx.sign([user_priv]).to_dict()
|
||||
tx['id'] = 'invalid id'
|
||||
|
||||
@ -60,7 +53,7 @@ def test_post_create_transaction_with_invalid_signature(b, client):
|
||||
from bigchaindb.models import Transaction
|
||||
user_priv, user_pub = crypto.generate_key_pair()
|
||||
|
||||
tx = Transaction.create([user_pub], [user_pub])
|
||||
tx = Transaction.create([user_pub], [([user_pub], 1)])
|
||||
tx = tx.sign([user_priv]).to_dict()
|
||||
tx['transaction']['fulfillments'][0]['fulfillment'] = 'invalid signature'
|
||||
|
||||
@ -77,7 +70,8 @@ def test_post_transfer_transaction_endpoint(b, client, user_pk, user_sk):
|
||||
|
||||
input_valid = b.get_owned_ids(user_pk).pop()
|
||||
create_tx = b.get_transaction(input_valid.txid)
|
||||
transfer_tx = Transaction.transfer(create_tx.to_inputs(), [user_pub], create_tx.asset)
|
||||
transfer_tx = Transaction.transfer(create_tx.to_inputs(),
|
||||
[([user_pub], 1)], create_tx.asset)
|
||||
transfer_tx = transfer_tx.sign([user_sk])
|
||||
|
||||
res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict()))
|
||||
@ -94,7 +88,8 @@ def test_post_invalid_transfer_transaction_returns_400(b, client, user_pk, user_
|
||||
|
||||
input_valid = b.get_owned_ids(user_pk).pop()
|
||||
create_tx = b.get_transaction(input_valid.txid)
|
||||
transfer_tx = Transaction.transfer(create_tx.to_inputs(), [user_pub], create_tx.asset)
|
||||
transfer_tx = Transaction.transfer(create_tx.to_inputs(),
|
||||
[([user_pub], 1)], create_tx.asset)
|
||||
|
||||
res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict()))
|
||||
assert res.status_code == 400
|
Loading…
x
Reference in New Issue
Block a user