From 026fc0051e488cf0e2eabf94c592b4e0e577c0f6 Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Wed, 17 Aug 2016 12:00:16 +0200 Subject: [PATCH 01/98] Planning release --- bigchaindb_common.py | 1 + test_bigchaindb_common.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 bigchaindb_common.py create mode 100644 test_bigchaindb_common.py diff --git a/bigchaindb_common.py b/bigchaindb_common.py new file mode 100644 index 00000000..40a96afc --- /dev/null +++ b/bigchaindb_common.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/test_bigchaindb_common.py b/test_bigchaindb_common.py new file mode 100644 index 00000000..ed70f9ea --- /dev/null +++ b/test_bigchaindb_common.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +test_bigchaindb_common +---------------------------------- + +Tests for `bigchaindb_common` module. +""" + +import pytest + + +from bigchaindb_common import bigchaindb_common + + +class TestBigchaindb_common(object): + + @classmethod + def setup_class(cls): + pass + + def test_something(self): + pass + + @classmethod + def teardown_class(cls): + pass + From 1ca9efd2661b80f6c20c25b6bd4e17c7e3593180 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 17 Aug 2016 14:40:10 +0200 Subject: [PATCH 02/98] Clean up after move --- bigchaindb_common.py | 1 - transaction.py | 473 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 473 insertions(+), 1 deletion(-) delete mode 100644 bigchaindb_common.py create mode 100644 transaction.py diff --git a/bigchaindb_common.py b/bigchaindb_common.py deleted file mode 100644 index 40a96afc..00000000 --- a/bigchaindb_common.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/transaction.py b/transaction.py new file mode 100644 index 00000000..c38b2860 --- /dev/null +++ b/transaction.py @@ -0,0 +1,473 @@ +from copy import deepcopy +from functools import reduce +from operator import and_ +from uuid import uuid4 + +from cryptoconditions import ( + Fulfillment as CCFulfillment, + ThresholdSha256Fulfillment, + Ed25519Fulfillment, +) +from cryptoconditions.exceptions import ParsingError + +# TODO: Eventually remove all coupling from the core BigchainDB code base, as this module will life separately. +from bigchaindb.crypto import ( + SigningKey, + hash_data, +) +from bigchaindb.exceptions import ( + KeypairMismatchException, +) +from bigchaindb.util import ( + serialize, + # TODO: Rename function in util to `gen_timestamp` or `create_timestamp` + timestamp as gen_timestamp, +) + + +class Fulfillment(object): + def __init__(self, fulfillment, owners_before=None, fid=0, tx_input=None): + """Create a new fulfillment + + Args: + # TODO: Write a description here + owners_before (Optional(list)): base58 encoded public key of the owners of the asset before this + transaction. + """ + self.fid = fid + # TODO: Check if `fulfillment` corresponds to `owners_before`, otherwise fail + self.fulfillment = fulfillment + + if tx_input is not None and not isinstance(tx_input, TransactionLink): + raise TypeError('`tx_input` must be a TransactionLink instance') + else: + self.tx_input = tx_input + + if not isinstance(owners_before, list): + raise TypeError('`owners_before` must be a list instance') + else: + self.owners_before = owners_before + + def to_dict(self): + try: + # When we have signed the fulfillment, this will work + fulfillment = self.fulfillment.serialize_uri() + except TypeError: + fulfillment = None + + try: + # NOTE: `self.tx_input` can be `None` and that's fine + tx_input = self.tx_input.to_dict() + except AttributeError: + tx_input = None + + return { + 'owners_before': self.owners_before, + 'input': tx_input, + 'fulfillment': fulfillment, + 'details': self.fulfillment.to_dict(), + 'fid': self.fid, + } + + @classmethod + def gen_default(cls, owners_after, fid=0): + """Creates default fulfillments for transactions, depending on how many `owners_after` are supplied. + """ + if not isinstance(owners_after, list): + raise TypeError('`owners_after` must be a list instance') + else: + owners_after_count = len(owners_after) + + if owners_after_count == 0: + # TODO: Replace this error with the logic for a hashlock condition + raise NotImplementedError('Hashlock conditions are not implemented in BigchainDB yet') + elif owners_after_count == 1: + return cls(Ed25519Fulfillment(public_key=owners_after[0]), owners_after, fid) + else: + threshold_ffill = cls(ThresholdSha256Fulfillment(threshold=len(owners_after)), owners_after, fid) + for owner_after in owners_after: + threshold_ffill.fulfillment.add_subfulfillment(Ed25519Fulfillment(public_key=owner_after)) + return threshold_ffill + + def gen_condition(self): + return Condition(self.fulfillment.condition_uri, self.owners_before, self.fid) + + @classmethod + def from_dict(cls, ffill): + """ Serializes a BigchainDB 'jsonized' fulfillment back to a BigchainDB Fulfillment class. + """ + try: + fulfillment = CCFulfillment.from_uri(ffill['fulfillment']) + except TypeError: + fulfillment = CCFulfillment.from_dict(ffill['details']) + return cls(fulfillment, ffill['owners_before'], ffill['fid'], TransactionLink.from_dict(ffill['input'])) + + +class Condition(object): + def __init__(self, condition_uri, owners_after=None, cid=0): + # TODO: Add more description + """Create a new condition for a fulfillment + + Args + owners_after (Optional(list)): base58 encoded public key of the owner of the digital asset after + this transaction. + + """ + self.cid = cid + # TODO: Check if `condition_uri` corresponds to `owners_after`, otherwise fail + self.condition_uri = condition_uri + + if not isinstance(owners_after, list): + raise TypeError('`owners_after` must be a list instance') + else: + self.owners_after = owners_after + + def to_dict(self): + return { + 'owners_after': self.owners_after, + 'condition': self.condition_uri, + 'cid': self.cid + } + + @classmethod + def from_dict(cls, cond): + """ Serializes a BigchainDB 'jsonized' condition back to a BigchainDB Condition class. + """ + return cls(cond['condition'], cond['owners_after'], cond['cid']) + + +class Data(object): + def __init__(self, payload=None, payload_id=None): + self.payload_id = payload_id if payload_id is not None else self.to_hash() + if payload is not None and not isinstance(payload, dict): + raise TypeError('`payload` must be a dict instance or None') + else: + self.payload = payload + + @classmethod + def from_dict(cls, payload): + try: + return cls(payload['payload'], payload['hash']) + except TypeError: + return cls() + + def to_dict(self): + if self.payload is None: + return None + else: + return { + 'payload': self.payload, + 'hash': str(self.payload_id), + } + + def to_hash(self): + return uuid4() + + +class TransactionLink(object): + # NOTE: In an IPLD implementation, this class is not necessary anymore, as an IPLD link can simply point to an + # object, as well as an objects properties. So instead of having a (de)serializable class, we can have a + # simple IPLD link of the form (not clear yet how to address indexes in arrays: + # - https://github.com/ipld/specs/issues/20 + def __init__(self, tx_id=None, cid=None): + self.tx_id = tx_id + self.cid = cid + + @classmethod + def from_dict(cls, link): + return cls(link['tx_id'], link['cid']) + + def to_dict(self): + if self.tx_id is None and self.cid is None: + return None + else: + return { + 'tx_id': self.tx_id, + 'cid': self.cid, + } + + +class Transaction(object): + CREATE = 'CREATE' + TRANSFER = 'TRANSFER' + VERSION = 1 + + def __init__(self, operation, fulfillments=None, conditions=None, data=None, timestamp=None, version=None): + # TODO: Update this comment + """Create a new transaction in memory + + A transaction in BigchainDB is a transfer of a digital asset between two entities represented + by public keys. + + Currently BigchainDB supports two types of operations: + + `CREATE` - Only federation nodes are allowed to use this operation. In a create operation + a federation node creates a digital asset in BigchainDB and assigns that asset to a public + key. The owner of the private key can then decided to transfer this digital asset by using the + `transaction id` of the transaction as an input in a `TRANSFER` transaction. + + `TRANSFER` - A transfer operation allows for a transfer of the digital assets between entities. + + If a transaction is initialized with the inputs being `None` a `operation` `CREATE` is + chosen. Otherwise the transaction is of `operation` `TRANSFER`. + + Args: + # TODO: Write a description here + fulfillments + conditions + operation + data (Optional[dict]): dictionary with information about asset. + + Raises: + TypeError: if the optional ``data`` argument is not a ``dict``. + + # TODO: Incorporate this text somewhere better in the docs of this class + Some use cases for this class: + + 1. Create a new `CREATE` transaction: + - This means `inputs` is empty + + 2. Create a new `TRANSFER` transaction: + - This means `inputs` is a filled list (one to multiple transactions) + + 3. Written transactions must be managed somehow in the user's program: use `from_dict` + + + """ + self.timestamp = timestamp if timestamp is not None else gen_timestamp() + self.version = version if version is not None else Transaction.VERSION + + if operation is not Transaction.CREATE and operation is not Transaction.TRANSFER: + raise TypeError('`operation` must be either CREATE or TRANSFER') + else: + self.operation = operation + + if conditions is not None and not isinstance(conditions, list): + raise TypeError('`conditions` must be a list instance or None') + elif conditions is None: + self.conditions = [] + else: + self.conditions = conditions + + if fulfillments is not None and not isinstance(fulfillments, list): + raise TypeError('`fulfillments` must be a list instance or None') + elif fulfillments is None: + self.fulfillments = [] + else: + self.fulfillments = fulfillments + + # TODO: rename this to data + if data is not None and not isinstance(data, Data): + raise TypeError('`data` must be a Data instance or None') + else: + self.data = data + + # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, + # since for example the Transaction class in BigchainDB doesn't have to sign transactions. + def sign(self, private_keys): + """ Signs a transaction + Acts as a proxy for `_sign_fulfillments`, for exposing a nicer API to the outside. + """ + self._sign_fulfillments(private_keys) + return self + + # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, + # since for example the Transaction class in BigchainDB doesn't have to sign transactions. + def _sign_fulfillments(self, private_keys): + if private_keys is None: + # TODO: Figure out the correct Python error + raise Exception('`private_keys` cannot be None') + if not isinstance(private_keys, list): + private_keys = [private_keys] + + # Generate public keys from private keys and match them in a dictionary: + # key: public_key + # value: private_key + gen_public_key = lambda private_key: private_key.get_verifying_key().to_ascii().decode() + key_pairs = {gen_public_key(SigningKey(private_key)): SigningKey(private_key) for private_key in private_keys} + + # TODO: The condition for a transfer-tx will come from an input + for fulfillment, condition in zip(self.fulfillments, self.conditions): + # NOTE: We clone the current transaction but only add the condition and fulfillment we're currently + # working on. + tx_partial = Transaction(self.operation, [fulfillment], [condition], self.data, self.timestamp, + self.version) + self._sign_fulfillment(fulfillment, str(tx_partial), key_pairs) + + # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, + # since for example the Transaction class in BigchainDB doesn't have to sign transactions. + def _sign_fulfillment(self, fulfillment, tx_serialized, key_pairs): + if isinstance(fulfillment.fulfillment, Ed25519Fulfillment): + self._fulfill_simple_signature_fulfillment(fulfillment, tx_serialized, key_pairs) + elif isinstance(fulfillment.fulfillment, ThresholdSha256Fulfillment): + self._fulfill_threshold_signature_fulfillment(fulfillment, tx_serialized, key_pairs) + + # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, + # since for example the Transaction class in BigchainDB doesn't have to sign transactions. + def _fulfill_simple_signature_fulfillment(self, fulfillment, tx_serialized, key_pairs): + # TODO: Update comment + """Fulfill a cryptoconditions.Ed25519Fulfillment + + Args: + fulfillment (dict): BigchainDB fulfillment to fulfill. + parsed_fulfillment (cryptoconditions.Ed25519Fulfillment): cryptoconditions.Ed25519Fulfillment instance. + fulfillment_message (dict): message to sign. + key_pairs (dict): dictionary of (public_key, private_key) pairs. + + Returns: + object: fulfilled cryptoconditions.Ed25519Fulfillment + + """ + owner_before = fulfillment.owners_before[0] + try: + # NOTE: By signing the CC fulfillment here directly, we're changing the transactions's fulfillment by + # reference, and that's good :) + fulfillment.fulfillment.sign(tx_serialized, key_pairs[owner_before]) + except KeyError: + raise KeypairMismatchException('Public key {} is not a pair to any of the private keys' + .format(owner_before)) + + # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, + # since for example the Transaction class in BigchainDB doesn't have to sign transactions. + def _fulfill_threshold_signature_fulfillment(self, fulfillment, tx_serialized, key_pairs): + # TODO: Update comment + """Fulfill a cryptoconditions.ThresholdSha256Fulfillment + + Args: + fulfillment (dict): BigchainDB fulfillment to fulfill. + parsed_fulfillment (ThresholdSha256Fulfillment): ThresholdSha256Fulfillment instance. + fulfillment_message (dict): message to sign. + key_pairs (dict): dictionary of (public_key, private_key) pairs. + + Returns: + object: fulfilled cryptoconditions.ThresholdSha256Fulfillment + + """ + for owner_before in fulfillment.owners_before: + try: + # TODO: CC should throw a KeypairMismatchException, instead of our manual mapping here + # TODO FOR CC: Naming wise this is not so smart, `get_subcondition` in fact doesn't return a condition + # but a fulfillment:( + # TODO FOR CC: `get_subcondition` is singular. One would not expect to get a list back. + subfulfillment = fulfillment.fulfillment.get_subcondition_from_vk(owner_before)[0] + except IndexError: + raise KeypairMismatchException('Public key {} cannot be found in the fulfillment' + .format(owner_before)) + try: + private_key = key_pairs[owner_before] + except KeyError: + raise KeypairMismatchException('Public key {} is not a pair to any of the private keys' + .format(owner_before)) + + subfulfillment.sign(tx_serialized, private_key) + + def fulfillments_valid(self): + # TODO: Update Comment + """Verify the signature of a transaction + + A valid transaction should have been signed `current_owner` corresponding private key. + + Args: + signed_transaction (dict): a transaction with the `signature` included. + + Returns: + bool: True if the signature is correct, False otherwise. + """ + zipped_io = list(zip(self.fulfillments, self.conditions)) + + if len(zipped_io) > 1: + # TODO: The condition for a transfer-tx will come from an input + gen_tx = lambda ffill, cond: Transaction(self.operation, [ffill], [cond], self.data, self.timestamp, + self.version).fulfillments_valid() + return reduce(and_, map(gen_tx, zipped_io)) + else: + return self._fulfillment_valid() + + def _fulfillment_valid(self): + # NOTE: We're always taking the first fulfillment, as this method is called recursively. + # See: `fulfillments_valid` + fulfillment = self.fulfillments[0].fulfillment + + try: + parsed_fulfillment = CCFulfillment.from_uri(fulfillment.serialize_uri()) + # TODO: Figure out if we need all three of those errors here + except (TypeError, ValueError, ParsingError): + return False + + # TODO: For transfer-transaction, we'll also have to validate against the given condition + # NOTE: We pass a timestamp here, as in case of a timeout condition we'll have to validate against it. + return parsed_fulfillment.validate(message=Transaction._to_str(Transaction._remove_signatures(self.to_dict())), + now=gen_timestamp()) + + def transfer(self, conditions): + return Transaction(Transaction.TRANSFER, self._fulfillments_as_inputs(), conditions) + + def _fulfillments_as_inputs(self): + return [Fulfillment(ffill.fulfillment, + ffill.owners_before, + ffill.fid, + TransactionLink(self.to_hash(), ffill.fid)) + for ffill in self.fulfillments] + + def to_dict(self): + try: + data = self.data.to_dict() + except AttributeError: + # NOTE: data can be None and that's OK + data = None + + tx_body = { + 'fulfillments': [fulfillment.to_dict() for fulfillment in self.fulfillments], + 'conditions': [condition.to_dict() for condition in self.conditions], + 'operation': str(self.operation), + 'timestamp': self.timestamp, + 'data': data, + } + tx = { + 'version': self.version, + 'transaction': tx_body, + } + + tx_id = Transaction._to_hash(Transaction._to_str(Transaction._remove_signatures(tx))) + tx['id'] = tx_id + + return tx + + @staticmethod + def _remove_signatures(tx_dict): + # NOTE: Remove reference since we need `tx_dict` only for the transaction's hash + tx_dict = deepcopy(tx_dict) + for fulfillment in tx_dict['transaction']['fulfillments']: + # NOTE: Not all Cryptoconditions return a `signature` key (e.g. ThresholdSha256Fulfillment), so setting it + # to `None` in any case could yield incorrect signatures. This is why we only set it to `None` if + # it's set in the dict. + if 'signature' in fulfillment['details']: + fulfillment['details']['signature'] = None + fulfillment['fulfillment'] = None + try: + for subfulfillment in fulfillment['details']['subfulfillments']: + subfulfillment['signature'] = None + except KeyError: + pass + return tx_dict + + @staticmethod + def _to_hash(value): + return hash_data(value) + + def to_hash(self): + return self.to_dict()['id'] + + @staticmethod + def _to_str(value): + return serialize(value) + + def __str__(self): + return Transaction._to_str(self.to_dict()) + + @classmethod + def from_dict(cls, tx_body): + tx = tx_body['transaction'] + return cls(tx['operation'], [Fulfillment.from_dict(fulfillment) for fulfillment in tx['fulfillments']], + [Condition.from_dict(condition) for condition in tx['conditions']], Data.from_dict(tx['data']), + tx['timestamp'], tx_body['version']) From bb603ff6ebd4bdbec57d285c017b60530718e855 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 17 Aug 2016 14:48:10 +0200 Subject: [PATCH 03/98] Add exceptions.py --- exceptions.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 exceptions.py diff --git a/exceptions.py b/exceptions.py new file mode 100644 index 00000000..480a0bd8 --- /dev/null +++ b/exceptions.py @@ -0,0 +1,62 @@ +"""Custom exceptions used in the `bigchaindb` package. +""" + + +class OperationError(Exception): + """Raised when an operation cannot go through""" + + +class TransactionDoesNotExist(Exception): + """Raised if the transaction is not in the database""" + + +class TransactionOwnerError(Exception): + """Raised if a user tries to transfer a transaction they don't own""" + + +class DoubleSpend(Exception): + """Raised if a double spend is found""" + + +class InvalidHash(Exception): + """Raised if there was an error checking the hash for a particular operation""" + + +class InvalidSignature(Exception): + """Raised if there was an error checking the signature for a particular operation""" + + +class DatabaseAlreadyExists(Exception): + """Raised when trying to create the database but the db is already there""" + + +class DatabaseDoesNotExist(Exception): + """Raised when trying to delete the database but the db is not there""" + + +class KeypairNotFoundException(Exception): + """Raised if operation cannot proceed because the keypair was not given""" + + +class KeypairMismatchException(Exception): + """Raised if the private key(s) provided for signing don't match any of the curret owner(s)""" + + +class StartupError(Exception): + """Raised when there is an error starting up the system""" + + +class ImproperVoteError(Exception): + """Raised if a vote is not constructed correctly, or signed incorrectly""" + + +class MultipleVotesError(Exception): + """Raised if a voter has voted more than once""" + + +class GenesisBlockAlreadyExistsError(Exception): + """Raised when trying to create the already existing genesis block""" + + +class CyclicBlockchainError(Exception): + """Raised when there is a cycle in the blockchain""" From 1aa29abc25008ae68777f4502576ff169fb62730 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 17 Aug 2016 14:51:15 +0200 Subject: [PATCH 04/98] Add crypto.py --- crypto.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 crypto.py diff --git a/crypto.py b/crypto.py new file mode 100644 index 00000000..96be68e2 --- /dev/null +++ b/crypto.py @@ -0,0 +1,17 @@ +# Separate all crypto code so that we can easily test several implementations + +import sha3 +from cryptoconditions import crypto + + +def hash_data(data): + """Hash the provided data using SHA3-256""" + return sha3.sha3_256(data.encode()).hexdigest() + + +def generate_key_pair(): + sk, pk = crypto.ed25519_generate_key_pair() + return sk.decode(), pk.decode() + +SigningKey = crypto.Ed25519SigningKey +VerifyingKey = crypto.Ed25519VerifyingKey From ee838c1329c0f756c378c78fd6cfa9d4cd1d47d7 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 17 Aug 2016 15:05:16 +0200 Subject: [PATCH 05/98] Adjust setup to package structure --- transaction.py | 9 ++++----- util.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 util.py diff --git a/transaction.py b/transaction.py index c38b2860..851bc79f 100644 --- a/transaction.py +++ b/transaction.py @@ -11,17 +11,16 @@ from cryptoconditions import ( from cryptoconditions.exceptions import ParsingError # TODO: Eventually remove all coupling from the core BigchainDB code base, as this module will life separately. -from bigchaindb.crypto import ( +from bigchaindb_common.crypto import ( SigningKey, hash_data, ) -from bigchaindb.exceptions import ( +from bigchaindb_common.exceptions import ( KeypairMismatchException, ) -from bigchaindb.util import ( +from bigchaindb_common.util import ( serialize, - # TODO: Rename function in util to `gen_timestamp` or `create_timestamp` - timestamp as gen_timestamp, + gen_timestamp, ) diff --git a/util.py b/util.py new file mode 100644 index 00000000..3f96f413 --- /dev/null +++ b/util.py @@ -0,0 +1,33 @@ +import time + +import rapidjson + + +def gen_timestamp(): + """The Unix time, rounded to the nearest second. + See https://en.wikipedia.org/wiki/Unix_time + + Returns: + str: the Unix time + """ + return str(round(time.time())) + + +def serialize(data): + """Serialize a dict into a JSON formatted string. + + This function enforces rules like the separator and order of keys. This ensures that all dicts + are serialized in the same way. + + This is specially important for hashing data. We need to make sure that everyone serializes their data + in the same way so that we do not have hash mismatches for the same structure due to serialization + differences. + + Args: + data (dict): dict to serialize + + Returns: + str: JSON formatted string + + """ + return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, sort_keys=True) From cc4a466fece2933fdb0a9070e60034fe2f7403d4 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 17 Aug 2016 15:29:13 +0200 Subject: [PATCH 06/98] Fix tests --- transaction.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/transaction.py b/transaction.py index 851bc79f..32096ce7 100644 --- a/transaction.py +++ b/transaction.py @@ -174,7 +174,10 @@ class TransactionLink(object): @classmethod def from_dict(cls, link): - return cls(link['tx_id'], link['cid']) + try: + return cls(link['tx_id'], link['cid']) + except TypeError: + return cls() def to_dict(self): if self.tx_id is None and self.cid is None: From a0a4a95b7704484178f7e1642cd44605ada0a004 Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 18 Aug 2016 10:44:05 +0200 Subject: [PATCH 07/98] Add test coverage --- transaction.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/transaction.py b/transaction.py index 32096ce7..70737f79 100644 --- a/transaction.py +++ b/transaction.py @@ -10,7 +10,6 @@ from cryptoconditions import ( ) from cryptoconditions.exceptions import ParsingError -# TODO: Eventually remove all coupling from the core BigchainDB code base, as this module will life separately. from bigchaindb_common.crypto import ( SigningKey, hash_data, @@ -166,8 +165,7 @@ class Data(object): class TransactionLink(object): # NOTE: In an IPLD implementation, this class is not necessary anymore, as an IPLD link can simply point to an # object, as well as an objects properties. So instead of having a (de)serializable class, we can have a - # simple IPLD link of the form (not clear yet how to address indexes in arrays: - # - https://github.com/ipld/specs/issues/20 + # simple IPLD link of the form: `//transaction/conditions//` def __init__(self, tx_id=None, cid=None): self.tx_id = tx_id self.cid = cid @@ -258,12 +256,23 @@ class Transaction(object): else: self.fulfillments = fulfillments - # TODO: rename this to data if data is not None and not isinstance(data, Data): raise TypeError('`data` must be a Data instance or None') else: self.data = data + def add_fulfillment(self, fulfillment): + if fulfillment is not None and not isinstance(fulfillment, Fulfillment): + raise TypeError('`fulfillment` must be a Fulfillment instance or None') + else: + self.fulfillments.append(fulfillment) + + def add_condition(self, condition): + if condition is not None and not isinstance(condition, Condition): + raise TypeError('`condition` must be a Condition instance or None') + else: + self.conditions.append(condition) + # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, # since for example the Transaction class in BigchainDB doesn't have to sign transactions. def sign(self, private_keys): @@ -276,11 +285,8 @@ class Transaction(object): # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, # since for example the Transaction class in BigchainDB doesn't have to sign transactions. def _sign_fulfillments(self, private_keys): - if private_keys is None: - # TODO: Figure out the correct Python error - raise Exception('`private_keys` cannot be None') - if not isinstance(private_keys, list): - private_keys = [private_keys] + if private_keys is None or not isinstance(private_keys, list): + raise TypeError('`private_keys` cannot be None') # Generate public keys from private keys and match them in a dictionary: # key: public_key @@ -291,7 +297,7 @@ class Transaction(object): # TODO: The condition for a transfer-tx will come from an input for fulfillment, condition in zip(self.fulfillments, self.conditions): # NOTE: We clone the current transaction but only add the condition and fulfillment we're currently - # working on. + # working on plus all previously signed ones. tx_partial = Transaction(self.operation, [fulfillment], [condition], self.data, self.timestamp, self.version) self._sign_fulfillment(fulfillment, str(tx_partial), key_pairs) @@ -375,13 +381,11 @@ class Transaction(object): Returns: bool: True if the signature is correct, False otherwise. """ - zipped_io = list(zip(self.fulfillments, self.conditions)) - - if len(zipped_io) > 1: + if len(self.fulfillments) > 1 and len(self.conditions) > 1: # TODO: The condition for a transfer-tx will come from an input gen_tx = lambda ffill, cond: Transaction(self.operation, [ffill], [cond], self.data, self.timestamp, self.version).fulfillments_valid() - return reduce(and_, map(gen_tx, zipped_io)) + return reduce(and_, map(gen_tx, self.fulfillments, self.conditions)) else: return self._fulfillment_valid() From 43e876650cff459da4188ac0bc562676d76c811d Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 18 Aug 2016 11:33:30 +0200 Subject: [PATCH 08/98] Comply to flake8 --- transaction.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/transaction.py b/transaction.py index 70737f79..150596c5 100644 --- a/transaction.py +++ b/transaction.py @@ -291,7 +291,8 @@ class Transaction(object): # Generate public keys from private keys and match them in a dictionary: # key: public_key # value: private_key - gen_public_key = lambda private_key: private_key.get_verifying_key().to_ascii().decode() + def gen_public_key(private_key): + return private_key.get_verifying_key().to_ascii().decode() key_pairs = {gen_public_key(SigningKey(private_key)): SigningKey(private_key) for private_key in private_keys} # TODO: The condition for a transfer-tx will come from an input @@ -382,9 +383,9 @@ class Transaction(object): bool: True if the signature is correct, False otherwise. """ if len(self.fulfillments) > 1 and len(self.conditions) > 1: - # TODO: The condition for a transfer-tx will come from an input - gen_tx = lambda ffill, cond: Transaction(self.operation, [ffill], [cond], self.data, self.timestamp, - self.version).fulfillments_valid() + def gen_tx(fulfillment, condition): + return Transaction(self.operation, [fulfillment], [condition], self.data, self.timestamp, + self.version).fulfillments_valid() return reduce(and_, map(gen_tx, self.fulfillments, self.conditions)) else: return self._fulfillment_valid() From dd51a0bcd81b127e72f2e8a0270ae070b880ee19 Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 18 Aug 2016 14:54:44 +0200 Subject: [PATCH 09/98] Add test coverage --- crypto.py | 5 +++-- transaction.py | 11 ++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crypto.py b/crypto.py index 96be68e2..e440f81d 100644 --- a/crypto.py +++ b/crypto.py @@ -10,8 +10,9 @@ def hash_data(data): def generate_key_pair(): - sk, pk = crypto.ed25519_generate_key_pair() - return sk.decode(), pk.decode() + # TODO FOR CC: Adjust interface so that this function becomes unnecessary + private_key, public_key = crypto.ed25519_generate_key_pair() + return private_key.decode(), public_key.decode() SigningKey = crypto.Ed25519SigningKey VerifyingKey = crypto.Ed25519VerifyingKey diff --git a/transaction.py b/transaction.py index 150596c5..bc160d07 100644 --- a/transaction.py +++ b/transaction.py @@ -286,12 +286,13 @@ class Transaction(object): # since for example the Transaction class in BigchainDB doesn't have to sign transactions. def _sign_fulfillments(self, private_keys): if private_keys is None or not isinstance(private_keys, list): - raise TypeError('`private_keys` cannot be None') + raise TypeError('`private_keys` must be a list instance') # Generate public keys from private keys and match them in a dictionary: # key: public_key # value: private_key def gen_public_key(private_key): + # TODO FOR CC: Adjust interface so that this function becomes unnecessary return private_key.get_verifying_key().to_ascii().decode() key_pairs = {gen_public_key(SigningKey(private_key)): SigningKey(private_key) for private_key in private_keys} @@ -307,13 +308,13 @@ class Transaction(object): # since for example the Transaction class in BigchainDB doesn't have to sign transactions. def _sign_fulfillment(self, fulfillment, tx_serialized, key_pairs): if isinstance(fulfillment.fulfillment, Ed25519Fulfillment): - self._fulfill_simple_signature_fulfillment(fulfillment, tx_serialized, key_pairs) + self._sign_simple_signature_fulfillment(fulfillment, tx_serialized, key_pairs) elif isinstance(fulfillment.fulfillment, ThresholdSha256Fulfillment): - self._fulfill_threshold_signature_fulfillment(fulfillment, tx_serialized, key_pairs) + self._sign_threshold_signature_fulfillment(fulfillment, tx_serialized, key_pairs) # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, # since for example the Transaction class in BigchainDB doesn't have to sign transactions. - def _fulfill_simple_signature_fulfillment(self, fulfillment, tx_serialized, key_pairs): + def _sign_simple_signature_fulfillment(self, fulfillment, tx_serialized, key_pairs): # TODO: Update comment """Fulfill a cryptoconditions.Ed25519Fulfillment @@ -338,7 +339,7 @@ class Transaction(object): # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, # since for example the Transaction class in BigchainDB doesn't have to sign transactions. - def _fulfill_threshold_signature_fulfillment(self, fulfillment, tx_serialized, key_pairs): + def _sign_threshold_signature_fulfillment(self, fulfillment, tx_serialized, key_pairs): # TODO: Update comment """Fulfill a cryptoconditions.ThresholdSha256Fulfillment From f0fc3c424384ca68393324a6fe7c22c323fdc46b Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 19 Aug 2016 10:42:20 +0200 Subject: [PATCH 10/98] Transfer-tx fulfillments validation --- transaction.py | 110 +++++++++++++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 41 deletions(-) diff --git a/transaction.py b/transaction.py index bc160d07..bf0de622 100644 --- a/transaction.py +++ b/transaction.py @@ -101,6 +101,31 @@ class Fulfillment(object): return cls(fulfillment, ffill['owners_before'], ffill['fid'], TransactionLink.from_dict(ffill['input'])) +class TransactionLink(object): + # NOTE: In an IPLD implementation, this class is not necessary anymore, as an IPLD link can simply point to an + # object, as well as an objects properties. So instead of having a (de)serializable class, we can have a + # simple IPLD link of the form: `//transaction/conditions//` + def __init__(self, tx_id=None, cid=None): + self.tx_id = tx_id + self.cid = cid + + @classmethod + def from_dict(cls, link): + try: + return cls(link['tx_id'], link['cid']) + except TypeError: + return cls() + + def to_dict(self): + if self.tx_id is None and self.cid is None: + return None + else: + return { + 'tx_id': self.tx_id, + 'cid': self.cid, + } + + class Condition(object): def __init__(self, condition_uri, owners_after=None, cid=0): # TODO: Add more description @@ -162,31 +187,6 @@ class Data(object): return uuid4() -class TransactionLink(object): - # NOTE: In an IPLD implementation, this class is not necessary anymore, as an IPLD link can simply point to an - # object, as well as an objects properties. So instead of having a (de)serializable class, we can have a - # simple IPLD link of the form: `//transaction/conditions//` - def __init__(self, tx_id=None, cid=None): - self.tx_id = tx_id - self.cid = cid - - @classmethod - def from_dict(cls, link): - try: - return cls(link['tx_id'], link['cid']) - except TypeError: - return cls() - - def to_dict(self): - if self.tx_id is None and self.cid is None: - return None - else: - return { - 'tx_id': self.tx_id, - 'cid': self.cid, - } - - class Transaction(object): CREATE = 'CREATE' TRANSFER = 'TRANSFER' @@ -371,7 +371,7 @@ class Transaction(object): subfulfillment.sign(tx_serialized, private_key) - def fulfillments_valid(self): + def fulfillments_valid(self, input_condition_uris=None): # TODO: Update Comment """Verify the signature of a transaction @@ -383,29 +383,57 @@ class Transaction(object): Returns: bool: True if the signature is correct, False otherwise. """ - if len(self.fulfillments) > 1 and len(self.conditions) > 1: - def gen_tx(fulfillment, condition): - return Transaction(self.operation, [fulfillment], [condition], self.data, self.timestamp, - self.version).fulfillments_valid() - return reduce(and_, map(gen_tx, self.fulfillments, self.conditions)) - else: - return self._fulfillment_valid() + if not isinstance(input_condition_uris, list) and input_condition_uris is not None: + raise TypeError('`input_condition_uris` must be list instance') + elif input_condition_uris is None: + input_condition_uris = [] - def _fulfillment_valid(self): + 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_uris=None): + return Transaction(self.operation, [fulfillment], [condition], self.data, self.timestamp, + self.version).fulfillments_valid(input_condition_uris) + + if self.operation is Transaction.CREATE: + if not fulfillments_count == conditions_count: + raise ValueError('Fulfillments, conditions must have the same count') + elif fulfillments_count > 1 and conditions_count > 1: + return reduce(and_, map(gen_tx, self.fulfillments, self.conditions)) + else: + return self._fulfillment_valid() + elif self.operation is Transaction.TRANSFER: + if not fulfillments_count == conditions_count == input_condition_uris_count: + raise ValueError('Fulfillments, conditions and input_condition_uris must have the same count') + elif fulfillments_count > 1 and conditions_count > 1 and input_condition_uris > 1: + return reduce(and_, map(gen_tx, self.fulfillments, self.conditions, input_condition_uris)) + else: + return self._fulfillment_valid(input_condition_uris.pop()) + else: + raise TypeError('`operation` must be either `Transaction.TRANSFER` or `Transaction.CREATE`') + + def _fulfillment_valid(self, input_condition_uri=None): # NOTE: We're always taking the first fulfillment, as this method is called recursively. # See: `fulfillments_valid` - fulfillment = self.fulfillments[0].fulfillment + fulfillment = self.fulfillments[0] try: - parsed_fulfillment = CCFulfillment.from_uri(fulfillment.serialize_uri()) - # TODO: Figure out if we need all three of those errors here + parsed_fulfillment = CCFulfillment.from_uri(fulfillment.fulfillment.serialize_uri()) except (TypeError, ValueError, ParsingError): return False - # TODO: For transfer-transaction, we'll also have to validate against the given condition - # NOTE: We pass a timestamp here, as in case of a timeout condition we'll have to validate against it. - return parsed_fulfillment.validate(message=Transaction._to_str(Transaction._remove_signatures(self.to_dict())), - now=gen_timestamp()) + if self.operation == Transaction.CREATE: + input_condition_valid = True + else: + # NOTE: When passing a `TRANSFER` transaction for validation, we check if it's valid by validating its + # input condition (taken from previous transaction) against the current fulfillment. + input_condition_valid = input_condition_uri == fulfillment.fulfillment.condition_uri + + tx_serialized = Transaction._to_str(Transaction._remove_signatures(self.to_dict())) + # NOTE: We pass a timestamp to `.validate`, as in case of a timeout condition we'll have to validate against + # it. + return parsed_fulfillment.validate(message=tx_serialized, now=gen_timestamp()) and input_condition_valid def transfer(self, conditions): return Transaction(Transaction.TRANSFER, self._fulfillments_as_inputs(), conditions) From 1798c623e3e4b51e8fb5e612d252b97ab1533f9c Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 19 Aug 2016 11:39:34 +0200 Subject: [PATCH 11/98] Remove condition and fulfillment ids --- transaction.py | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/transaction.py b/transaction.py index bf0de622..bd3c7aea 100644 --- a/transaction.py +++ b/transaction.py @@ -24,7 +24,7 @@ from bigchaindb_common.util import ( class Fulfillment(object): - def __init__(self, fulfillment, owners_before=None, fid=0, tx_input=None): + def __init__(self, fulfillment, owners_before=None, tx_input=None): """Create a new fulfillment Args: @@ -32,7 +32,6 @@ class Fulfillment(object): owners_before (Optional(list)): base58 encoded public key of the owners of the asset before this transaction. """ - self.fid = fid # TODO: Check if `fulfillment` corresponds to `owners_before`, otherwise fail self.fulfillment = fulfillment @@ -64,11 +63,10 @@ class Fulfillment(object): 'input': tx_input, 'fulfillment': fulfillment, 'details': self.fulfillment.to_dict(), - 'fid': self.fid, } @classmethod - def gen_default(cls, owners_after, fid=0): + def gen_default(cls, owners_after): """Creates default fulfillments for transactions, depending on how many `owners_after` are supplied. """ if not isinstance(owners_after, list): @@ -80,15 +78,15 @@ class Fulfillment(object): # TODO: Replace this error with the logic for a hashlock condition raise NotImplementedError('Hashlock conditions are not implemented in BigchainDB yet') elif owners_after_count == 1: - return cls(Ed25519Fulfillment(public_key=owners_after[0]), owners_after, fid) + return cls(Ed25519Fulfillment(public_key=owners_after[0]), owners_after) else: - threshold_ffill = cls(ThresholdSha256Fulfillment(threshold=len(owners_after)), owners_after, fid) + threshold_ffill = cls(ThresholdSha256Fulfillment(threshold=len(owners_after)), owners_after) for owner_after in owners_after: threshold_ffill.fulfillment.add_subfulfillment(Ed25519Fulfillment(public_key=owner_after)) return threshold_ffill def gen_condition(self): - return Condition(self.fulfillment.condition_uri, self.owners_before, self.fid) + return Condition(self.fulfillment.condition_uri, self.owners_before) @classmethod def from_dict(cls, ffill): @@ -98,36 +96,36 @@ class Fulfillment(object): fulfillment = CCFulfillment.from_uri(ffill['fulfillment']) except TypeError: fulfillment = CCFulfillment.from_dict(ffill['details']) - return cls(fulfillment, ffill['owners_before'], ffill['fid'], TransactionLink.from_dict(ffill['input'])) + return cls(fulfillment, ffill['owners_before'], TransactionLink.from_dict(ffill['input'])) class TransactionLink(object): # NOTE: In an IPLD implementation, this class is not necessary anymore, as an IPLD link can simply point to an # object, as well as an objects properties. So instead of having a (de)serializable class, we can have a # simple IPLD link of the form: `//transaction/conditions//` - def __init__(self, tx_id=None, cid=None): - self.tx_id = tx_id - self.cid = cid + def __init__(self, transaction_id=None, condition_id=None): + self.transaction_id = transaction_id + self.condition_id = condition_id @classmethod def from_dict(cls, link): try: - return cls(link['tx_id'], link['cid']) + return cls(link['transaction_id'], link['condition_id']) except TypeError: return cls() def to_dict(self): - if self.tx_id is None and self.cid is None: + if self.transaction_id is None and self.condition_id is None: return None else: return { - 'tx_id': self.tx_id, - 'cid': self.cid, + 'transaction_id': self.transaction_id, + 'condition_id': self.condition_id, } class Condition(object): - def __init__(self, condition_uri, owners_after=None, cid=0): + def __init__(self, condition_uri, owners_after=None): # TODO: Add more description """Create a new condition for a fulfillment @@ -136,7 +134,6 @@ class Condition(object): this transaction. """ - self.cid = cid # TODO: Check if `condition_uri` corresponds to `owners_after`, otherwise fail self.condition_uri = condition_uri @@ -149,14 +146,13 @@ class Condition(object): return { 'owners_after': self.owners_after, 'condition': self.condition_uri, - 'cid': self.cid } @classmethod def from_dict(cls, cond): """ Serializes a BigchainDB 'jsonized' condition back to a BigchainDB Condition class. """ - return cls(cond['condition'], cond['owners_after'], cond['cid']) + return cls(cond['condition'], cond['owners_after']) class Data(object): @@ -441,9 +437,8 @@ class Transaction(object): def _fulfillments_as_inputs(self): return [Fulfillment(ffill.fulfillment, ffill.owners_before, - ffill.fid, - TransactionLink(self.to_hash(), ffill.fid)) - for ffill in self.fulfillments] + TransactionLink(self.to_hash(), fulfillment_id)) + for fulfillment_id, ffill in enumerate(self.fulfillments)] def to_dict(self): try: @@ -471,7 +466,7 @@ class Transaction(object): @staticmethod def _remove_signatures(tx_dict): - # NOTE: Remove reference since we need `tx_dict` only for the transaction's hash + # NOTE: We remove the reference since we need `tx_dict` only for the transaction's hash tx_dict = deepcopy(tx_dict) for fulfillment in tx_dict['transaction']['fulfillments']: # NOTE: Not all Cryptoconditions return a `signature` key (e.g. ThresholdSha256Fulfillment), so setting it From 45a946fc241cb8399b6b56912236174f0a30ece4 Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 19 Aug 2016 13:59:49 +0200 Subject: [PATCH 12/98] Fix signing logic Specifically for transfer-tx with multiple inputs and outputs. --- transaction.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/transaction.py b/transaction.py index bd3c7aea..5fb77fa0 100644 --- a/transaction.py +++ b/transaction.py @@ -32,7 +32,7 @@ class Fulfillment(object): owners_before (Optional(list)): base58 encoded public key of the owners of the asset before this transaction. """ - # TODO: Check if `fulfillment` corresponds to `owners_before`, otherwise fail + # TODO: Derive `owner_before` from fulfillment self.fulfillment = fulfillment if tx_input is not None and not isinstance(tx_input, TransactionLink): @@ -134,7 +134,7 @@ class Condition(object): this transaction. """ - # TODO: Check if `condition_uri` corresponds to `owners_after`, otherwise fail + # TODO: Derive `owner_after` from condition self.condition_uri = condition_uri if not isinstance(owners_after, list): @@ -298,7 +298,8 @@ class Transaction(object): # working on plus all previously signed ones. tx_partial = Transaction(self.operation, [fulfillment], [condition], self.data, self.timestamp, self.version) - self._sign_fulfillment(fulfillment, str(tx_partial), key_pairs) + tx_serialized = Transaction._to_str(Transaction._remove_signatures(tx_partial.to_dict())) + self._sign_fulfillment(fulfillment, tx_serialized, key_pairs) # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, # since for example the Transaction class in BigchainDB doesn't have to sign transactions. @@ -388,9 +389,12 @@ class Transaction(object): fulfillments_count = len(self.fulfillments) conditions_count = len(self.conditions) - def gen_tx(fulfillment, condition, input_condition_uris=None): - return Transaction(self.operation, [fulfillment], [condition], self.data, self.timestamp, - self.version).fulfillments_valid(input_condition_uris) + def gen_tx(fulfillment, condition, input_condition_uri=None): + tx = Transaction(self.operation, [fulfillment], [condition], self.data, self.timestamp, self.version) + if input_condition_uri is not None: + return tx.fulfillments_valid([input_condition_uri]) + else: + return tx.fulfillments_valid() if self.operation is Transaction.CREATE: if not fulfillments_count == conditions_count: @@ -402,7 +406,7 @@ class Transaction(object): elif self.operation is Transaction.TRANSFER: if not fulfillments_count == conditions_count == input_condition_uris_count: raise ValueError('Fulfillments, conditions and input_condition_uris must have the same count') - elif fulfillments_count > 1 and conditions_count > 1 and input_condition_uris > 1: + elif fulfillments_count > 1 and conditions_count > 1 and input_condition_uris_count > 1: return reduce(and_, map(gen_tx, self.fulfillments, self.conditions, input_condition_uris)) else: return self._fulfillment_valid(input_condition_uris.pop()) From b2b0f56e40b616fd2ce20ed7d5c556b022d28af4 Mon Sep 17 00:00:00 2001 From: tim Date: Tue, 23 Aug 2016 20:08:51 +0200 Subject: [PATCH 13/98] Compliance to legacy BDB models --- exceptions.py | 4 +++ transaction.py | 72 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 22 deletions(-) diff --git a/exceptions.py b/exceptions.py index 480a0bd8..1b6d84d4 100644 --- a/exceptions.py +++ b/exceptions.py @@ -2,6 +2,10 @@ """ +class ConfigurationError(Exception): + """Raised when there is a problem with server configuration""" + + class OperationError(Exception): """Raised when an operation cannot go through""" diff --git a/transaction.py b/transaction.py index 5fb77fa0..8d678f6d 100644 --- a/transaction.py +++ b/transaction.py @@ -103,24 +103,30 @@ class TransactionLink(object): # NOTE: In an IPLD implementation, this class is not necessary anymore, as an IPLD link can simply point to an # object, as well as an objects properties. So instead of having a (de)serializable class, we can have a # simple IPLD link of the form: `//transaction/conditions//` - def __init__(self, transaction_id=None, condition_id=None): - self.transaction_id = transaction_id - self.condition_id = condition_id + def __init__(self, txid=None, cid=None): + self.txid = txid + self.cid = cid + + def is_defined(self): + if self.txid is None and self.cid is None: + return False + else: + return True @classmethod def from_dict(cls, link): try: - return cls(link['transaction_id'], link['condition_id']) + return cls(link['txid'], link['cid']) except TypeError: return cls() def to_dict(self): - if self.transaction_id is None and self.condition_id is None: + if self.txid is None and self.cid is None: return None else: return { - 'transaction_id': self.transaction_id, - 'condition_id': self.condition_id, + 'txid': self.txid, + 'cid': self.cid, } @@ -166,7 +172,7 @@ class Data(object): @classmethod def from_dict(cls, payload): try: - return cls(payload['payload'], payload['hash']) + return cls(payload['payload'], payload['uuid']) except TypeError: return cls() @@ -176,16 +182,18 @@ class Data(object): else: return { 'payload': self.payload, - 'hash': str(self.payload_id), + 'uuid': self.payload_id, } def to_hash(self): - return uuid4() + return str(uuid4()) class Transaction(object): CREATE = 'CREATE' TRANSFER = 'TRANSFER' + GENESIS = 'GENESIS' + ALLOWED_OPERATIONS = (CREATE, TRANSFER, GENESIS) VERSION = 1 def __init__(self, operation, fulfillments=None, conditions=None, data=None, timestamp=None, version=None): @@ -233,7 +241,7 @@ class Transaction(object): self.timestamp = timestamp if timestamp is not None else gen_timestamp() self.version = version if version is not None else Transaction.VERSION - if operation is not Transaction.CREATE and operation is not Transaction.TRANSFER: + if operation not in Transaction.ALLOWED_OPERATIONS: raise TypeError('`operation` must be either CREATE or TRANSFER') else: self.operation = operation @@ -257,6 +265,31 @@ class Transaction(object): else: self.data = data + @classmethod + def create(cls, owners_before, owners_after, inputs, operation, payload=None): + if operation == Transaction.CREATE or operation == Transaction.GENESIS: + ffill = Fulfillment.gen_default(owners_after) + cond = ffill.gen_condition() + return cls(operation, [ffill], [cond], Data(payload)) + else: + # TODO: Replace this with an actual implementation, maybe calling + # `self.transfer` is sufficient already :) + raise NotImplementedError() + + def transfer(self, conditions): + # TODO: Check here if a condition is submitted or smth else + return Transaction(Transaction.TRANSFER, self._fulfillments_as_inputs(), conditions) + + def simple_transfer(self, owners_after): + condition = Fulfillment.gen_default(owners_after).gen_condition() + return self.transfer([condition]) + + def _fulfillments_as_inputs(self): + return [Fulfillment(ffill.fulfillment, + ffill.owners_before, + TransactionLink(self.to_hash(), fulfillment_id)) + for fulfillment_id, ffill in enumerate(self.fulfillments)] + def add_fulfillment(self, fulfillment): if fulfillment is not None and not isinstance(fulfillment, Fulfillment): raise TypeError('`fulfillment` must be a Fulfillment instance or None') @@ -396,7 +429,7 @@ class Transaction(object): else: return tx.fulfillments_valid() - if self.operation is Transaction.CREATE: + if self.operation in (Transaction.CREATE, Transaction.GENESIS): if not fulfillments_count == conditions_count: raise ValueError('Fulfillments, conditions must have the same count') elif fulfillments_count > 1 and conditions_count > 1: @@ -411,7 +444,7 @@ class Transaction(object): else: return self._fulfillment_valid(input_condition_uris.pop()) else: - raise TypeError('`operation` must be either `Transaction.TRANSFER` or `Transaction.CREATE`') + raise TypeError('`operation` must be either `TRANSFER`, `CREATE` or `GENESIS`') def _fulfillment_valid(self, input_condition_uri=None): # NOTE: We're always taking the first fulfillment, as this method is called recursively. @@ -435,15 +468,6 @@ class Transaction(object): # it. return parsed_fulfillment.validate(message=tx_serialized, now=gen_timestamp()) and input_condition_valid - def transfer(self, conditions): - return Transaction(Transaction.TRANSFER, self._fulfillments_as_inputs(), conditions) - - def _fulfillments_as_inputs(self): - return [Fulfillment(ffill.fulfillment, - ffill.owners_before, - TransactionLink(self.to_hash(), fulfillment_id)) - for fulfillment_id, ffill in enumerate(self.fulfillments)] - def to_dict(self): try: data = self.data.to_dict() @@ -490,6 +514,10 @@ class Transaction(object): def _to_hash(value): return hash_data(value) + @property + def id(self): + return self.to_hash() + def to_hash(self): return self.to_dict()['id'] From e5ce4df2389b84d031959ec389f4cb7a3e793870 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 24 Aug 2016 11:57:41 +0200 Subject: [PATCH 14/98] Adjust fulfillment validation interface --- transaction.py | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/transaction.py b/transaction.py index 8d678f6d..0d8b8b35 100644 --- a/transaction.py +++ b/transaction.py @@ -107,11 +107,8 @@ class TransactionLink(object): self.txid = txid self.cid = cid - def is_defined(self): - if self.txid is None and self.cid is None: - return False - else: - return True + def __bool__(self): + return not (self.txid is None and self.cid is None) @classmethod def from_dict(cls, link): @@ -278,7 +275,7 @@ class Transaction(object): def transfer(self, conditions): # TODO: Check here if a condition is submitted or smth else - return Transaction(Transaction.TRANSFER, self._fulfillments_as_inputs(), conditions) + return self.__class__(Transaction.TRANSFER, self._fulfillments_as_inputs(), conditions) def simple_transfer(self, owners_after): condition = Fulfillment.gen_default(owners_after).gen_condition() @@ -401,21 +398,18 @@ class Transaction(object): subfulfillment.sign(tx_serialized, private_key) - def fulfillments_valid(self, input_condition_uris=None): + def fulfillments_valid(self, input_conditions=None): + if isinstance(input_conditions, list): + return self._fulfillments_valid([cond.condition_uri for cond + in input_conditions]) + elif input_conditions is None: + return self._fulfillments_valid() + else: + raise TypeError('`input_conditions` must be list instance or None') + + def _fulfillments_valid(self, input_condition_uris=None): # TODO: Update Comment - """Verify the signature of a transaction - - A valid transaction should have been signed `current_owner` corresponding private key. - - Args: - signed_transaction (dict): a transaction with the `signature` included. - - Returns: - bool: True if the signature is correct, False otherwise. - """ - if not isinstance(input_condition_uris, list) and input_condition_uris is not None: - raise TypeError('`input_condition_uris` must be list instance') - elif input_condition_uris is None: + if input_condition_uris is None: input_condition_uris = [] input_condition_uris_count = len(input_condition_uris) @@ -425,9 +419,9 @@ class Transaction(object): def gen_tx(fulfillment, condition, input_condition_uri=None): tx = Transaction(self.operation, [fulfillment], [condition], self.data, self.timestamp, self.version) if input_condition_uri is not None: - return tx.fulfillments_valid([input_condition_uri]) + return tx._fulfillments_valid([input_condition_uri]) else: - return tx.fulfillments_valid() + return tx._fulfillments_valid() if self.operation in (Transaction.CREATE, Transaction.GENESIS): if not fulfillments_count == conditions_count: From 3d967acde4dcacc56ff271783dca5221cfc779de Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 24 Aug 2016 18:29:20 +0200 Subject: [PATCH 15/98] Add serialization validation for txids --- transaction.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/transaction.py b/transaction.py index 0d8b8b35..e6cc9e97 100644 --- a/transaction.py +++ b/transaction.py @@ -16,6 +16,7 @@ from bigchaindb_common.crypto import ( ) from bigchaindb_common.exceptions import ( KeypairMismatchException, + InvalidHash, ) from bigchaindb_common.util import ( serialize, @@ -523,8 +524,23 @@ class Transaction(object): return Transaction._to_str(self.to_dict()) @classmethod + # TODO: Make this method more pretty def from_dict(cls, tx_body): - tx = tx_body['transaction'] - return cls(tx['operation'], [Fulfillment.from_dict(fulfillment) for fulfillment in tx['fulfillments']], - [Condition.from_dict(condition) for condition in tx['conditions']], Data.from_dict(tx['data']), - tx['timestamp'], tx_body['version']) + # NOTE: Remove reference to avoid side effects + tx_body = deepcopy(tx_body) + try: + proposed_tx_id = tx_body.pop('id') + except KeyError: + raise InvalidHash() + valid_tx_id = Transaction._to_hash(Transaction._to_str(Transaction._remove_signatures(tx_body))) + if proposed_tx_id != valid_tx_id: + raise InvalidHash() + else: + tx = tx_body['transaction'] + fulfillments = [Fulfillment.from_dict(fulfillment) for fulfillment + in tx['fulfillments']] + conditions = [Condition.from_dict(condition) for condition + in tx['conditions']] + data = Data.from_dict(tx['data']) + return cls(tx['operation'], fulfillments, conditions, data, + tx['timestamp'], tx_body['version']) From febb63f19877500f83b5b7d661538aa86f307c7f Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 24 Aug 2016 19:12:32 +0200 Subject: [PATCH 16/98] Use __eq__ to compare objects --- transaction.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/transaction.py b/transaction.py index e6cc9e97..71f20a90 100644 --- a/transaction.py +++ b/transaction.py @@ -46,6 +46,9 @@ class Fulfillment(object): else: self.owners_before = owners_before + def __eq__(self, other): + return self.to_dict() == other.to_dict() + def to_dict(self): try: # When we have signed the fulfillment, this will work @@ -111,6 +114,9 @@ class TransactionLink(object): def __bool__(self): return not (self.txid is None and self.cid is None) + def __eq__(self, other): + return self.to_dict() == self.to_dict() + @classmethod def from_dict(cls, link): try: @@ -146,6 +152,9 @@ class Condition(object): else: self.owners_after = owners_after + def __eq__(self, other): + return self.to_dict() == other.to_dict() + def to_dict(self): return { 'owners_after': self.owners_after, @@ -167,6 +176,9 @@ class Data(object): else: self.payload = payload + def __eq__(self, other): + return self.to_dict() == other.to_dict() + @classmethod def from_dict(cls, payload): try: @@ -263,6 +275,9 @@ class Transaction(object): else: self.data = data + def __eq__(self, other): + return self.to_dict() == other.to_dict() + @classmethod def create(cls, owners_before, owners_after, inputs, operation, payload=None): if operation == Transaction.CREATE or operation == Transaction.GENESIS: From 67819a17756a540cf91b98b0e9cecd1b2f08b346 Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 25 Aug 2016 19:29:16 +0200 Subject: [PATCH 17/98] Heavy refactor to comply with current implementation --- transaction.py | 231 +++++++++++++------------------------------------ 1 file changed, 60 insertions(+), 171 deletions(-) diff --git a/transaction.py b/transaction.py index 71f20a90..215b786a 100644 --- a/transaction.py +++ b/transaction.py @@ -25,15 +25,7 @@ from bigchaindb_common.util import ( class Fulfillment(object): - def __init__(self, fulfillment, owners_before=None, tx_input=None): - """Create a new fulfillment - - Args: - # TODO: Write a description here - owners_before (Optional(list)): base58 encoded public key of the owners of the asset before this - transaction. - """ - # TODO: Derive `owner_before` from fulfillment + def __init__(self, fulfillment, owners_before, tx_input=None): self.fulfillment = fulfillment if tx_input is not None and not isinstance(tx_input, TransactionLink): @@ -42,18 +34,17 @@ class Fulfillment(object): self.tx_input = tx_input if not isinstance(owners_before, list): - raise TypeError('`owners_before` must be a list instance') + raise TypeError('`owners_after` must be a list instance') else: self.owners_before = owners_before def __eq__(self, other): return self.to_dict() == other.to_dict() - def to_dict(self): + def to_dict(self, fid=None): try: - # When we have signed the fulfillment, this will work fulfillment = self.fulfillment.serialize_uri() - except TypeError: + except (TypeError, AttributeError): fulfillment = None try: @@ -62,35 +53,14 @@ class Fulfillment(object): except AttributeError: tx_input = None - return { + ffill = { 'owners_before': self.owners_before, 'input': tx_input, 'fulfillment': fulfillment, - 'details': self.fulfillment.to_dict(), } - - @classmethod - def gen_default(cls, owners_after): - """Creates default fulfillments for transactions, depending on how many `owners_after` are supplied. - """ - if not isinstance(owners_after, list): - raise TypeError('`owners_after` must be a list instance') - else: - owners_after_count = len(owners_after) - - if owners_after_count == 0: - # TODO: Replace this error with the logic for a hashlock condition - raise NotImplementedError('Hashlock conditions are not implemented in BigchainDB yet') - elif owners_after_count == 1: - return cls(Ed25519Fulfillment(public_key=owners_after[0]), owners_after) - else: - threshold_ffill = cls(ThresholdSha256Fulfillment(threshold=len(owners_after)), owners_after) - for owner_after in owners_after: - threshold_ffill.fulfillment.add_subfulfillment(Ed25519Fulfillment(public_key=owner_after)) - return threshold_ffill - - def gen_condition(self): - return Condition(self.fulfillment.condition_uri, self.owners_before) + if fid is not None: + ffill['fid'] = fid + return ffill @classmethod def from_dict(cls, ffill): @@ -99,7 +69,7 @@ class Fulfillment(object): try: fulfillment = CCFulfillment.from_uri(ffill['fulfillment']) except TypeError: - fulfillment = CCFulfillment.from_dict(ffill['details']) + fulfillment = None return cls(fulfillment, ffill['owners_before'], TransactionLink.from_dict(ffill['input'])) @@ -135,7 +105,7 @@ class TransactionLink(object): class Condition(object): - def __init__(self, condition_uri, owners_after=None): + def __init__(self, fulfillment, owners_after=None): # TODO: Add more description """Create a new condition for a fulfillment @@ -144,8 +114,7 @@ class Condition(object): this transaction. """ - # TODO: Derive `owner_after` from condition - self.condition_uri = condition_uri + self.fulfillment = fulfillment if not isinstance(owners_after, list): raise TypeError('`owners_after` must be a list instance') @@ -155,17 +124,22 @@ class Condition(object): def __eq__(self, other): return self.to_dict() == other.to_dict() - def to_dict(self): - return { + def to_dict(self, cid=None): + cond = { 'owners_after': self.owners_after, - 'condition': self.condition_uri, + 'condition': { + 'details': self.fulfillment.to_dict(), + 'uri': self.fulfillment.condition_uri, + } } + if cid is not None: + cond['cid'] = cid + return cond @classmethod def from_dict(cls, cond): - """ Serializes a BigchainDB 'jsonized' condition back to a BigchainDB Condition class. - """ - return cls(cond['condition'], cond['owners_after']) + fulfillment = CCFulfillment.from_dict(cond['condition']['details']) + return cls(fulfillment, cond['owners_after']) class Data(object): @@ -278,31 +252,6 @@ class Transaction(object): def __eq__(self, other): return self.to_dict() == other.to_dict() - @classmethod - def create(cls, owners_before, owners_after, inputs, operation, payload=None): - if operation == Transaction.CREATE or operation == Transaction.GENESIS: - ffill = Fulfillment.gen_default(owners_after) - cond = ffill.gen_condition() - return cls(operation, [ffill], [cond], Data(payload)) - else: - # TODO: Replace this with an actual implementation, maybe calling - # `self.transfer` is sufficient already :) - raise NotImplementedError() - - def transfer(self, conditions): - # TODO: Check here if a condition is submitted or smth else - return self.__class__(Transaction.TRANSFER, self._fulfillments_as_inputs(), conditions) - - def simple_transfer(self, owners_after): - condition = Fulfillment.gen_default(owners_after).gen_condition() - return self.transfer([condition]) - - def _fulfillments_as_inputs(self): - return [Fulfillment(ffill.fulfillment, - ffill.owners_before, - TransactionLink(self.to_hash(), fulfillment_id)) - for fulfillment_id, ffill in enumerate(self.fulfillments)] - def add_fulfillment(self, fulfillment): if fulfillment is not None and not isinstance(fulfillment, Fulfillment): raise TypeError('`fulfillment` must be a Fulfillment instance or None') @@ -315,8 +264,6 @@ class Transaction(object): else: self.conditions.append(condition) - # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, - # since for example the Transaction class in BigchainDB doesn't have to sign transactions. def sign(self, private_keys): """ Signs a transaction Acts as a proxy for `_sign_fulfillments`, for exposing a nicer API to the outside. @@ -324,8 +271,6 @@ class Transaction(object): self._sign_fulfillments(private_keys) return self - # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, - # since for example the Transaction class in BigchainDB doesn't have to sign transactions. def _sign_fulfillments(self, private_keys): if private_keys is None or not isinstance(private_keys, list): raise TypeError('`private_keys` must be a list instance') @@ -339,63 +284,36 @@ class Transaction(object): key_pairs = {gen_public_key(SigningKey(private_key)): SigningKey(private_key) for private_key in private_keys} # TODO: The condition for a transfer-tx will come from an input - for fulfillment, condition in zip(self.fulfillments, self.conditions): + for index, (fulfillment, condition) in enumerate(zip(self.fulfillments, self.conditions)): # 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, [fulfillment], [condition], self.data, self.timestamp, self.version) tx_serialized = Transaction._to_str(Transaction._remove_signatures(tx_partial.to_dict())) - self._sign_fulfillment(fulfillment, tx_serialized, key_pairs) + self._sign_fulfillment(fulfillment, index, tx_serialized, key_pairs) - # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, - # since for example the Transaction class in BigchainDB doesn't have to sign transactions. - def _sign_fulfillment(self, fulfillment, tx_serialized, key_pairs): + def _sign_fulfillment(self, fulfillment, index, tx_serialized, key_pairs): if isinstance(fulfillment.fulfillment, Ed25519Fulfillment): - self._sign_simple_signature_fulfillment(fulfillment, tx_serialized, key_pairs) + self._sign_simple_signature_fulfillment(fulfillment, index, tx_serialized, key_pairs) elif isinstance(fulfillment.fulfillment, ThresholdSha256Fulfillment): - self._sign_threshold_signature_fulfillment(fulfillment, tx_serialized, key_pairs) + self._sign_threshold_signature_fulfillment(fulfillment, index, tx_serialized, key_pairs) - # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, - # since for example the Transaction class in BigchainDB doesn't have to sign transactions. - def _sign_simple_signature_fulfillment(self, fulfillment, tx_serialized, key_pairs): - # TODO: Update comment - """Fulfill a cryptoconditions.Ed25519Fulfillment - - Args: - fulfillment (dict): BigchainDB fulfillment to fulfill. - parsed_fulfillment (cryptoconditions.Ed25519Fulfillment): cryptoconditions.Ed25519Fulfillment instance. - fulfillment_message (dict): message to sign. - key_pairs (dict): dictionary of (public_key, private_key) pairs. - - Returns: - object: fulfilled cryptoconditions.Ed25519Fulfillment - - """ + def _sign_simple_signature_fulfillment(self, fulfillment, index, tx_serialized, key_pairs): + # NOTE: To eliminate the dangers of accidentially signing a condition by reference, + # we remove the reference of fulfillment here intentionally. + # If the user of this class knows how to use it, this should never happen, + # but then again, never say never. + fulfillment = deepcopy(fulfillment) owner_before = fulfillment.owners_before[0] try: - # NOTE: By signing the CC fulfillment here directly, we're changing the transactions's fulfillment by - # reference, and that's good :) fulfillment.fulfillment.sign(tx_serialized, key_pairs[owner_before]) except KeyError: raise KeypairMismatchException('Public key {} is not a pair to any of the private keys' .format(owner_before)) + self.fulfillments[index] = fulfillment - # TODO: This shouldn't be in the base of the Transaction class, but rather only for the client implementation, - # since for example the Transaction class in BigchainDB doesn't have to sign transactions. - def _sign_threshold_signature_fulfillment(self, fulfillment, tx_serialized, key_pairs): - # TODO: Update comment - """Fulfill a cryptoconditions.ThresholdSha256Fulfillment - - Args: - fulfillment (dict): BigchainDB fulfillment to fulfill. - parsed_fulfillment (ThresholdSha256Fulfillment): ThresholdSha256Fulfillment instance. - fulfillment_message (dict): message to sign. - key_pairs (dict): dictionary of (public_key, private_key) pairs. - - Returns: - object: fulfilled cryptoconditions.ThresholdSha256Fulfillment - - """ + def _sign_threshold_signature_fulfillment(self, fulfillment, index, tx_serialized, key_pairs): + fulfillment = deepcopy(fulfillment) for owner_before in fulfillment.owners_before: try: # TODO: CC should throw a KeypairMismatchException, instead of our manual mapping here @@ -413,67 +331,43 @@ class Transaction(object): .format(owner_before)) subfulfillment.sign(tx_serialized, private_key) + self.fulfillments[index] = fulfillment def fulfillments_valid(self, input_conditions=None): - if isinstance(input_conditions, list): - return self._fulfillments_valid([cond.condition_uri for cond - in input_conditions]) - elif input_conditions is None: - return self._fulfillments_valid() + if self.operation in (Transaction.CREATE, Transaction.GENESIS): + return self._fulfillments_valid([cond.fulfillment.condition_uri + for cond in self.conditions]) + elif self.operation == Transaction.TRANSFER: + return self._fulfillments_valid([cond.fulfillment.condition_uri + for cond in input_conditions]) else: - raise TypeError('`input_conditions` must be list instance or None') - - def _fulfillments_valid(self, input_condition_uris=None): - # TODO: Update Comment - if input_condition_uris is None: - input_condition_uris = [] + raise TypeError('`operation` must be either `TRANSFER`, `CREATE` or `GENESIS`') + def _fulfillments_valid(self, input_condition_uris): 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): - tx = Transaction(self.operation, [fulfillment], [condition], self.data, self.timestamp, self.version) - if input_condition_uri is not None: - return tx._fulfillments_valid([input_condition_uri]) - else: - return tx._fulfillments_valid() + tx = Transaction(self.operation, [fulfillment], [condition], + self.data, self.timestamp, self.version) + tx_serialized = Transaction._to_str(Transaction._remove_signatures(tx.to_dict())) + return Transaction._fulfillment_valid(fulfillment, tx_serialized, + input_condition_uri) - if self.operation in (Transaction.CREATE, Transaction.GENESIS): - if not fulfillments_count == conditions_count: - raise ValueError('Fulfillments, conditions must have the same count') - elif fulfillments_count > 1 and conditions_count > 1: - return reduce(and_, map(gen_tx, self.fulfillments, self.conditions)) - else: - return self._fulfillment_valid() - elif self.operation is Transaction.TRANSFER: - if not fulfillments_count == conditions_count == input_condition_uris_count: - raise ValueError('Fulfillments, conditions and input_condition_uris must have the same count') - elif fulfillments_count > 1 and conditions_count > 1 and input_condition_uris_count > 1: - return reduce(and_, map(gen_tx, self.fulfillments, self.conditions, input_condition_uris)) - else: - return self._fulfillment_valid(input_condition_uris.pop()) + if not fulfillments_count == conditions_count == input_condition_uris_count: + raise ValueError('Fulfillments, conditions and input_condition_uris must have the same count') else: - raise TypeError('`operation` must be either `TRANSFER`, `CREATE` or `GENESIS`') - - def _fulfillment_valid(self, input_condition_uri=None): - # NOTE: We're always taking the first fulfillment, as this method is called recursively. - # See: `fulfillments_valid` - fulfillment = self.fulfillments[0] + return reduce(and_, map(gen_tx, self.fulfillments, self.conditions, input_condition_uris)) + @staticmethod + def _fulfillment_valid(fulfillment, tx_serialized, input_condition_uri=None): try: parsed_fulfillment = CCFulfillment.from_uri(fulfillment.fulfillment.serialize_uri()) except (TypeError, ValueError, ParsingError): return False + input_condition_valid = input_condition_uri == fulfillment.fulfillment.condition_uri - if self.operation == Transaction.CREATE: - input_condition_valid = True - else: - # NOTE: When passing a `TRANSFER` transaction for validation, we check if it's valid by validating its - # input condition (taken from previous transaction) against the current fulfillment. - input_condition_valid = input_condition_uri == fulfillment.fulfillment.condition_uri - - tx_serialized = Transaction._to_str(Transaction._remove_signatures(self.to_dict())) # NOTE: We pass a timestamp to `.validate`, as in case of a timeout condition we'll have to validate against # it. return parsed_fulfillment.validate(message=tx_serialized, now=gen_timestamp()) and input_condition_valid @@ -486,8 +380,10 @@ class Transaction(object): data = None tx_body = { - 'fulfillments': [fulfillment.to_dict() for fulfillment in self.fulfillments], - 'conditions': [condition.to_dict() for condition in self.conditions], + 'fulfillments': [fulfillment.to_dict(fid) for fid, fulfillment + in enumerate(self.fulfillments)], + 'conditions': [condition.to_dict(cid) for cid, condition + in enumerate(self.conditions)], 'operation': str(self.operation), 'timestamp': self.timestamp, 'data': data, @@ -510,14 +406,7 @@ class Transaction(object): # NOTE: Not all Cryptoconditions return a `signature` key (e.g. ThresholdSha256Fulfillment), so setting it # to `None` in any case could yield incorrect signatures. This is why we only set it to `None` if # it's set in the dict. - if 'signature' in fulfillment['details']: - fulfillment['details']['signature'] = None fulfillment['fulfillment'] = None - try: - for subfulfillment in fulfillment['details']['subfulfillments']: - subfulfillment['signature'] = None - except KeyError: - pass return tx_dict @staticmethod From 61caf1183c480feafd5c42467152a21ccf1e236f Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 25 Aug 2016 21:29:08 +0200 Subject: [PATCH 18/98] Add Transaction.create --- transaction.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/transaction.py b/transaction.py index 215b786a..018f13d2 100644 --- a/transaction.py +++ b/transaction.py @@ -249,6 +249,51 @@ class Transaction(object): else: self.data = data + @classmethod + def create(cls, owners_before, owners_after, payload): + if not isinstance(owners_before, list): + 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) == 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_for_cond = Ed25519Fulfillment(public_key=owners_after[0]) + ffill_tx = Fulfillment(ffill, owners_before) + cond_tx = Condition(ffill_for_cond, owners_after) + data = Data(payload) + return cls(cls.CREATE, [ffill_tx], [cond_tx], data) + + elif len(owners_before) == len(owners_after) and len(owners_after) > 1: + # NOTE: Multiple inputs and outputs case. + ffills = [Fulfillment(Ed25519Fulfillment(public_key=owner_before), + [owner_before]) + for owner_before in owners_before] + conds = [Condition(Ed25519Fulfillment(public_key=owner_after), + [owner_after]) + for owner_after in owners_after] + data = Data(payload) + return cls(cls.CREATE, ffills, conds, data) + + elif len(owners_before) == 1 and len(owners_after) > 1: + # NOTE: Multiple owners case + threshold = ThresholdSha256Fulfillment(threshold=len(owners_after)) + for owner_after in owners_after: + threshold.add_subfulfillment(Ed25519Fulfillment(public_key=owner_after)) + cond_tx = Condition(threshold, owners_after) + # TODO: Is this correct? Can I fulfill a threshold condition in + # a create? I guess so. + ffill = Ed25519Fulfillment(public_key=owners_before[0]) + ffill_tx = Fulfillment(ffill, owners_before) + data = Data(payload) + return cls(cls.CREATE, [ffill_tx], [cond_tx], data) + else: + # TODO: figure out exception + raise Exception() + def __eq__(self, other): return self.to_dict() == other.to_dict() From 329e6b3e6bb0ed8fc37e3ecb5552d5187719eda0 Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 25 Aug 2016 21:45:47 +0200 Subject: [PATCH 19/98] Correct fulfillment validation logic --- transaction.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/transaction.py b/transaction.py index 018f13d2..8ad7c221 100644 --- a/transaction.py +++ b/transaction.py @@ -285,14 +285,13 @@ class Transaction(object): threshold.add_subfulfillment(Ed25519Fulfillment(public_key=owner_after)) cond_tx = Condition(threshold, owners_after) # TODO: Is this correct? Can I fulfill a threshold condition in - # a create? I guess so. + # a create? I guess so?! ffill = Ed25519Fulfillment(public_key=owners_before[0]) ffill_tx = Fulfillment(ffill, owners_before) data = Data(payload) return cls(cls.CREATE, [ffill_tx], [cond_tx], data) else: - # TODO: figure out exception - raise Exception() + raise ValueError("This is not the case you're looking for ;)") def __eq__(self, other): return self.to_dict() == other.to_dict() @@ -380,8 +379,12 @@ class Transaction(object): def fulfillments_valid(self, input_conditions=None): if self.operation in (Transaction.CREATE, Transaction.GENESIS): - return self._fulfillments_valid([cond.fulfillment.condition_uri - for cond in self.conditions]) + # NOTE: Since in the case of a create-transaction we do not have + # to check for input_conditions, we're just submitting dummy + # values to the actual method. This simplifies it's logic + # greatly, as we do not have to check against `None` values. + return self._fulfillments_valid(['dummyvalue' + for cond in self.fulfillments]) elif self.operation == Transaction.TRANSFER: return self._fulfillments_valid([cond.fulfillment.condition_uri for cond in input_conditions]) @@ -397,21 +400,28 @@ class Transaction(object): tx = Transaction(self.operation, [fulfillment], [condition], self.data, self.timestamp, self.version) tx_serialized = Transaction._to_str(Transaction._remove_signatures(tx.to_dict())) - return Transaction._fulfillment_valid(fulfillment, tx_serialized, + return Transaction._fulfillment_valid(fulfillment, self.operation, + tx_serialized, input_condition_uri) + # TODO: For sure there need to be an equal amount of fulfillments and + # input_conditions, but not sure if there must be an equal amount + # of conditions if not fulfillments_count == conditions_count == input_condition_uris_count: raise ValueError('Fulfillments, conditions and input_condition_uris must have the same count') else: return reduce(and_, map(gen_tx, self.fulfillments, self.conditions, input_condition_uris)) @staticmethod - def _fulfillment_valid(fulfillment, tx_serialized, input_condition_uri=None): + def _fulfillment_valid(fulfillment, operation, tx_serialized, input_condition_uri=None): try: parsed_fulfillment = CCFulfillment.from_uri(fulfillment.fulfillment.serialize_uri()) except (TypeError, ValueError, ParsingError): return False - input_condition_valid = input_condition_uri == fulfillment.fulfillment.condition_uri + if operation in (Transaction.CREATE, Transaction.GENESIS): + input_condition_valid = True + else: + input_condition_valid = input_condition_uri == fulfillment.fulfillment.condition_uri # NOTE: We pass a timestamp to `.validate`, as in case of a timeout condition we'll have to validate against # it. From 520de0357f7ce2f31de382a3f47b69aa99a35682 Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 25 Aug 2016 22:21:49 +0200 Subject: [PATCH 20/98] Add Transaction.create for hashlock conditions --- transaction.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/transaction.py b/transaction.py index 8ad7c221..b2f6ec61 100644 --- a/transaction.py +++ b/transaction.py @@ -7,6 +7,7 @@ from cryptoconditions import ( Fulfillment as CCFulfillment, ThresholdSha256Fulfillment, Ed25519Fulfillment, + PreimageSha256Fulfillment, ) from cryptoconditions.exceptions import ParsingError @@ -116,8 +117,8 @@ class Condition(object): """ self.fulfillment = fulfillment - if not isinstance(owners_after, list): - raise TypeError('`owners_after` must be a list instance') + if not isinstance(owners_after, list) and owners_after is not None: + raise TypeError('`owners_after` must be a list instance or None') else: self.owners_after = owners_after @@ -132,6 +133,11 @@ class Condition(object): 'uri': self.fulfillment.condition_uri, } } + # TODO: This case should have it's own serialization test + if self.owners_after is None: + # NOTE: Hashlock condition case, we cannot show the `fulfillment`'s + # details, as this would expose the secret + cond['condition'].pop('details') if cid is not None: cond['cid'] = cid return cond @@ -250,12 +256,13 @@ class Transaction(object): self.data = data @classmethod - def create(cls, owners_before, owners_after, payload): + def create(cls, owners_before, owners_after, payload, secret=None): if not isinstance(owners_before, list): raise TypeError('`owners_before` must be a list instance') if not isinstance(owners_after, list): raise TypeError('`owners_after` must be a list instance') + data = Data(payload) 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 @@ -264,7 +271,6 @@ class Transaction(object): ffill_for_cond = Ed25519Fulfillment(public_key=owners_after[0]) ffill_tx = Fulfillment(ffill, owners_before) cond_tx = Condition(ffill_for_cond, owners_after) - data = Data(payload) return cls(cls.CREATE, [ffill_tx], [cond_tx], data) elif len(owners_before) == len(owners_after) and len(owners_after) > 1: @@ -275,7 +281,6 @@ class Transaction(object): conds = [Condition(Ed25519Fulfillment(public_key=owner_after), [owner_after]) for owner_after in owners_after] - data = Data(payload) return cls(cls.CREATE, ffills, conds, data) elif len(owners_before) == 1 and len(owners_after) > 1: @@ -288,8 +293,18 @@ class Transaction(object): # a create? I guess so?! ffill = Ed25519Fulfillment(public_key=owners_before[0]) ffill_tx = Fulfillment(ffill, owners_before) - data = Data(payload) return cls(cls.CREATE, [ffill_tx], [cond_tx], data) + + 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) + ffill = Ed25519Fulfillment(public_key=owners_before[0]) + ffill_tx = Fulfillment(ffill, owners_before) + return cls(cls.CREATE, [ffill_tx], [cond_tx], data) + + 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("This is not the case you're looking for ;)") From 66f7ab6ea4f398a4e05dfcf3932359eec02c6926 Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 25 Aug 2016 23:27:57 +0200 Subject: [PATCH 21/98] Add hashlock condition serialization --- transaction.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/transaction.py b/transaction.py index b2f6ec61..d84e3afa 100644 --- a/transaction.py +++ b/transaction.py @@ -5,6 +5,7 @@ from uuid import uuid4 from cryptoconditions import ( Fulfillment as CCFulfillment, + Condition as CCCondition, ThresholdSha256Fulfillment, Ed25519Fulfillment, PreimageSha256Fulfillment, @@ -126,25 +127,35 @@ class Condition(object): return self.to_dict() == other.to_dict() def to_dict(self, cid=None): + # TODO FOR CC: It must be able to recognize a hashlock condition + # and fulfillment! + condition = {} + try: + condition['details'] = self.fulfillment.to_dict() + except AttributeError: + pass + + try: + condition['uri'] = self.fulfillment.condition_uri + except AttributeError: + condition['uri'] = self.fulfillment + cond = { 'owners_after': self.owners_after, - 'condition': { - 'details': self.fulfillment.to_dict(), - 'uri': self.fulfillment.condition_uri, - } + 'condition': condition } - # TODO: This case should have it's own serialization test - if self.owners_after is None: - # NOTE: Hashlock condition case, we cannot show the `fulfillment`'s - # details, as this would expose the secret - cond['condition'].pop('details') if cid is not None: cond['cid'] = cid return cond @classmethod def from_dict(cls, cond): - fulfillment = CCFulfillment.from_dict(cond['condition']['details']) + # TODO: This case should have it's own serialization test + try: + fulfillment = CCFulfillment.from_dict(cond['condition']['details']) + except KeyError: + # NOTE: Hashlock condition case + fulfillment = cond['condition']['uri'] return cls(fulfillment, cond['owners_after']) @@ -298,12 +309,12 @@ class Transaction(object): 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) + cond_tx = Condition(hashlock.condition_uri) ffill = Ed25519Fulfillment(public_key=owners_before[0]) ffill_tx = Fulfillment(ffill, owners_before) return cls(cls.CREATE, [ffill_tx], [cond_tx], data) - elif len(owners_before) > 0 and len(owners_after) == 0 and secret is None: + elif len(owners_before) == 1 and len(owners_after) == 0 and secret is None: raise ValueError('Define a secret to create a hashlock condition') else: raise ValueError("This is not the case you're looking for ;)") From 46f0b2c62e323a2fc0d15d0ddc89b7ae1faab2ce Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 26 Aug 2016 15:43:06 +0200 Subject: [PATCH 22/98] Transaction.transfer add single input and outputs --- transaction.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/transaction.py b/transaction.py index d84e3afa..86d69dd8 100644 --- a/transaction.py +++ b/transaction.py @@ -5,7 +5,6 @@ from uuid import uuid4 from cryptoconditions import ( Fulfillment as CCFulfillment, - Condition as CCCondition, ThresholdSha256Fulfillment, Ed25519Fulfillment, PreimageSha256Fulfillment, @@ -267,7 +266,8 @@ class Transaction(object): self.data = data @classmethod - def create(cls, owners_before, owners_after, payload, secret=None): + def create(cls, owners_before, owners_after, payload, secret=None, + time_expire=None): if not isinstance(owners_before, list): raise TypeError('`owners_before` must be a list instance') if not isinstance(owners_after, list): @@ -314,14 +314,42 @@ class Transaction(object): ffill_tx = Fulfillment(ffill, owners_before) return cls(cls.CREATE, [ffill_tx], [cond_tx], data) + elif len(owners_before) == 1 and len(owners_after) == 0 and time_expire is not None: + raise NotImplementedError('Timeout conditions will be implemented later') + elif len(owners_before) == 1 and len(owners_after) == 0 and secret is None: raise ValueError('Define a secret to create a hashlock condition') + else: raise ValueError("This is not the case you're looking for ;)") + @classmethod + def transfer(cls, inputs, owners_after, payload, secret=None, time_expiry=None): + if not isinstance(inputs, list): + raise TypeError('`inputs` must be a list instance') + if not isinstance(owners_after, list): + raise TypeError('`owners_after` must be a list instance') + + data = Data(payload) + if len(inputs) == len(owners_after) and len(owners_after) == 1: + ffill_for_cond = Ed25519Fulfillment(public_key=owners_after[0]) + cond_tx = Condition(ffill_for_cond, owners_after) + return cls(cls.TRANSFER, inputs, [cond_tx], data) + def __eq__(self, other): return self.to_dict() == other.to_dict() + # TODO: There might be a better name + def to_spendable_fulfillments(self, condition_indices): + spendables = [] + for cid in condition_indices: + input_cond = self.conditions[cid] + ffill = Fulfillment(input_cond.fulfillment, + input_cond.owners_after, + TransactionLink(self.id, cid)) + spendables.append(ffill) + return spendables + def add_fulfillment(self, fulfillment): if fulfillment is not None and not isinstance(fulfillment, Fulfillment): raise TypeError('`fulfillment` must be a Fulfillment instance or None') From 71b7eaed3e8ba2ffd9560935564d96e8e02e93a9 Mon Sep 17 00:00:00 2001 From: tim Date: Mon, 29 Aug 2016 14:45:55 +0200 Subject: [PATCH 23/98] Small adjustments to transfer-tx interface --- transaction.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/transaction.py b/transaction.py index 86d69dd8..9efceafa 100644 --- a/transaction.py +++ b/transaction.py @@ -266,7 +266,7 @@ class Transaction(object): self.data = data @classmethod - def create(cls, owners_before, owners_after, payload, secret=None, + def create(cls, owners_before, owners_after, payload=None, secret=None, time_expire=None): if not isinstance(owners_before, list): raise TypeError('`owners_before` must be a list instance') @@ -324,7 +324,7 @@ class Transaction(object): raise ValueError("This is not the case you're looking for ;)") @classmethod - def transfer(cls, inputs, owners_after, payload, secret=None, time_expiry=None): + def transfer(cls, inputs, owners_after, payload=None, secret=None, time_expiry=None): if not isinstance(inputs, list): raise TypeError('`inputs` must be a list instance') if not isinstance(owners_after, list): @@ -332,23 +332,43 @@ class Transaction(object): data = Data(payload) if len(inputs) == len(owners_after) and len(owners_after) == 1: + # NOTE: Standard case, one input and one output ffill_for_cond = Ed25519Fulfillment(public_key=owners_after[0]) cond_tx = Condition(ffill_for_cond, owners_after) return cls(cls.TRANSFER, inputs, [cond_tx], data) + elif len(inputs) == 1 and len(owners_after) > 1: + # NOTE: Threshold condition case + threshold = ThresholdSha256Fulfillment(threshold=len(owners_after)) + for owner_after in owners_after: + threshold.add_subfulfillment(Ed25519Fulfillment(public_key=owner_after)) + cond_tx = Condition(threshold, owners_after) + return cls(cls.TRANSFER, inputs, [cond_tx], data) + + elif len(inputs) == len(owners_after) and len(owners_after) > 1: + # NOTE: Multiple inputs and outputs and threshold + # even though asset could also live on as multiple inputs + # and outputs + pass + def __eq__(self, other): return self.to_dict() == other.to_dict() - # TODO: There might be a better name - def to_spendable_fulfillments(self, condition_indices): - spendables = [] + def to_inputs(self, condition_indices=None): + inputs = [] + # NOTE: If no condition indices are passed, we just assume to + # take all conditions as inputs. + if condition_indices is None or len(condition_indices) == 0: + 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)) - spendables.append(ffill) - return spendables + inputs.append(ffill) + return inputs def add_fulfillment(self, fulfillment): if fulfillment is not None and not isinstance(fulfillment, Fulfillment): From e41ccae6bd1fed278adb0dd54dea15a2bf4e6e9c Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 2 Sep 2016 13:55:54 +0200 Subject: [PATCH 24/98] Create transfer-tx interface --- transaction.py | 114 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 32 deletions(-) diff --git a/transaction.py b/transaction.py index 9efceafa..e2b09c53 100644 --- a/transaction.py +++ b/transaction.py @@ -147,6 +147,44 @@ class Condition(object): cond['cid'] = cid return cond + @classmethod + def generate(cls, owners_after): + if not isinstance(owners_after, list): + raise TypeError('`owners_after` must be an instance of list') + + if len(owners_after) == 0: + raise ValueError('`owners_after` needs to contain at least one' + 'owner') + elif len(owners_after) == 1 and not isinstance(owners_after[0], list): + try: + ffill = Ed25519Fulfillment(public_key=owners_after[0]) + except TypeError: + ffill = owners_after[0] + return cls(ffill, owners_after) + else: + threshold = ThresholdSha256Fulfillment(threshold=len(owners_after)) + return cls(reduce(cls._gen_condition, owners_after, threshold), + owners_after) + + @classmethod + def _gen_condition(cls, initial, current): + if isinstance(current, list) and len(current) > 1: + ffill = ThresholdSha256Fulfillment(threshold=len(current)) + reduce(cls._gen_condition, current, ffill) + elif isinstance(current, list) and len(current) <= 1: + raise ValueError('Sublist cannot contain single owner') + else: + try: + current = current.pop() + except AttributeError: + pass + try: + ffill = Ed25519Fulfillment(public_key=current) + except TypeError: + ffill = current + initial.add_subfulfillment(ffill) + return initial + @classmethod def from_dict(cls, cond): # TODO: This case should have it's own serialization test @@ -279,29 +317,23 @@ class Transaction(object): # NOTE: For this case its sufficient to use the same # fulfillment for the fulfillment and condition. ffill = Ed25519Fulfillment(public_key=owners_before[0]) - ffill_for_cond = Ed25519Fulfillment(public_key=owners_after[0]) ffill_tx = Fulfillment(ffill, owners_before) - cond_tx = Condition(ffill_for_cond, owners_after) + cond_tx = Condition.generate(owners_after) return cls(cls.CREATE, [ffill_tx], [cond_tx], data) elif len(owners_before) == len(owners_after) and len(owners_after) > 1: - # NOTE: Multiple inputs and outputs case. + 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(Ed25519Fulfillment(public_key=owner_after), - [owner_after]) - for owner_after in owners_after] + conds = [Condition.generate(owners) for owners in owners_after] return cls(cls.CREATE, ffills, conds, data) elif len(owners_before) == 1 and len(owners_after) > 1: # NOTE: Multiple owners case - threshold = ThresholdSha256Fulfillment(threshold=len(owners_after)) - for owner_after in owners_after: - threshold.add_subfulfillment(Ed25519Fulfillment(public_key=owner_after)) - cond_tx = Condition(threshold, owners_after) - # TODO: Is this correct? Can I fulfill a threshold condition in - # a create? I guess so?! + cond_tx = Condition.generate(owners_after) ffill = Ed25519Fulfillment(public_key=owners_before[0]) ffill_tx = Fulfillment(ffill, owners_before) return cls(cls.CREATE, [ffill_tx], [cond_tx], data) @@ -321,35 +353,53 @@ class Transaction(object): raise ValueError('Define a secret to create a hashlock condition') else: - raise ValueError("This is not the case you're looking for ;)") + raise ValueError("This is not the cases you're looking for ;)") @classmethod - def transfer(cls, inputs, owners_after, payload=None, secret=None, time_expiry=None): + def transfer(cls, inputs, owners_after, payload=None): if not isinstance(inputs, list): raise TypeError('`inputs` must be a list instance') + if len(inputs) == 0: + raise ValueError('`inputs` must contain at least one item') if not isinstance(owners_after, list): raise TypeError('`owners_after` must be a list instance') - data = Data(payload) + # + # + # NOTE: Different cases for threshold conditions: + # + # Combining multiple `inputs` with an arbitrary number of + # `owners_after` can yield interesting cases for the creation of + # threshold conditions we'd like to support. The following + # notation is proposed: + # + # 1. The index of an `owner_after` corresponds to the index of + # an input: + # e.g. `transfer([input1], [a])`, means `input1` would now be + # owned by user `a`. + # + # 2. `owners_after` can (almost) get arbitrary deeply nested, + # creating various complex threshold conditions: + # e.g. `transfer([inp1, inp2], [[a, [b, c]], d])`, means + # `a`'s signature would have a 50% weight on `inp1` + # compared to `b` and `c` that share 25% of the leftover + # weight respectively. `inp2` is owned completely by `d`. + # + # if len(inputs) == len(owners_after) and len(owners_after) == 1: - # NOTE: Standard case, one input and one output - ffill_for_cond = Ed25519Fulfillment(public_key=owners_after[0]) - cond_tx = Condition(ffill_for_cond, owners_after) - return cls(cls.TRANSFER, inputs, [cond_tx], data) - - elif len(inputs) == 1 and len(owners_after) > 1: - # NOTE: Threshold condition case - threshold = ThresholdSha256Fulfillment(threshold=len(owners_after)) - for owner_after in owners_after: - threshold.add_subfulfillment(Ed25519Fulfillment(public_key=owner_after)) - cond_tx = Condition(threshold, owners_after) - return cls(cls.TRANSFER, inputs, [cond_tx], data) - + conditions = [Condition.generate(owners_after)] elif len(inputs) == len(owners_after) and len(owners_after) > 1: - # NOTE: Multiple inputs and outputs and threshold - # even though asset could also live on as multiple inputs - # and outputs - pass + conditions = [Condition.generate(owners) for owners + in owners_after] + elif len(inputs) != len(owners_after): + raise ValueError("`inputs` and `owners_after`'s count must be the " + "same") + else: + raise ValueError("This is not the cases you're looking for ;)") + + data = Data(payload) + inputs = deepcopy(inputs) + return cls(cls.TRANSFER, inputs, conditions, data) def __eq__(self, other): return self.to_dict() == other.to_dict() From 331150b9c22067612df4902a206b30584b8fd36c Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 2 Sep 2016 14:51:33 +0200 Subject: [PATCH 25/98] Increase test coverage --- transaction.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/transaction.py b/transaction.py index e2b09c53..5a04cd0b 100644 --- a/transaction.py +++ b/transaction.py @@ -83,7 +83,7 @@ class TransactionLink(object): self.cid = cid def __bool__(self): - return not (self.txid is None and self.cid is None) + return self.txid is not None and self.cid is not None def __eq__(self, other): return self.to_dict() == self.to_dict() @@ -346,14 +346,14 @@ class Transaction(object): ffill_tx = Fulfillment(ffill, owners_before) return cls(cls.CREATE, [ffill_tx], [cond_tx], data) - elif len(owners_before) == 1 and len(owners_after) == 0 and time_expire is not None: + 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) == 1 and len(owners_after) == 0 and secret is None: + 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("This is not the cases you're looking for ;)") + raise ValueError("These are not the cases you're looking for ;)") @classmethod def transfer(cls, inputs, owners_after, payload=None): @@ -364,8 +364,6 @@ class Transaction(object): if not isinstance(owners_after, list): raise TypeError('`owners_after` must be a list instance') - # - # # NOTE: Different cases for threshold conditions: # # Combining multiple `inputs` with an arbitrary number of @@ -384,18 +382,15 @@ class Transaction(object): # `a`'s signature would have a 50% weight on `inp1` # compared to `b` and `c` that share 25% of the leftover # weight respectively. `inp2` is owned completely by `d`. - # - # - if len(inputs) == len(owners_after) and len(owners_after) == 1: - conditions = [Condition.generate(owners_after)] - elif len(inputs) == len(owners_after) and len(owners_after) > 1: - conditions = [Condition.generate(owners) for owners - in owners_after] - elif len(inputs) != len(owners_after): + 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") - else: - raise ValueError("This is not the cases you're looking for ;)") data = Data(payload) inputs = deepcopy(inputs) From 2ae5d49783ff7d167185c65c63c67cb866cdfc13 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 14 Sep 2016 13:46:17 +0200 Subject: [PATCH 26/98] Adjust fulfillment (de)serialization --- transaction.py | 92 +++++++++++++++++++++----------------------------- util.py | 20 ++++++++--- 2 files changed, 54 insertions(+), 58 deletions(-) diff --git a/transaction.py b/transaction.py index 5a04cd0b..a5f681a8 100644 --- a/transaction.py +++ b/transaction.py @@ -46,7 +46,15 @@ class Fulfillment(object): try: fulfillment = self.fulfillment.serialize_uri() except (TypeError, AttributeError): - fulfillment = None + # NOTE: When a non-signed transaction is casted to a dict, + # `self.fulfillments` value is lost, as in the node's + # transaction model that is saved to the database, does not + # account for its dictionary form but just for its signed uri + # form. + # Hence, when a non-signed fulfillment is to be cast to a + # dict, we just call its internal `to_dict` method here and + # its `from_dict` method in `Fulfillment.from_dict`. + fulfillment = self.fulfillment.to_dict() try: # NOTE: `self.tx_input` can be `None` and that's fine @@ -70,14 +78,17 @@ class Fulfillment(object): try: fulfillment = CCFulfillment.from_uri(ffill['fulfillment']) except TypeError: - fulfillment = None + # NOTE: See comment about this special case in + # `Fulfillment.to_dict` + fulfillment = CCFulfillment.from_dict(ffill['fulfillment']) return cls(fulfillment, ffill['owners_before'], TransactionLink.from_dict(ffill['input'])) class TransactionLink(object): - # NOTE: In an IPLD implementation, this class is not necessary anymore, as an IPLD link can simply point to an - # object, as well as an objects properties. So instead of having a (de)serializable class, we can have a - # simple IPLD link of the form: `//transaction/conditions//` + # NOTE: In an IPLD implementation, this class is not necessary anymore, + # as an IPLD link can simply point to an object, as well as an objects + # properties. So instead of having a (de)serializable class, we can have a + # simple IPLD link of the form: `//transaction/conditions//` def __init__(self, txid=None, cid=None): self.txid = txid self.cid = cid @@ -111,8 +122,8 @@ class Condition(object): """Create a new condition for a fulfillment Args - owners_after (Optional(list)): base58 encoded public key of the owner of the digital asset after - this transaction. + owners_after (Optional(list)): base58 encoded public key of the + owner of the digital asset after this transaction. """ self.fulfillment = fulfillment @@ -234,48 +245,9 @@ class Transaction(object): ALLOWED_OPERATIONS = (CREATE, TRANSFER, GENESIS) VERSION = 1 - def __init__(self, operation, fulfillments=None, conditions=None, data=None, timestamp=None, version=None): + def __init__(self, operation, fulfillments=None, conditions=None, + data=None, timestamp=None, version=None): # TODO: Update this comment - """Create a new transaction in memory - - A transaction in BigchainDB is a transfer of a digital asset between two entities represented - by public keys. - - Currently BigchainDB supports two types of operations: - - `CREATE` - Only federation nodes are allowed to use this operation. In a create operation - a federation node creates a digital asset in BigchainDB and assigns that asset to a public - key. The owner of the private key can then decided to transfer this digital asset by using the - `transaction id` of the transaction as an input in a `TRANSFER` transaction. - - `TRANSFER` - A transfer operation allows for a transfer of the digital assets between entities. - - If a transaction is initialized with the inputs being `None` a `operation` `CREATE` is - chosen. Otherwise the transaction is of `operation` `TRANSFER`. - - Args: - # TODO: Write a description here - fulfillments - conditions - operation - data (Optional[dict]): dictionary with information about asset. - - Raises: - TypeError: if the optional ``data`` argument is not a ``dict``. - - # TODO: Incorporate this text somewhere better in the docs of this class - Some use cases for this class: - - 1. Create a new `CREATE` transaction: - - This means `inputs` is empty - - 2. Create a new `TRANSFER` transaction: - - This means `inputs` is a filled list (one to multiple transactions) - - 3. Written transactions must be managed somehow in the user's program: use `from_dict` - - - """ self.timestamp = timestamp if timestamp is not None else gen_timestamp() self.version = version if version is not None else Transaction.VERSION @@ -397,7 +369,11 @@ class Transaction(object): return cls(cls.TRANSFER, inputs, conditions, data) def __eq__(self, other): - return self.to_dict() == other.to_dict() + try: + other = other.to_dict() + except AttributeError: + return False + return self.to_dict() == other def to_inputs(self, condition_indices=None): inputs = [] @@ -428,6 +404,8 @@ class Transaction(object): self.conditions.append(condition) def sign(self, private_keys): + # TODO: Singing should be possible with at least one of all private + # keys supplied to this method. """ Signs a transaction Acts as a proxy for `_sign_fulfillments`, for exposing a nicer API to the outside. """ @@ -460,6 +438,9 @@ class Transaction(object): self._sign_simple_signature_fulfillment(fulfillment, index, tx_serialized, key_pairs) elif isinstance(fulfillment.fulfillment, ThresholdSha256Fulfillment): self._sign_threshold_signature_fulfillment(fulfillment, index, tx_serialized, key_pairs) + else: + raise ValueError("Fulfillment couldn't be matched to " + 'Cryptocondition fulfillment type.') def _sign_simple_signature_fulfillment(self, fulfillment, index, tx_serialized, key_pairs): # NOTE: To eliminate the dangers of accidentially signing a condition by reference, @@ -574,12 +555,14 @@ class Transaction(object): @staticmethod def _remove_signatures(tx_dict): - # NOTE: We remove the reference since we need `tx_dict` only for the transaction's hash + # NOTE: We remove the reference since we need `tx_dict` only for the + # transaction's hash tx_dict = deepcopy(tx_dict) for fulfillment in tx_dict['transaction']['fulfillments']: - # NOTE: Not all Cryptoconditions return a `signature` key (e.g. ThresholdSha256Fulfillment), so setting it - # to `None` in any case could yield incorrect signatures. This is why we only set it to `None` if - # it's set in the dict. + # NOTE: Not all Cryptoconditions return a `signature` key (e.g. + # ThresholdSha256Fulfillment), so setting it to `None` in any case + # could yield incorrect signatures. This is why we only set it to + # `None` if it's set in the dict. fulfillment['fulfillment'] = None return tx_dict @@ -599,7 +582,8 @@ class Transaction(object): return serialize(value) def __str__(self): - return Transaction._to_str(self.to_dict()) + tx = Transaction._remove_signatures(self.to_dict()) + return Transaction._to_str(tx) @classmethod # TODO: Make this method more pretty diff --git a/util.py b/util.py index 3f96f413..4e7a9818 100644 --- a/util.py +++ b/util.py @@ -8,7 +8,7 @@ def gen_timestamp(): See https://en.wikipedia.org/wiki/Unix_time Returns: - str: the Unix time + str: the Unix time """ return str(round(time.time())) @@ -24,10 +24,22 @@ def serialize(data): differences. Args: - data (dict): dict to serialize + data (dict): dict to serialize - Returns: - str: JSON formatted string + Returns: + str: JSON formatted string """ return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, sort_keys=True) + + +def deserialize(data): + """Deserialize a JSON formatted string into a dict. + + Args: + data (str): JSON formatted string. + + Returns: + dict: dict resulting from the serialization of a JSON formatted string. + """ + return rapidjson.loads(data) From ab3473b0a0f5d636aeda8922f79eb8fcbdd90d5e Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 16 Sep 2016 11:12:55 +0200 Subject: [PATCH 27/98] Catch CC Error for Fulfillment --- transaction.py | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/transaction.py b/transaction.py index a5f681a8..63e973f2 100644 --- a/transaction.py +++ b/transaction.py @@ -3,26 +3,15 @@ from functools import reduce from operator import and_ from uuid import uuid4 -from cryptoconditions import ( - Fulfillment as CCFulfillment, - ThresholdSha256Fulfillment, - Ed25519Fulfillment, - PreimageSha256Fulfillment, -) +from cryptoconditions import (Fulfillment as CCFulfillment, + ThresholdSha256Fulfillment, Ed25519Fulfillment, + PreimageSha256Fulfillment) from cryptoconditions.exceptions import ParsingError -from bigchaindb_common.crypto import ( - SigningKey, - hash_data, -) -from bigchaindb_common.exceptions import ( - KeypairMismatchException, - InvalidHash, -) -from bigchaindb_common.util import ( - serialize, - gen_timestamp, -) +from bigchaindb_common.crypto import SigningKey, hash_data +from bigchaindb_common.exceptions import (KeypairMismatchException, + InvalidHash, InvalidSignature) +from bigchaindb_common.util import serialize, gen_timestamp class Fulfillment(object): @@ -77,6 +66,9 @@ class Fulfillment(object): """ try: fulfillment = CCFulfillment.from_uri(ffill['fulfillment']) + except ValueError: + # TODO FOR CC: Throw an `InvalidSignature` error in this case. + raise InvalidSignature("Fulfillment URI could'nt been parsed") except TypeError: # NOTE: See comment about this special case in # `Fulfillment.to_dict` From c3e47f851d6f2c533be5698f73ac2a3d9127178b Mon Sep 17 00:00:00 2001 From: tim Date: Tue, 20 Sep 2016 18:31:38 +0200 Subject: [PATCH 28/98] Allow custom thresholds --- transaction.py | 66 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/transaction.py b/transaction.py index 63e973f2..20d38fdb 100644 --- a/transaction.py +++ b/transaction.py @@ -152,9 +152,37 @@ class Condition(object): @classmethod def generate(cls, owners_after): + """Generates conditions from a specifically formed tuple or list. + + If a ThresholdCondition has to be generated where the threshold is + always the number of subconditions it is split between, a list of + the following structure is sufficient: + + [(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 (list|tuple): The users that should be able to + fulfill the condition that is being + created. + Returns: + A `Condition` that can be used in a `Transaction`. + + """ + if isinstance(owners_after, tuple): + owners_after, threshold = owners_after + else: + threshold = len(owners_after) + if not isinstance(owners_after, list): raise TypeError('`owners_after` must be an instance of list') - if len(owners_after) == 0: raise ValueError('`owners_after` needs to contain at least one' 'owner') @@ -165,26 +193,42 @@ class Condition(object): ffill = owners_after[0] return cls(ffill, owners_after) else: - threshold = ThresholdSha256Fulfillment(threshold=len(owners_after)) - return cls(reduce(cls._gen_condition, owners_after, threshold), - owners_after) + initial_cond = ThresholdSha256Fulfillment(threshold=threshold) + threshold_cond = reduce(cls._gen_condition, owners_after, + initial_cond) + return cls(threshold_cond, owners_after) @classmethod def _gen_condition(cls, initial, current): - if isinstance(current, list) and len(current) > 1: - ffill = ThresholdSha256Fulfillment(threshold=len(current)) - reduce(cls._gen_condition, current, ffill) - elif isinstance(current, list) and len(current) <= 1: + if isinstance(current, tuple): + owners_after, threshold = current + else: + 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) + reduce(cls._gen_condition, owners_after, ffill) + elif isinstance(owners_after, list) and len(owners_after) <= 1: raise ValueError('Sublist cannot contain single owner') else: try: - current = current.pop() + owners_after = owners_after.pop() except AttributeError: pass try: - ffill = Ed25519Fulfillment(public_key=current) + ffill = Ed25519Fulfillment(public_key=owners_after) except TypeError: - ffill = current + # NOTE: Instead of submitting base58 encoded addresses, a user + # of this class can also submit fully instantiated + # Cryptoconditions. In the case of casting `owners_after` + # to a Ed25519Fulfillment with the result of a + # `TypeError`, we're assuming that `owners_after` is a + # Cryptocondition then. + ffill = owners_after initial.add_subfulfillment(ffill) return initial From 506b05ee681dc36ecc09f5b4e58bc9f9aa26810e Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 21 Sep 2016 11:08:18 +0200 Subject: [PATCH 29/98] PR feedback --- transaction.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/transaction.py b/transaction.py index 20d38fdb..64fc8bc9 100644 --- a/transaction.py +++ b/transaction.py @@ -68,7 +68,7 @@ class Fulfillment(object): fulfillment = CCFulfillment.from_uri(ffill['fulfillment']) except ValueError: # TODO FOR CC: Throw an `InvalidSignature` error in this case. - raise InvalidSignature("Fulfillment URI could'nt been parsed") + raise InvalidSignature("Fulfillment URI couldn't been parsed") except TypeError: # NOTE: See comment about this special case in # `Fulfillment.to_dict` @@ -111,13 +111,6 @@ class TransactionLink(object): class Condition(object): def __init__(self, fulfillment, owners_after=None): # TODO: Add more description - """Create a new condition for a fulfillment - - Args - owners_after (Optional(list)): base58 encoded public key of the - owner of the digital asset after this transaction. - - """ self.fulfillment = fulfillment if not isinstance(owners_after, list) and owners_after is not None: @@ -234,7 +227,6 @@ class Condition(object): @classmethod def from_dict(cls, cond): - # TODO: This case should have it's own serialization test try: fulfillment = CCFulfillment.from_dict(cond['condition']['details']) except KeyError: @@ -283,12 +275,13 @@ class Transaction(object): def __init__(self, operation, fulfillments=None, conditions=None, data=None, timestamp=None, version=None): - # TODO: Update this comment + # TODO: Write a comment self.timestamp = timestamp if timestamp is not None else gen_timestamp() self.version = version if version is not None else Transaction.VERSION if operation not in Transaction.ALLOWED_OPERATIONS: - raise TypeError('`operation` must be either CREATE or TRANSFER') + raise TypeError('`operation` must be one of {}' + .format(', '.join(self.__cls__.ALLOWED_OPERATIONS))) else: self.operation = operation @@ -460,7 +453,6 @@ class Transaction(object): return private_key.get_verifying_key().to_ascii().decode() key_pairs = {gen_public_key(SigningKey(private_key)): SigningKey(private_key) for private_key in private_keys} - # TODO: The condition for a transfer-tx will come from an input for index, (fulfillment, condition) in enumerate(zip(self.fulfillments, self.conditions)): # NOTE: We clone the current transaction but only add the condition and fulfillment we're currently # working on plus all previously signed ones. @@ -540,9 +532,6 @@ class Transaction(object): tx_serialized, input_condition_uri) - # TODO: For sure there need to be an equal amount of fulfillments and - # input_conditions, but not sure if there must be an equal amount - # of conditions if not fulfillments_count == conditions_count == input_condition_uris_count: raise ValueError('Fulfillments, conditions and input_condition_uris must have the same count') else: From 5088ed356864291459315865949b464e7939c2d6 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 21 Sep 2016 11:24:03 +0200 Subject: [PATCH 30/98] Fix tests --- transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transaction.py b/transaction.py index 64fc8bc9..77d00d29 100644 --- a/transaction.py +++ b/transaction.py @@ -281,7 +281,7 @@ class Transaction(object): if operation not in Transaction.ALLOWED_OPERATIONS: raise TypeError('`operation` must be one of {}' - .format(', '.join(self.__cls__.ALLOWED_OPERATIONS))) + .format(', '.join(self.__class__.ALLOWED_OPERATIONS))) else: self.operation = operation From 833fa25ca3acb51dff39aec59c62e521c48a279f Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 4 Oct 2016 17:07:50 +0200 Subject: [PATCH 31/98] add fulfillment exception --- exceptions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/exceptions.py b/exceptions.py index 1b6d84d4..4e42ebee 100644 --- a/exceptions.py +++ b/exceptions.py @@ -64,3 +64,7 @@ class GenesisBlockAlreadyExistsError(Exception): 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""" From b48f301915299ac01cc70107b22a969aa8cf1067 Mon Sep 17 00:00:00 2001 From: tim Date: Tue, 11 Oct 2016 16:02:28 +0200 Subject: [PATCH 32/98] Make transaction.py compy to 79 chars --- transaction.py | 179 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 122 insertions(+), 57 deletions(-) diff --git a/transaction.py b/transaction.py index 77d00d29..562cff01 100644 --- a/transaction.py +++ b/transaction.py @@ -62,7 +62,7 @@ class Fulfillment(object): @classmethod def from_dict(cls, ffill): - """ Serializes a BigchainDB 'jsonized' fulfillment back to a BigchainDB Fulfillment class. + """Serializes a dictionary to a Fulfillment object. """ try: fulfillment = CCFulfillment.from_uri(ffill['fulfillment']) @@ -73,7 +73,8 @@ class Fulfillment(object): # NOTE: See comment about this special case in # `Fulfillment.to_dict` fulfillment = CCFulfillment.from_dict(ffill['fulfillment']) - return cls(fulfillment, ffill['owners_before'], TransactionLink.from_dict(ffill['input'])) + inp = TransactionLink.from_dict(ffill['input']) + return cls(fulfillment, ffill['owners_before'], inp) class TransactionLink(object): @@ -237,7 +238,11 @@ class Condition(object): class Data(object): def __init__(self, payload=None, payload_id=None): - self.payload_id = payload_id if payload_id is not None else self.to_hash() + if payload_id is not None: + self.payload_id = payload_id + else: + self.payload_id = self.to_hash() + if payload is not None and not isinstance(payload, dict): raise TypeError('`payload` must be a dict instance or None') else: @@ -276,12 +281,20 @@ class Transaction(object): def __init__(self, operation, fulfillments=None, conditions=None, data=None, timestamp=None, version=None): # TODO: Write a comment - self.timestamp = timestamp if timestamp is not None else gen_timestamp() - self.version = version if version is not None else Transaction.VERSION + if version is not None: + self.version = version + else: + self.version = self.__class__.VERSION + + if timestamp is not None: + self.timestamp = timestamp + else: + self.timestamp = gen_timestamp() if operation not in Transaction.ALLOWED_OPERATIONS: + allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS) raise TypeError('`operation` must be one of {}' - .format(', '.join(self.__class__.ALLOWED_OPERATIONS))) + .format(allowed_ops)) else: self.operation = operation @@ -339,7 +352,8 @@ class Transaction(object): ffill_tx = Fulfillment(ffill, owners_before) return cls(cls.CREATE, [ffill_tx], [cond_tx], data) - elif len(owners_before) == 1 and len(owners_after) == 0 and secret is not None: + 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) @@ -347,10 +361,13 @@ class Transaction(object): ffill_tx = Fulfillment(ffill, owners_before) return cls(cls.CREATE, [ffill_tx], [cond_tx], data) - 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 + 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: + 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: @@ -421,8 +438,10 @@ class Transaction(object): return inputs def add_fulfillment(self, fulfillment): - if fulfillment is not None and not isinstance(fulfillment, Fulfillment): - raise TypeError('`fulfillment` must be a Fulfillment instance or None') + if (fulfillment is not None and not + isinstance(fulfillment, Fulfillment)): + raise TypeError('`fulfillment` must be a Fulfillment instance or ' + 'None') else: self.fulfillments.append(fulfillment) @@ -436,7 +455,8 @@ class Transaction(object): # TODO: Singing should be possible with at least one of all private # keys supplied to this method. """ Signs a transaction - Acts as a proxy for `_sign_fulfillments`, for exposing a nicer API to the outside. + Acts as a proxy for `_sign_fulfillments`, for exposing a nicer API + to the outside. """ self._sign_fulfillments(private_keys) return self @@ -445,64 +465,91 @@ class Transaction(object): if private_keys is None or not isinstance(private_keys, list): raise TypeError('`private_keys` must be a list instance') - # Generate public keys from private keys and match them in a dictionary: + # TODO: Convert this comment to a doc string + # Generate public keys from private keys and match them in a + # dictionary: # key: public_key # value: private_key def gen_public_key(private_key): - # TODO FOR CC: Adjust interface so that this function becomes unnecessary + # TODO FOR CC: Adjust interface so that this function becomes + # unnecessary return private_key.get_verifying_key().to_ascii().decode() - key_pairs = {gen_public_key(SigningKey(private_key)): SigningKey(private_key) for private_key in private_keys} + key_pairs = {gen_public_key(SigningKey(private_key)): + SigningKey(private_key) for private_key in private_keys} - for index, (fulfillment, condition) in enumerate(zip(self.fulfillments, self.conditions)): - # 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, [fulfillment], [condition], self.data, self.timestamp, + zippedIO = enumerate(zip(self.fulfillments, self.conditions)) + for index, (fulfillment, condition) in zippedIO: + # 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, [fulfillment], + [condition], self.data, self.timestamp, self.version) - tx_serialized = Transaction._to_str(Transaction._remove_signatures(tx_partial.to_dict())) - self._sign_fulfillment(fulfillment, index, tx_serialized, key_pairs) + + tx_partial_dict = tx_partial.to_dict() + tx_partial_dict = Transaction._remove_signatures(tx_partial_dict) + tx_serialized = Transaction._to_str(tx_partial_dict) + + self._sign_fulfillment(fulfillment, index, tx_serialized, + key_pairs) def _sign_fulfillment(self, fulfillment, index, tx_serialized, key_pairs): if isinstance(fulfillment.fulfillment, Ed25519Fulfillment): - self._sign_simple_signature_fulfillment(fulfillment, index, tx_serialized, key_pairs) + self._sign_simple_signature_fulfillment(fulfillment, index, + tx_serialized, key_pairs) elif isinstance(fulfillment.fulfillment, ThresholdSha256Fulfillment): - self._sign_threshold_signature_fulfillment(fulfillment, index, tx_serialized, key_pairs) + self._sign_threshold_signature_fulfillment(fulfillment, index, + tx_serialized, + key_pairs) else: raise ValueError("Fulfillment couldn't be matched to " 'Cryptocondition fulfillment type.') - def _sign_simple_signature_fulfillment(self, fulfillment, index, tx_serialized, key_pairs): - # NOTE: To eliminate the dangers of accidentially signing a condition by reference, - # we remove the reference of fulfillment here intentionally. - # If the user of this class knows how to use it, this should never happen, - # but then again, never say never. + def _sign_simple_signature_fulfillment(self, fulfillment, index, + tx_serialized, key_pairs): + # NOTE: To eliminate the dangers of accidentally signing a condition by + # reference, we remove the reference of fulfillment here + # intentionally. If the user of this class knows how to use it, + # this should never happen, but then again, never say never. fulfillment = deepcopy(fulfillment) owner_before = fulfillment.owners_before[0] try: - fulfillment.fulfillment.sign(tx_serialized, key_pairs[owner_before]) + fulfillment.fulfillment.sign(tx_serialized, + key_pairs[owner_before]) except KeyError: - raise KeypairMismatchException('Public key {} is not a pair to any of the private keys' + raise KeypairMismatchException('Public key {} is not a pair to ' + 'any of the private keys' .format(owner_before)) self.fulfillments[index] = fulfillment - def _sign_threshold_signature_fulfillment(self, fulfillment, index, tx_serialized, key_pairs): + def _sign_threshold_signature_fulfillment(self, fulfillment, index, + tx_serialized, key_pairs): fulfillment = deepcopy(fulfillment) for owner_before in fulfillment.owners_before: try: - # TODO: CC should throw a KeypairMismatchException, instead of our manual mapping here - # TODO FOR CC: Naming wise this is not so smart, `get_subcondition` in fact doesn't return a condition - # but a fulfillment:( - # TODO FOR CC: `get_subcondition` is singular. One would not expect to get a list back. - subfulfillment = fulfillment.fulfillment.get_subcondition_from_vk(owner_before)[0] + # TODO: CC should throw a KeypairMismatchException, instead of + # our manual mapping here + + # TODO FOR CC: Naming wise this is not so smart, + # `get_subcondition` in fact doesn't return a + # condition but a fulfillment + + # TODO FOR CC: `get_subcondition` is singular. One would not + # expect to get a list back. + ccffill = fulfillment.fulfillment + subffill = ccffill.get_subcondition_from_vk(owner_before)[0] except IndexError: - raise KeypairMismatchException('Public key {} cannot be found in the fulfillment' + raise KeypairMismatchException('Public key {} cannot be found ' + 'in the fulfillment' .format(owner_before)) try: private_key = key_pairs[owner_before] except KeyError: - raise KeypairMismatchException('Public key {} is not a pair to any of the private keys' + raise KeypairMismatchException('Public key {} is not a pair ' + 'to any of the private keys' .format(owner_before)) - subfulfillment.sign(tx_serialized, private_key) + subffill.sign(tx_serialized, private_key) self.fulfillments[index] = fulfillment def fulfillments_valid(self, input_conditions=None): @@ -517,7 +564,9 @@ class Transaction(object): return self._fulfillments_valid([cond.fulfillment.condition_uri for cond in input_conditions]) else: - raise TypeError('`operation` must be either `TRANSFER`, `CREATE` or `GENESIS`') + allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS) + raise TypeError('`operation` must be one of {}' + .format(allowed_ops)) def _fulfillments_valid(self, input_condition_uris): input_condition_uris_count = len(input_condition_uris) @@ -527,30 +576,39 @@ class Transaction(object): def gen_tx(fulfillment, condition, input_condition_uri=None): tx = Transaction(self.operation, [fulfillment], [condition], self.data, self.timestamp, self.version) - tx_serialized = Transaction._to_str(Transaction._remove_signatures(tx.to_dict())) + tx_dict = tx.to_dict() + tx_dict = Transaction._remove_signatures(tx_dict) + tx_serialized = Transaction._to_str(tx_dict) return Transaction._fulfillment_valid(fulfillment, self.operation, tx_serialized, input_condition_uri) - if not fulfillments_count == conditions_count == input_condition_uris_count: - raise ValueError('Fulfillments, conditions and input_condition_uris must have the same count') + if not fulfillments_count == conditions_count == \ + input_condition_uris_count: + raise ValueError('Fulfillments, conditions and ' + 'input_condition_uris must have the same count') else: - return reduce(and_, map(gen_tx, self.fulfillments, self.conditions, input_condition_uris)) + return reduce(and_, map(gen_tx, self.fulfillments, self.conditions, + input_condition_uris)) @staticmethod - def _fulfillment_valid(fulfillment, operation, tx_serialized, input_condition_uri=None): + def _fulfillment_valid(fulfillment, operation, tx_serialized, + input_condition_uri=None): + ccffill = fulfillment.fulfillment try: - parsed_fulfillment = CCFulfillment.from_uri(fulfillment.fulfillment.serialize_uri()) + parsed_ffill = CCFulfillment.from_uri(ccffill.serialize_uri()) except (TypeError, ValueError, ParsingError): return False - if operation in (Transaction.CREATE, Transaction.GENESIS): - input_condition_valid = True - else: - input_condition_valid = input_condition_uri == fulfillment.fulfillment.condition_uri - # NOTE: We pass a timestamp to `.validate`, as in case of a timeout condition we'll have to validate against - # it. - return parsed_fulfillment.validate(message=tx_serialized, now=gen_timestamp()) and input_condition_valid + if operation in (Transaction.CREATE, Transaction.GENESIS): + input_cond_valid = True + else: + input_cond_valid = input_condition_uri == ccffill.condition_uri + + # NOTE: We pass a timestamp to `.validate`, as in case of a timeout + # condition we'll have to validate against it + return parsed_ffill.validate(message=tx_serialized, + now=gen_timestamp()) and input_cond_valid def to_dict(self): try: @@ -573,9 +631,11 @@ class Transaction(object): 'transaction': tx_body, } - tx_id = Transaction._to_hash(Transaction._to_str(Transaction._remove_signatures(tx))) - tx['id'] = tx_id + tx_no_signatures = Transaction._remove_signatures(tx) + tx_serialized = Transaction._to_str(tx_no_signatures) + tx_id = Transaction._to_hash(tx_serialized) + tx['id'] = tx_id return tx @staticmethod @@ -619,7 +679,11 @@ class Transaction(object): proposed_tx_id = tx_body.pop('id') except KeyError: raise InvalidHash() - valid_tx_id = Transaction._to_hash(Transaction._to_str(Transaction._remove_signatures(tx_body))) + + tx_body_no_signatures = Transaction._remove_signatures(tx_body) + tx_body_serialized = Transaction._to_str(tx_body_no_signatures) + valid_tx_id = Transaction._to_hash(tx_body_serialized) + if proposed_tx_id != valid_tx_id: raise InvalidHash() else: @@ -629,5 +693,6 @@ class Transaction(object): conditions = [Condition.from_dict(condition) for condition in tx['conditions']] data = Data.from_dict(tx['data']) + return cls(tx['operation'], fulfillments, conditions, data, tx['timestamp'], tx_body['version']) From c09b9fc0fd45c4873d24b987949f93a4969ddb93 Mon Sep 17 00:00:00 2001 From: tim Date: Tue, 11 Oct 2016 17:29:22 +0200 Subject: [PATCH 33/98] Make util.py comply to 79 chars --- util.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/util.py b/util.py index 4e7a9818..f6f671db 100644 --- a/util.py +++ b/util.py @@ -16,11 +16,12 @@ def gen_timestamp(): def serialize(data): """Serialize a dict into a JSON formatted string. - This function enforces rules like the separator and order of keys. This ensures that all dicts - are serialized in the same way. + This function enforces rules like the separator and order of keys. + This ensures that all dicts are serialized in the same way. - This is specially important for hashing data. We need to make sure that everyone serializes their data - in the same way so that we do not have hash mismatches for the same structure due to serialization + This is specially important for hashing data. We need to make sure that + everyone serializes their data in the same way so that we do not have + hash mismatches for the same structure due to serialization differences. Args: @@ -30,7 +31,8 @@ def serialize(data): str: JSON formatted string """ - return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, sort_keys=True) + return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, + sort_keys=True) def deserialize(data): @@ -40,6 +42,7 @@ def deserialize(data): data (str): JSON formatted string. Returns: - dict: dict resulting from the serialization of a JSON formatted string. + dict: dict resulting from the serialization of a JSON formatted + string. """ return rapidjson.loads(data) From 24852bd99fc9e1c0331a4e4d3ed5aa9999677407 Mon Sep 17 00:00:00 2001 From: tim Date: Tue, 11 Oct 2016 17:34:11 +0200 Subject: [PATCH 34/98] Make exceptions.py comply to 80 chars --- exceptions.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/exceptions.py b/exceptions.py index 4e42ebee..8e4d8eca 100644 --- a/exceptions.py +++ b/exceptions.py @@ -23,11 +23,13 @@ class DoubleSpend(Exception): class InvalidHash(Exception): - """Raised if there was an error checking the hash for a particular operation""" + """Raised if there was an error checking the hash for a particular + operation""" class InvalidSignature(Exception): - """Raised if there was an error checking the signature for a particular operation""" + """Raised if there was an error checking the signature for a particular + operation""" class DatabaseAlreadyExists(Exception): @@ -43,7 +45,8 @@ class KeypairNotFoundException(Exception): class KeypairMismatchException(Exception): - """Raised if the private key(s) provided for signing don't match any of the curret owner(s)""" + """Raised if the private key(s) provided for signing don't match any of the + current owner(s)""" class StartupError(Exception): @@ -67,4 +70,5 @@ class CyclicBlockchainError(Exception): class FulfillmentNotInValidBlock(Exception): - """Raised when a transaction depends on an invalid or undecided fulfillment""" + """Raised when a transaction depends on an invalid or undecided + fulfillment""" From fed1135c13c3c5c00b4062b4d03f5f695323e2dd Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 12 Oct 2016 13:56:14 +0200 Subject: [PATCH 35/98] Renaming inp to input_ --- transaction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transaction.py b/transaction.py index 562cff01..aebc3ac5 100644 --- a/transaction.py +++ b/transaction.py @@ -73,8 +73,8 @@ class Fulfillment(object): # NOTE: See comment about this special case in # `Fulfillment.to_dict` fulfillment = CCFulfillment.from_dict(ffill['fulfillment']) - inp = TransactionLink.from_dict(ffill['input']) - return cls(fulfillment, ffill['owners_before'], inp) + input_ = TransactionLink.from_dict(ffill['input']) + return cls(fulfillment, ffill['owners_before'], input_) class TransactionLink(object): From 4f72ba9e1e056034de7897b22f51e7a54f595d16 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 28 Sep 2016 10:24:55 +0200 Subject: [PATCH 36/98] Rename Data to Metadata --- transaction.py | 67 +++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/transaction.py b/transaction.py index aebc3ac5..6ac39b2f 100644 --- a/transaction.py +++ b/transaction.py @@ -236,35 +236,35 @@ class Condition(object): return cls(fulfillment, cond['owners_after']) -class Data(object): - def __init__(self, payload=None, payload_id=None): - if payload_id is not None: - self.payload_id = payload_id +class Metadata(object): + def __init__(self, data=None, data_id=None): + if data_id is not None: + self.data_id = data_id else: - self.payload_id = self.to_hash() + self.data_id = self.to_hash() - if payload is not None and not isinstance(payload, dict): - raise TypeError('`payload` must be a dict instance or None') + if data is not None and not isinstance(data, dict): + raise TypeError('`data` must be a dict instance or None') else: - self.payload = payload + self.data = data def __eq__(self, other): return self.to_dict() == other.to_dict() @classmethod - def from_dict(cls, payload): + def from_dict(cls, data): try: - return cls(payload['payload'], payload['uuid']) + return cls(data['data'], data['id']) except TypeError: return cls() def to_dict(self): - if self.payload is None: + if self.data is None: return None else: return { - 'payload': self.payload, - 'uuid': self.payload_id, + 'data': self.data, + 'id': self.data_id, } def to_hash(self): @@ -279,7 +279,7 @@ class Transaction(object): VERSION = 1 def __init__(self, operation, fulfillments=None, conditions=None, - data=None, timestamp=None, version=None): + metadata=None, timestamp=None, version=None): # TODO: Write a comment if version is not None: self.version = version @@ -312,10 +312,10 @@ class Transaction(object): else: self.fulfillments = fulfillments - if data is not None and not isinstance(data, Data): - raise TypeError('`data` must be a Data instance or None') + if metadata is not None and not isinstance(metadata, Metadata): + raise TypeError('`metadata` must be a Metadata instance or None') else: - self.data = data + self.metadata = metadata @classmethod def create(cls, owners_before, owners_after, payload=None, secret=None, @@ -325,7 +325,7 @@ class Transaction(object): if not isinstance(owners_after, list): raise TypeError('`owners_after` must be a list instance') - data = Data(payload) + metadata = Metadata(payload) 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 @@ -333,7 +333,7 @@ class Transaction(object): ffill = Ed25519Fulfillment(public_key=owners_before[0]) ffill_tx = Fulfillment(ffill, owners_before) cond_tx = Condition.generate(owners_after) - return cls(cls.CREATE, [ffill_tx], [cond_tx], data) + return cls(cls.CREATE, [ffill_tx], [cond_tx], metadata) elif len(owners_before) == len(owners_after) and len(owners_after) > 1: raise NotImplementedError('Multiple inputs and outputs not' @@ -343,14 +343,14 @@ class Transaction(object): [owner_before]) for owner_before in owners_before] conds = [Condition.generate(owners) for owners in owners_after] - return cls(cls.CREATE, ffills, conds, data) + return cls(cls.CREATE, ffills, conds, metadata) 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, [ffill_tx], [cond_tx], data) + return cls(cls.CREATE, [ffill_tx], [cond_tx], metadata) elif (len(owners_before) == 1 and len(owners_after) == 0 and secret is not None): @@ -359,7 +359,7 @@ class Transaction(object): cond_tx = Condition(hashlock.condition_uri) ffill = Ed25519Fulfillment(public_key=owners_before[0]) ffill_tx = Fulfillment(ffill, owners_before) - return cls(cls.CREATE, [ffill_tx], [cond_tx], data) + return cls(cls.CREATE, [ffill_tx], [cond_tx], metadata) elif (len(owners_before) > 0 and len(owners_after) == 0 and time_expire is not None): @@ -410,9 +410,9 @@ class Transaction(object): raise ValueError("`inputs` and `owners_after`'s count must be the " "same") - data = Data(payload) + metadata = Metadata(payload) inputs = deepcopy(inputs) - return cls(cls.TRANSFER, inputs, conditions, data) + return cls(cls.TRANSFER, inputs, conditions, metadata) def __eq__(self, other): try: @@ -483,8 +483,8 @@ class Transaction(object): # and fulfillment we're currently working on plus all # previously signed ones. tx_partial = Transaction(self.operation, [fulfillment], - [condition], self.data, self.timestamp, - self.version) + [condition], self.metadata, + self.timestamp, self.version) tx_partial_dict = tx_partial.to_dict() tx_partial_dict = Transaction._remove_signatures(tx_partial_dict) @@ -575,10 +575,11 @@ class Transaction(object): def gen_tx(fulfillment, condition, input_condition_uri=None): tx = Transaction(self.operation, [fulfillment], [condition], - self.data, self.timestamp, self.version) + self.metadata, self.timestamp, self.version) tx_dict = tx.to_dict() tx_dict = Transaction._remove_signatures(tx_dict) tx_serialized = Transaction._to_str(tx_dict) + return Transaction._fulfillment_valid(fulfillment, self.operation, tx_serialized, input_condition_uri) @@ -612,10 +613,10 @@ class Transaction(object): def to_dict(self): try: - data = self.data.to_dict() + metadata = self.metadata.to_dict() except AttributeError: - # NOTE: data can be None and that's OK - data = None + # NOTE: metadata can be None and that's OK + metadata = None tx_body = { 'fulfillments': [fulfillment.to_dict(fid) for fid, fulfillment @@ -624,7 +625,7 @@ class Transaction(object): in enumerate(self.conditions)], 'operation': str(self.operation), 'timestamp': self.timestamp, - 'data': data, + 'metadata': metadata, } tx = { 'version': self.version, @@ -692,7 +693,7 @@ class Transaction(object): in tx['fulfillments']] conditions = [Condition.from_dict(condition) for condition in tx['conditions']] - data = Data.from_dict(tx['data']) + metadata = Metadata.from_dict(tx['metadata']) - return cls(tx['operation'], fulfillments, conditions, data, + return cls(tx['operation'], fulfillments, conditions, metadata, tx['timestamp'], tx_body['version']) From 095f3c203de7331f35b43b2de71b3a5d2cedd046 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 28 Sep 2016 11:03:32 +0200 Subject: [PATCH 37/98] Add Asset exceptions --- exceptions.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/exceptions.py b/exceptions.py index 8e4d8eca..2e1dc670 100644 --- a/exceptions.py +++ b/exceptions.py @@ -72,3 +72,11 @@ class CyclicBlockchainError(Exception): class FulfillmentNotInValidBlock(Exception): """Raised when a transaction depends on an invalid or undecided fulfillment""" + + +class AssetIdMismatch(Exception): + """Raised when multiple transaction inputs related to different assets""" + + +class AmountError(Exception): + """Raised when the amount of a non-divisible asset is different then 1""" From 9df7c70720faab20d1fb56148cfba4127098c20b Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 28 Sep 2016 12:03:54 +0200 Subject: [PATCH 38/98] Add basic Asset model --- transaction.py | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/transaction.py b/transaction.py index 6ac39b2f..cf0d3e94 100644 --- a/transaction.py +++ b/transaction.py @@ -10,7 +10,8 @@ from cryptoconditions.exceptions import ParsingError from bigchaindb_common.crypto import SigningKey, hash_data from bigchaindb_common.exceptions import (KeypairMismatchException, - InvalidHash, InvalidSignature) + InvalidHash, InvalidSignature, + AmountError) from bigchaindb_common.util import serialize, gen_timestamp @@ -236,6 +237,45 @@ class Condition(object): return cls(fulfillment, cond['owners_after']) +class Asset(object): + def __init__(self, data=None, data_id=None, divisible=False, + updatable=False, refillable=False): + if data is not None and not isinstance(data, dict): + raise TypeError('`data` must be a dict instance or None') + else: + self.data = data + + # TODO: Add ID method here I guess + self.data_id = data_id if data_id is not None else self.to_hash() + self.divisible = divisible + self.updatable = updatable + self.refillable = refillable + + def __eq__(self, other): + try: + other_dict = other.to_dict() + except AttributeError: + return False + return self.to_dict() == other_dict + + def to_dict(self): + return { + 'id': self.data_id, + 'divisible': self.divisible, + 'updatable': self.updatable, + 'refillable': self.refillable, + 'data': self.data, + } + + @classmethod + def from_dict(cls, asset): + return cls(asset['data'], asset['id'], asset['divisible'], + asset['updatable'], asset['refillable']) + + def to_hash(self): + return str(uuid4()) + + class Metadata(object): def __init__(self, data=None, data_id=None): if data_id is not None: From 4390cfb8a953009af15e0ebba2795460e6f3ae36 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 28 Sep 2016 14:09:39 +0200 Subject: [PATCH 39/98] More renaming of payload => data --- transaction.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/transaction.py b/transaction.py index cf0d3e94..5c0878cd 100644 --- a/transaction.py +++ b/transaction.py @@ -10,8 +10,7 @@ from cryptoconditions.exceptions import ParsingError from bigchaindb_common.crypto import SigningKey, hash_data from bigchaindb_common.exceptions import (KeypairMismatchException, - InvalidHash, InvalidSignature, - AmountError) + InvalidHash, InvalidSignature) from bigchaindb_common.util import serialize, gen_timestamp @@ -245,7 +244,6 @@ class Asset(object): else: self.data = data - # TODO: Add ID method here I guess self.data_id = data_id if data_id is not None else self.to_hash() self.divisible = divisible self.updatable = updatable @@ -358,14 +356,14 @@ class Transaction(object): self.metadata = metadata @classmethod - def create(cls, owners_before, owners_after, payload=None, secret=None, + def create(cls, owners_before, owners_after, data=None, secret=None, time_expire=None): if not isinstance(owners_before, list): raise TypeError('`owners_before` must be a list instance') if not isinstance(owners_after, list): raise TypeError('`owners_after` must be a list instance') - metadata = Metadata(payload) + metadata = Metadata(data) 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 @@ -414,7 +412,7 @@ class Transaction(object): raise ValueError("These are not the cases you're looking for ;)") @classmethod - def transfer(cls, inputs, owners_after, payload=None): + def transfer(cls, inputs, owners_after, data=None): if not isinstance(inputs, list): raise TypeError('`inputs` must be a list instance') if len(inputs) == 0: @@ -450,7 +448,7 @@ class Transaction(object): raise ValueError("`inputs` and `owners_after`'s count must be the " "same") - metadata = Metadata(payload) + metadata = Metadata(data) inputs = deepcopy(inputs) return cls(cls.TRANSFER, inputs, conditions, metadata) From 5925c3a80ac236e75e39119f588c84b14aff7d79 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 28 Sep 2016 16:03:43 +0200 Subject: [PATCH 40/98] Add Asset into work-flow-functions --- transaction.py | 60 +++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/transaction.py b/transaction.py index 5c0878cd..b694ae87 100644 --- a/transaction.py +++ b/transaction.py @@ -1,6 +1,5 @@ from copy import deepcopy from functools import reduce -from operator import and_ from uuid import uuid4 from cryptoconditions import (Fulfillment as CCFulfillment, @@ -267,8 +266,8 @@ class Asset(object): @classmethod def from_dict(cls, asset): - return cls(asset['data'], asset['id'], asset['divisible'], - asset['updatable'], asset['refillable']) + return cls(asset.get('data'), asset['id'], asset.get('divisible'), + asset.get('updatable'), asset.get('refillable')) def to_hash(self): return str(uuid4()) @@ -316,7 +315,7 @@ class Transaction(object): ALLOWED_OPERATIONS = (CREATE, TRANSFER, GENESIS) VERSION = 1 - def __init__(self, operation, fulfillments=None, conditions=None, + def __init__(self, operation, asset, fulfillments=None, conditions=None, metadata=None, timestamp=None, version=None): # TODO: Write a comment if version is not None: @@ -336,6 +335,11 @@ class Transaction(object): else: self.operation = operation + if not isinstance(asset, Asset): + raise TypeError('`asset` must be an Asset instance') + else: + self.asset = asset + if conditions is not None and not isinstance(conditions, list): raise TypeError('`conditions` must be a list instance or None') elif conditions is None: @@ -356,14 +360,14 @@ class Transaction(object): self.metadata = metadata @classmethod - def create(cls, owners_before, owners_after, data=None, secret=None, - time_expire=None): + def create(cls, owners_before, owners_after, metadata=None, asset=None, + secret=None, time_expire=None): if not isinstance(owners_before, list): raise TypeError('`owners_before` must be a list instance') if not isinstance(owners_after, list): raise TypeError('`owners_after` must be a list instance') - metadata = Metadata(data) + 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 @@ -371,7 +375,7 @@ class Transaction(object): ffill = Ed25519Fulfillment(public_key=owners_before[0]) ffill_tx = Fulfillment(ffill, owners_before) cond_tx = Condition.generate(owners_after) - return cls(cls.CREATE, [ffill_tx], [cond_tx], metadata) + return cls(cls.CREATE, asset, [ffill_tx], [cond_tx], metadata) elif len(owners_before) == len(owners_after) and len(owners_after) > 1: raise NotImplementedError('Multiple inputs and outputs not' @@ -381,14 +385,14 @@ class Transaction(object): [owner_before]) for owner_before in owners_before] conds = [Condition.generate(owners) for owners in owners_after] - return cls(cls.CREATE, ffills, conds, metadata) + return cls(cls.CREATE, asset, ffills, conds, metadata) 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, [ffill_tx], [cond_tx], metadata) + return cls(cls.CREATE, asset, [ffill_tx], [cond_tx], metadata) elif (len(owners_before) == 1 and len(owners_after) == 0 and secret is not None): @@ -397,7 +401,7 @@ class Transaction(object): cond_tx = Condition(hashlock.condition_uri) ffill = Ed25519Fulfillment(public_key=owners_before[0]) ffill_tx = Fulfillment(ffill, owners_before) - return cls(cls.CREATE, [ffill_tx], [cond_tx], metadata) + 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): @@ -412,7 +416,7 @@ class Transaction(object): raise ValueError("These are not the cases you're looking for ;)") @classmethod - def transfer(cls, inputs, owners_after, data=None): + def transfer(cls, inputs, owners_after, metadata=None, asset=None): if not isinstance(inputs, list): raise TypeError('`inputs` must be a list instance') if len(inputs) == 0: @@ -448,9 +452,9 @@ class Transaction(object): raise ValueError("`inputs` and `owners_after`'s count must be the " "same") - metadata = Metadata(data) + metadata = Metadata(metadata) inputs = deepcopy(inputs) - return cls(cls.TRANSFER, inputs, conditions, metadata) + return cls(cls.TRANSFER, asset, inputs, conditions, metadata) def __eq__(self, other): try: @@ -503,7 +507,6 @@ class Transaction(object): if private_keys is None or not isinstance(private_keys, list): raise TypeError('`private_keys` must be a list instance') - # TODO: Convert this comment to a doc string # Generate public keys from private keys and match them in a # dictionary: # key: public_key @@ -520,14 +523,13 @@ class Transaction(object): # 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, [fulfillment], + tx_partial = Transaction(self.operation, self.asset, [fulfillment], [condition], self.metadata, self.timestamp, self.version) tx_partial_dict = tx_partial.to_dict() tx_partial_dict = Transaction._remove_signatures(tx_partial_dict) tx_serialized = Transaction._to_str(tx_partial_dict) - self._sign_fulfillment(fulfillment, index, tx_serialized, key_pairs) @@ -612,8 +614,9 @@ class Transaction(object): conditions_count = len(self.conditions) def gen_tx(fulfillment, condition, input_condition_uri=None): - tx = Transaction(self.operation, [fulfillment], [condition], - self.metadata, self.timestamp, self.version) + tx = Transaction(self.operation, self.asset, [fulfillment], + [condition], self.metadata, self.timestamp, + self.version) tx_dict = tx.to_dict() tx_dict = Transaction._remove_signatures(tx_dict) tx_serialized = Transaction._to_str(tx_dict) @@ -627,8 +630,9 @@ class Transaction(object): raise ValueError('Fulfillments, conditions and ' 'input_condition_uris must have the same count') else: - return reduce(and_, map(gen_tx, self.fulfillments, self.conditions, - input_condition_uris)) + partial_transactions = map(gen_tx, self.fulfillments, + self.conditions, input_condition_uris) + return all(partial_transactions) @staticmethod def _fulfillment_valid(fulfillment, operation, tx_serialized, @@ -656,6 +660,14 @@ class Transaction(object): # NOTE: metadata can be None and that's OK metadata = None + # TODO: At this point I'm not sure if this behavior shouldn't rather + # be implemented in the Asset's `to_dict` method. + if self.operation in (self.__class__.GENESIS, self.__class__.CREATE): + asset = self.asset.to_dict() + else: + # NOTE: A `asset` in a `TRANSFER` only contains the asset's id + asset = {'id': self.asset.data_id} + tx_body = { 'fulfillments': [fulfillment.to_dict(fid) for fid, fulfillment in enumerate(self.fulfillments)], @@ -664,6 +676,7 @@ class Transaction(object): 'operation': str(self.operation), 'timestamp': self.timestamp, 'metadata': metadata, + 'asset': asset, } tx = { 'version': self.version, @@ -732,6 +745,7 @@ 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']) - return cls(tx['operation'], fulfillments, conditions, metadata, - tx['timestamp'], tx_body['version']) + return cls(tx['operation'], asset, fulfillments, conditions, + metadata, tx['timestamp'], tx_body['version']) From e4a98cf5b5588b2349c4b4359860dde7c36e2690 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 28 Sep 2016 16:20:36 +0200 Subject: [PATCH 41/98] Add Asset amount to condition --- transaction.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/transaction.py b/transaction.py index b694ae87..345a4a8f 100644 --- a/transaction.py +++ b/transaction.py @@ -109,9 +109,11 @@ class TransactionLink(object): class Condition(object): - def __init__(self, fulfillment, owners_after=None): + def __init__(self, fulfillment, owners_after=None, amount=1): # TODO: Add more description self.fulfillment = fulfillment + # TODO: Not sure if we should validate for value here + self.amount = amount if not isinstance(owners_after, list) and owners_after is not None: raise TypeError('`owners_after` must be a list instance or None') @@ -137,7 +139,8 @@ class Condition(object): cond = { 'owners_after': self.owners_after, - 'condition': condition + 'condition': condition, + 'amount': self.amount } if cid is not None: cond['cid'] = cid @@ -232,7 +235,7 @@ class Condition(object): except KeyError: # NOTE: Hashlock condition case fulfillment = cond['condition']['uri'] - return cls(fulfillment, cond['owners_after']) + return cls(fulfillment, cond['owners_after'], cond['amount']) class Asset(object): From e71efb78751670843c9e3bcba882fda8529bc185 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Fri, 7 Oct 2016 15:46:55 +0200 Subject: [PATCH 42/98] initial integration of asset --- transaction.py | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/transaction.py b/transaction.py index 345a4a8f..f56d3b56 100644 --- a/transaction.py +++ b/transaction.py @@ -9,7 +9,8 @@ from cryptoconditions.exceptions import ParsingError from bigchaindb_common.crypto import SigningKey, hash_data from bigchaindb_common.exceptions import (KeypairMismatchException, - InvalidHash, InvalidSignature) + InvalidHash, InvalidSignature, + AmountError) from bigchaindb_common.util import serialize, gen_timestamp @@ -241,16 +242,14 @@ class Condition(object): class Asset(object): def __init__(self, data=None, data_id=None, divisible=False, updatable=False, refillable=False): - if data is not None and not isinstance(data, dict): - raise TypeError('`data` must be a dict instance or None') - else: - self.data = data - + self.data = data self.data_id = data_id if data_id is not None else self.to_hash() self.divisible = divisible self.updatable = updatable self.refillable = refillable + self._validate_asset() + def __eq__(self, other): try: other_dict = other.to_dict() @@ -269,12 +268,34 @@ class Asset(object): @classmethod def from_dict(cls, asset): - return cls(asset.get('data'), asset['id'], asset.get('divisible'), - asset.get('updatable'), asset.get('refillable')) + return cls(asset.get('data'), asset['id'], asset.get('divisible', False), + asset.get('updatable', False), asset.get('refillable', False)) def to_hash(self): return str(uuid4()) + def _validate_asset(self): + """Validate digital asset""" + if self.data is not None and not isinstance(self.data, dict): + raise TypeError('`data` must be a dict instance or None') + if not isinstance(self.divisible, bool): + raise TypeError('`divisible` must be a boolean') + if not isinstance(self.refillable, bool): + raise TypeError('`refillable` must be a boolean') + if not isinstance(self.updatable, bool): + raise TypeError('`updatable` must be a boolean') + + # TODO: amount needs to be validate, somehow, somewhere ... + # if not isinstance(self.amount, int): + # raise TypeError('`amount` must be an int') + # if self.divisible is False and self.amount != 1: + # raise AmountError('Non-divisible assets must have amount 1') + # if self.amount < 1: + # raise AmountError('The amount cannot be less then 1') + + # if self.divisible or self.updatable or self.refillable or self.amount != 1: + # raise NotImplementedError("Divisible assets are not yet implemented!") + class Metadata(object): def __init__(self, data=None, data_id=None): @@ -338,6 +359,10 @@ class Transaction(object): else: self.operation = operation + # If an asset is not defined in a `CREATE` transaction, create a default one. + if asset is None and operation == Transaction.CREATE: + asset = Asset() + if not isinstance(asset, Asset): raise TypeError('`asset` must be an Asset instance') else: @@ -419,7 +444,7 @@ class Transaction(object): raise ValueError("These are not the cases you're looking for ;)") @classmethod - def transfer(cls, inputs, owners_after, metadata=None, asset=None): + def transfer(cls, inputs, owners_after, asset, metadata=None): if not isinstance(inputs, list): raise TypeError('`inputs` must be a list instance') if len(inputs) == 0: From 997e497d0ac479cde8535f2f25ae7a755808f355 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 12 Oct 2016 15:55:50 +0200 Subject: [PATCH 43/98] fix pep8 issues --- transaction.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/transaction.py b/transaction.py index f56d3b56..eed38878 100644 --- a/transaction.py +++ b/transaction.py @@ -9,8 +9,7 @@ from cryptoconditions.exceptions import ParsingError from bigchaindb_common.crypto import SigningKey, hash_data from bigchaindb_common.exceptions import (KeypairMismatchException, - InvalidHash, InvalidSignature, - AmountError) + InvalidHash, InvalidSignature) from bigchaindb_common.util import serialize, gen_timestamp @@ -268,8 +267,10 @@ class Asset(object): @classmethod def from_dict(cls, asset): - return cls(asset.get('data'), asset['id'], asset.get('divisible', False), - asset.get('updatable', False), asset.get('refillable', False)) + return cls(asset.get('data'), asset['id'], + asset.get('divisible', False), + asset.get('updatable', False), + asset.get('refillable', False)) def to_hash(self): return str(uuid4()) @@ -285,17 +286,6 @@ class Asset(object): if not isinstance(self.updatable, bool): raise TypeError('`updatable` must be a boolean') - # TODO: amount needs to be validate, somehow, somewhere ... - # if not isinstance(self.amount, int): - # raise TypeError('`amount` must be an int') - # if self.divisible is False and self.amount != 1: - # raise AmountError('Non-divisible assets must have amount 1') - # if self.amount < 1: - # raise AmountError('The amount cannot be less then 1') - - # if self.divisible or self.updatable or self.refillable or self.amount != 1: - # raise NotImplementedError("Divisible assets are not yet implemented!") - class Metadata(object): def __init__(self, data=None, data_id=None): @@ -359,7 +349,8 @@ class Transaction(object): else: self.operation = operation - # If an asset is not defined in a `CREATE` transaction, create a default one. + # If an asset is not defined in a `CREATE` transaction, create a + # default one. if asset is None and operation == Transaction.CREATE: asset = Asset() From 55ee5550a04743ca839b4de3a6133b63ed91c65a Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 13 Oct 2016 10:46:24 +0200 Subject: [PATCH 44/98] Correct raised error --- transaction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transaction.py b/transaction.py index eed38878..d370a61c 100644 --- a/transaction.py +++ b/transaction.py @@ -344,8 +344,8 @@ class Transaction(object): if operation not in Transaction.ALLOWED_OPERATIONS: allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS) - raise TypeError('`operation` must be one of {}' - .format(allowed_ops)) + raise ValueError('`operation` must be one of {}' + .format(allowed_ops)) else: self.operation = operation From 95cd4037d56c8f46f2cd4688c9b7ec97c4d4c1ac Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 13 Oct 2016 10:48:52 +0200 Subject: [PATCH 45/98] Remove resolved TODOs --- transaction.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/transaction.py b/transaction.py index d370a61c..20d4de2e 100644 --- a/transaction.py +++ b/transaction.py @@ -679,8 +679,6 @@ class Transaction(object): # NOTE: metadata can be None and that's OK metadata = None - # TODO: At this point I'm not sure if this behavior shouldn't rather - # be implemented in the Asset's `to_dict` method. if self.operation in (self.__class__.GENESIS, self.__class__.CREATE): asset = self.asset.to_dict() else: From 470f2694d781614d39ad42bee91ff099c48b29ed Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Thu, 13 Oct 2016 16:33:41 +0200 Subject: [PATCH 46/98] Small modifications to support new cryptoconditions --- transaction.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/transaction.py b/transaction.py index 20d4de2e..984ee31b 100644 --- a/transaction.py +++ b/transaction.py @@ -533,7 +533,14 @@ class Transaction(object): def gen_public_key(private_key): # TODO FOR CC: Adjust interface so that this function becomes # unnecessary - return private_key.get_verifying_key().to_ascii().decode() + + # cc now provides a single method `encode` to return the key + # in several different encodings. + public_key = private_key.get_verifying_key().encode() + # Returned values from cc are always bytestrings so here we need + # to decode to convert the bytestring into a python str + return public_key.decode() + key_pairs = {gen_public_key(SigningKey(private_key)): SigningKey(private_key) for private_key in private_keys} @@ -573,7 +580,9 @@ class Transaction(object): fulfillment = deepcopy(fulfillment) owner_before = fulfillment.owners_before[0] try: - fulfillment.fulfillment.sign(tx_serialized, + # cryptoconditions makes no assumptions of the encoding of the + # message to sign or verify. It only accepts bytestrings + fulfillment.fulfillment.sign(tx_serialized.encode(), key_pairs[owner_before]) except KeyError: raise KeypairMismatchException('Public key {} is not a pair to ' @@ -608,7 +617,9 @@ class Transaction(object): 'to any of the private keys' .format(owner_before)) - subffill.sign(tx_serialized, private_key) + # cryptoconditions makes no assumptions of the encoding of the + # message to sign or verify. It only accepts bytestrings + subffill.sign(tx_serialized.encode(), private_key) self.fulfillments[index] = fulfillment def fulfillments_valid(self, input_conditions=None): @@ -669,7 +680,10 @@ class Transaction(object): # NOTE: We pass a timestamp to `.validate`, as in case of a timeout # condition we'll have to validate against it - return parsed_ffill.validate(message=tx_serialized, + + # cryptoconditions makes no assumptions of the encoding of the + # message to sign or verify. It only accepts bytestrings + return parsed_ffill.validate(message=tx_serialized.encode(), now=gen_timestamp()) and input_cond_valid def to_dict(self): From 39940d2a71f6eb108eb77dce1946394f75d5db54 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Thu, 13 Oct 2016 16:12:27 +0200 Subject: [PATCH 47/98] prevent adding None as fulfillment / condition to Transaction --- transaction.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/transaction.py b/transaction.py index 984ee31b..c40a3970 100644 --- a/transaction.py +++ b/transaction.py @@ -499,18 +499,14 @@ class Transaction(object): return inputs def add_fulfillment(self, fulfillment): - if (fulfillment is not None and not - isinstance(fulfillment, Fulfillment)): - raise TypeError('`fulfillment` must be a Fulfillment instance or ' - 'None') - else: - self.fulfillments.append(fulfillment) + if not isinstance(fulfillment, Fulfillment): + raise TypeError('`fulfillment` must be a Fulfillment instance') + self.fulfillments.append(fulfillment) def add_condition(self, condition): - if condition is not None and not isinstance(condition, Condition): + if not isinstance(condition, Condition): raise TypeError('`condition` must be a Condition instance or None') - else: - self.conditions.append(condition) + self.conditions.append(condition) def sign(self, private_keys): # TODO: Singing should be possible with at least one of all private From 76a0314d7cc102872bde451f860d5f5346eb9489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Thu, 20 Oct 2016 08:27:41 -0700 Subject: [PATCH 48/98] Improve documentation (#42) * Add doc strings for Fulfillment cls * Add doc strings for TransactionLink cls * Add doc strings for Condition cls * Add doc strings for Data cls * Add doc strings for Transaction cls * Add doc strings for Asset cls --- transaction.py | 590 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 529 insertions(+), 61 deletions(-) diff --git a/transaction.py b/transaction.py index c40a3970..e8f7a63d 100644 --- a/transaction.py +++ b/transaction.py @@ -14,7 +14,30 @@ from bigchaindb_common.util import serialize, gen_timestamp class Fulfillment(object): + """A Fulfillment is used to spend assets locked by a Condition. + + Attributes: + fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment + to be signed with a private key. + owners_before (:obj:`list` of :obj:`str`): A list of owners after a + Transaction was confirmed. + tx_input (:class:`~bigchaindb_common.transaction. TransactionLink`, + optional): A link representing the input of a `TRANSFER` + Transaction. + """ + def __init__(self, fulfillment, owners_before, tx_input=None): + """Fulfillment shims a Cryptocondition Fulfillment for BigchainDB. + + Args: + fulfillment (:class:`cryptoconditions.Fulfillment`): A + Fulfillment to be signed with a private key. + owners_before (:obj:`list` of :obj:`str`): A list of owners + after a Transaction was confirmed. + tx_input (:class:`~bigchaindb_common.transaction. + TransactionLink`, optional): A link representing the input + of a `TRANSFER` Transaction. + """ self.fulfillment = fulfillment if tx_input is not None and not isinstance(tx_input, TransactionLink): @@ -28,9 +51,25 @@ class Fulfillment(object): self.owners_before = owners_before def __eq__(self, other): + # TODO: If `other !== Fulfillment` return `False` return self.to_dict() == other.to_dict() def to_dict(self, fid=None): + """Transforms the object to a Python dictionary. + + Note: + A `fid` can be submitted to be included in the dictionary + representation. + + If a Fulfillment hasn't been signed yet, this method returns a + dictionary representation. + + Args: + fid (int, optional): The Fulfillment's index in a Transaction. + + Returns: + dict: The Fulfillment as an alternative serialization format. + """ try: fulfillment = self.fulfillment.serialize_uri() except (TypeError, AttributeError): @@ -61,7 +100,20 @@ class Fulfillment(object): @classmethod def from_dict(cls, ffill): - """Serializes a dictionary to a Fulfillment object. + """Transforms a Python dictionary to a Fulfillment object. + + Note: + Optionally, this method can also serialize a Cryptoconditions- + Fulfillment that is not yet signed. + + Args: + ffill (dict): The Fulfillment to be transformed. + + Returns: + :class:`~bigchaindb_common.transaction.Fulfillment` + + Raises: + InvalidSignature: If a Fulfillment's URI couldn't be parsed. """ try: fulfillment = CCFulfillment.from_uri(ffill['fulfillment']) @@ -77,11 +129,29 @@ class Fulfillment(object): class TransactionLink(object): - # NOTE: In an IPLD implementation, this class is not necessary anymore, - # as an IPLD link can simply point to an object, as well as an objects - # properties. So instead of having a (de)serializable class, we can have a - # simple IPLD link of the form: `//transaction/conditions//` + """An object for unidirectional linking to a Transaction's Condition. + + Attributes: + txid (str, optional): A Transaction to link to. + cid (int, optional): A Condition's index in a Transaction with id + `txid`. + """ + def __init__(self, txid=None, cid=None): + """Used to point to a specific Condition of a Transaction. + + Note: + In an IPLD implementation, this class is not necessary anymore, + as an IPLD link can simply point to an object, as well as an + objects properties. So instead of having a (de)serializable + class, we can have a simple IPLD link of the form: + `//transaction/conditions//`. + + Args: + txid (str, optional): A Transaction to link to. + cid (int, optional): A Condition's index in a Transaction with + id `txid`. + """ self.txid = txid self.cid = cid @@ -89,16 +159,30 @@ class TransactionLink(object): return self.txid is not None and self.cid is not None def __eq__(self, other): + # TODO: If `other !== TransactionLink` return `False` return self.to_dict() == self.to_dict() @classmethod def from_dict(cls, link): + """Transforms a Python dictionary to a TransactionLink object. + + Args: + link (dict): The link to be transformed. + + Returns: + :class:`~bigchaindb_common.transaction.TransactionLink` + """ try: return cls(link['txid'], link['cid']) 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.txid is None and self.cid is None: return None else: @@ -109,8 +193,29 @@ class TransactionLink(object): class Condition(object): + """A Condition is used to lock an asset. + + Attributes: + fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment + to extract a Condition from. + owners_after (:obj:`list` of :obj:`str`, optional): A list of + owners before a Transaction was confirmed. + """ + def __init__(self, fulfillment, owners_after=None, amount=1): - # TODO: Add more description + """Condition shims a Cryptocondition condition for BigchainDB. + + Args: + fulfillment (:class:`cryptoconditions.Fulfillment`): A + Fulfillment to extract a Condition from. + owners_after (:obj:`list` of :obj:`str`, optional): A list of + owners before a Transaction was confirmed. + amount (int): The amount of Assets to be locked with this + Condition. + + Raises: + TypeError: if `owners_after` is not instance of `list`. + """ self.fulfillment = fulfillment # TODO: Not sure if we should validate for value here self.amount = amount @@ -121,9 +226,25 @@ class Condition(object): self.owners_after = owners_after def __eq__(self, other): + # TODO: If `other !== Condition` return `False` return self.to_dict() == other.to_dict() def to_dict(self, cid=None): + """Transforms the object to a Python dictionary. + + Note: + A `cid` can be submitted to be included in the dictionary + representation. + + A dictionary serialization of the Fulfillment the Condition was + derived from is always provided. + + Args: + cid (int, optional): The Condition's index in a Transaction. + + Returns: + dict: The Condition as an alternative serialization format. + """ # TODO FOR CC: It must be able to recognize a hashlock condition # and fulfillment! condition = {} @@ -148,30 +269,38 @@ class Condition(object): @classmethod def generate(cls, owners_after): - """Generates conditions from a specifically formed tuple or list. + """Generates a Condition from a specifically formed tuple or list. - If a ThresholdCondition has to be generated where the threshold is - always the number of subconditions it is split between, a list of - the following structure is sufficient: + Note: + If a ThresholdCondition has to be generated where the threshold + is always the number of subconditions it is split between, a + list of the following structure is sufficient: - [(address|condition)*, [(address|condition)*, ...], ...] + [(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: + 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) + ([(address|condition)*, + ([(address|condition)*, ...], subthreshold), + ...], threshold) Args: - owners_after (list|tuple): The users that should be able to - fulfill the condition that is being - created. - Returns: - A `Condition` that can be used in a `Transaction`. + owners_after (:obj:`list` of :obj:`str`|tuple): The users that + should be able to fulfill the Condition that is being + created. + Returns: + A Condition that can be used in a Transaction. + + Returns: + TypeError: If `owners_after` is not an instance of `list`. + TypeError: If `owners_after` is an empty list. """ + # TODO: We probably want to remove the tuple logic for weights here + # again: https://github.com/bigchaindb/bigchaindb-common/issues/ + # 12#issuecomment-251665325 if isinstance(owners_after, tuple): owners_after, threshold = owners_after else: @@ -196,6 +325,22 @@ class Condition(object): @classmethod def _gen_condition(cls, initial, current): + """Generates ThresholdSha256 conditions from a list of new owners. + + Note: + This method is intended only to be used with a reduce function. + For a description on how to use this method, see + `Condition.generate`. + + Args: + initial (:class:`cryptoconditions.ThresholdSha256Fulfillment`): + A Condition representing the overall root. + current (:obj:`list` of :obj:`str`|str): A list of new owners + or a single new owner. + + Returns: + :class:`cryptoconditions.ThresholdSha256Fulfillment`: + """ if isinstance(current, tuple): owners_after, threshold = current else: @@ -230,6 +375,20 @@ class Condition(object): @classmethod def from_dict(cls, cond): + """Transforms a Python dictionary to a Condition object. + + Note: + To pass a serialization cycle multiple times, a + Cryptoconditions Fulfillment needs to be present in the + passed-in dictionary, as Condition URIs are not serializable + anymore. + + Args: + cond (dict): The Condition to be transformed. + + Returns: + :class:`~bigchaindb_common.transaction.Condition` + """ try: fulfillment = CCFulfillment.from_dict(cond['condition']['details']) except KeyError: @@ -239,8 +398,25 @@ class Condition(object): class Asset(object): + """An Asset is a fungible unit to spend and lock with Transactions. + + Note: + Currently, the following flags are not yet fully supported: + - `divisible` + - `updatable` + - `refillable` + + Attributes: + data (dict): A dictionary of data that can be added to an Asset. + data_id (str): A unique identifier of `data`'s content. + divisible (bool): A flag indicating if an Asset can be divided. + updatable (bool): A flag indicating if an Asset can be updated. + refillable (bool): A flag indicating if an Asset can be refilled. + """ + def __init__(self, data=None, data_id=None, divisible=False, updatable=False, refillable=False): + """An Asset is not required to contain any extra data from outside.""" self.data = data self.data_id = data_id if data_id is not None else self.to_hash() self.divisible = divisible @@ -257,6 +433,12 @@ class Asset(object): return self.to_dict() == other_dict def to_dict(self): + """Transforms the object to a Python dictionary. + + Returns: + (dict): The Asset object as an alternative serialization + format. + """ return { 'id': self.data_id, 'divisible': self.divisible, @@ -267,16 +449,25 @@ class Asset(object): @classmethod def from_dict(cls, asset): + """Transforms a Python dictionary to an Asset object. + + Args: + asset (dict): The dictionary to be serialized. + + Returns: + :class:`~bigchaindb_common.transaction.Asset` + """ return cls(asset.get('data'), asset['id'], asset.get('divisible', False), asset.get('updatable', False), asset.get('refillable', False)) def to_hash(self): + """Generates a unqiue uuid for an Asset""" return str(uuid4()) def _validate_asset(self): - """Validate digital asset""" + """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') if not isinstance(self.divisible, bool): @@ -288,7 +479,21 @@ class Asset(object): class Metadata(object): + """Metadata is used to store a dictionary and its hash in a Transaction.""" + def __init__(self, data=None, data_id=None): + """Metadata stores a payload `data` as well as data's hash, `data_id`. + + Note: + When no `data_id` is provided, one is being generated by + this method. + + Args: + data (dict): A dictionary to be held by Metadata. + data_id (str): A hash corresponding to the contents of + `data`. + """ + # TODO: Rename `payload_id` to `id` if data_id is not None: self.data_id = data_id else: @@ -300,16 +505,31 @@ class Metadata(object): self.data = data def __eq__(self, other): + # TODO: If `other !== Data` return `False` return self.to_dict() == other.to_dict() @classmethod def from_dict(cls, data): + """Transforms a Python dictionary to a Metadata object. + + Args: + data (dict): The dictionary to be serialized. + + Returns: + :class:`~bigchaindb_common.transaction.Metadata` + """ try: return cls(data['data'], data['id']) except TypeError: return cls() def to_dict(self): + """Transforms the object to a Python dictionary. + + Returns: + (dict|None): The Metadata object as an alternative + serialization format. + """ if self.data is None: return None else: @@ -319,10 +539,29 @@ class Metadata(object): } def to_hash(self): + """A hash corresponding to the contents of `payload`.""" return str(uuid4()) class Transaction(object): + """A Transaction is used to create and transfer assets. + + Note: + For adding Fulfillments and Conditions, this class provides methods + to do so. + + Attributes: + operation (str): Defines the operation of the Transaction. + fulfillments (:obj:`list` of :class:`~bigchaindb_common. + transaction.Fulfillment`, optional): Define the assets to + spend. + conditions (:obj:`list` of :class:`~bigchaindb_common. + transaction.Condition`, optional): Define the assets to lock. + metadata (:class:`~bigchaindb_common.transaction.Metadata`): + Metadata to be stored along with the Transaction. + timestamp (int): Defines the time a Transaction was created. + version (int): Defines the version number of a Transaction. + """ CREATE = 'CREATE' TRANSFER = 'TRANSFER' GENESIS = 'GENESIS' @@ -331,7 +570,28 @@ class Transaction(object): def __init__(self, operation, asset, fulfillments=None, conditions=None, metadata=None, timestamp=None, version=None): - # TODO: Write a comment + """The constructor allows to create a customizable Transaction. + + Note: + When no `version` or `timestamp`, is provided, one is being + generated by this method. + + Args: + operation (str): Defines the operation of the Transaction. + asset (:class:`~bigchaindb_common.transaction.Asset`): An Asset + to be transferred or created in a Transaction. + fulfillments (:obj:`list` of :class:`~bigchaindb_common. + transaction.Fulfillment`, optional): Define the assets to + spend. + conditions (:obj:`list` of :class:`~bigchaindb_common. + transaction.Condition`, optional): Define the assets to + lock. + metadata (:class:`~bigchaindb_common.transaction.Metadata`): + Metadata to be stored along with the Transaction. + timestamp (int): Defines the time a Transaction was created. + version (int): Defines the version number of a Transaction. + + """ if version is not None: self.version = version else: @@ -381,6 +641,36 @@ class Transaction(object): @classmethod def create(cls, owners_before, owners_after, metadata=None, asset=None, secret=None, time_expire=None): + """A simple way to generate a `CREATE` transaction. + + Note: + This method currently supports the following Cryptoconditions + use cases: + - Ed25519 + - ThresholdSha256 + - PreimageSha256. + + Additionally, it provides support for the following BigchainDB + use cases: + - Multiple inputs and outputs. + + Args: + owners_before (:obj:`list` of :obj:`str`): A list of keys that + represent the creators of this asset. + owners_after (:obj:`list` of :obj:`str`): A list of keys that + represent the receivers of this Transaction. + metadata (dict): Python dictionary to be stored along with the + 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` + """ if not isinstance(owners_before, list): raise TypeError('`owners_before` must be a list instance') if not isinstance(owners_after, list): @@ -436,6 +726,43 @@ class Transaction(object): @classmethod def transfer(cls, inputs, owners_after, asset, metadata=None): + """A simple way to generate a `TRANSFER` transaction. + + Note: + Different cases for threshold conditions: + + Combining multiple `inputs` with an arbitrary number of + `owners_after` can yield interesting cases for the creation of + threshold conditions we'd like to support. The following + notation is proposed: + + 1. The index of an `owner_after` corresponds to the index of + an input: + e.g. `transfer([input1], [a])`, means `input1` would now be + owned by user `a`. + + 2. `owners_after` can (almost) get arbitrary deeply nested, + creating various complex threshold conditions: + e.g. `transfer([inp1, inp2], [[a, [b, c]], d])`, means + `a`'s signature would have a 50% weight on `inp1` + compared to `b` and `c` that share 25% of the leftover + weight respectively. `inp2` is owned completely by `d`. + + Args: + inputs (:obj:`list` of :class:`~bigchaindb_common.transaction. + Fulfillment`): Converted "output" Conditions, intended to + be used as "input" Fulfillments in the transfer to + generate. + owners_after (:obj:`list` of :obj:`str`): A list of keys that + represent the receivers of this Transaction. + asset (:class:`~bigchaindb_common.transaction.Asset`): An Asset + to be transferred in this Transaction. + metadata (dict): Python dictionary to be stored along with the + Transaction. + + Returns: + :class:`~bigchaindb_common.transaction.Transaction` + """ if not isinstance(inputs, list): raise TypeError('`inputs` must be a list instance') if len(inputs) == 0: @@ -443,24 +770,7 @@ class Transaction(object): if not isinstance(owners_after, list): raise TypeError('`owners_after` must be a list instance') - # NOTE: Different cases for threshold conditions: - # - # Combining multiple `inputs` with an arbitrary number of - # `owners_after` can yield interesting cases for the creation of - # threshold conditions we'd like to support. The following - # notation is proposed: - # - # 1. The index of an `owner_after` corresponds to the index of - # an input: - # e.g. `transfer([input1], [a])`, means `input1` would now be - # owned by user `a`. - # - # 2. `owners_after` can (almost) get arbitrary deeply nested, - # creating various complex threshold conditions: - # e.g. `transfer([inp1, inp2], [[a, [b, c]], d])`, means - # `a`'s signature would have a 50% weight on `inp1` - # compared to `b` and `c` that share 25% of the leftover - # weight respectively. `inp2` is owned completely by `d`. + # NOTE: See doc strings `Note` for description. if len(inputs) == len(owners_after): if len(owners_after) == 1: conditions = [Condition.generate(owners_after)] @@ -483,10 +793,29 @@ class Transaction(object): return self.to_dict() == other def to_inputs(self, condition_indices=None): + """Converts a Transaction's Conditions to spendable Fulfillments. + + Note: + Takes the Transaction's Conditions and derives Fulfillments + from it that can then be passed into `Transaction.transfer` as + `inputs`. + A list of integers can be passed to `condition_indices` that + defines which Conditions should be returned as inputs. + If no `condition_indices` are passed (empty list or None) all + Condition of the Transaction are passed. + + Args: + condition_indices (:obj:`list` of int): Defines which + Conditions should be returned as inputs. + + Returns: + :obj:`list` of :class:`~bigchaindb_common.transaction. + Fulfillment` + """ inputs = [] - # NOTE: If no condition indices are passed, we just assume to - # take all conditions as 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)] @@ -499,33 +828,58 @@ class Transaction(object): return inputs def add_fulfillment(self, fulfillment): + """Adds a Fulfillment to a Transaction's list of Fulfillments. + + Args: + fulfillment (:class:`~bigchaindb_common.transaction. + Fulfillment`): A Fulfillment to be added to the + Transaction. + """ if not isinstance(fulfillment, Fulfillment): raise TypeError('`fulfillment` must be a Fulfillment instance') self.fulfillments.append(fulfillment) def add_condition(self, condition): + """Adds a Condition to a Transaction's list of Conditions. + + Args: + condition (:class:`~bigchaindb_common.transaction. + Condition`): A Condition to be added to the + Transaction. + """ if not isinstance(condition, Condition): raise TypeError('`condition` must be a Condition instance or None') self.conditions.append(condition) def sign(self, private_keys): + """Fulfills a previous Transaction's Condition by signing Fulfillments. + + Note: + This method works only for the following Cryptoconditions + currently: + - Ed25519Fulfillment + - ThresholdSha256Fulfillment + Furthermore, note that all keys required to fully sign the + Transaction have to be passed to this method. A subset of all + will cause this method to fail. + + Args: + private_keys (:obj:`list` of :obj:`str`): A complete list of + all private keys needed to sign all Fulfillments of this + Transaction. + + Returns: + :class:`~bigchaindb_common.transaction.Transaction` + """ # TODO: Singing should be possible with at least one of all private # keys supplied to this method. - """ Signs a transaction - Acts as a proxy for `_sign_fulfillments`, for exposing a nicer API - to the outside. - """ - self._sign_fulfillments(private_keys) - return self - - def _sign_fulfillments(self, private_keys): if private_keys is None or not isinstance(private_keys, list): raise TypeError('`private_keys` must be a list instance') - # Generate public keys from private keys and match them in a - # dictionary: - # key: public_key - # value: private_key + # NOTE: Generate public keys from private keys and match them in a + # dictionary: + # key: public_key + # value: private_key def gen_public_key(private_key): # TODO FOR CC: Adjust interface so that this function becomes # unnecessary @@ -554,8 +908,25 @@ class Transaction(object): tx_serialized = Transaction._to_str(tx_partial_dict) self._sign_fulfillment(fulfillment, index, tx_serialized, key_pairs) + return self def _sign_fulfillment(self, fulfillment, index, tx_serialized, key_pairs): + """Signs a single Fulfillment with a partial Transaction as message. + + Note: + This method works only for the following Cryptoconditions + currently: + - Ed25519Fulfillment + - ThresholdSha256Fulfillment. + + Args: + fulfillment (:class:`~bigchaindb_common.transaction. + Fulfillment`) The Fulfillment to be signed. + index (int): The index (or `fid`) of the Fulfillment to be + signed. + tx_serialized (str): The Transaction to be used as message. + key_pairs (dict): The keys to sign the Transaction with. + """ if isinstance(fulfillment.fulfillment, Ed25519Fulfillment): self._sign_simple_signature_fulfillment(fulfillment, index, tx_serialized, key_pairs) @@ -569,6 +940,16 @@ class Transaction(object): def _sign_simple_signature_fulfillment(self, fulfillment, index, tx_serialized, key_pairs): + """Signs a Ed25519Fulfillment. + + Args: + fulfillment (:class:`~bigchaindb_common.transaction. + Fulfillment`) The Fulfillment to be signed. + index (int): The index (or `fid`) of the Fulfillment to be + signed. + tx_serialized (str): The Transaction to be used as message. + key_pairs (dict): The keys to sign the Transaction with. + """ # NOTE: To eliminate the dangers of accidentally signing a condition by # reference, we remove the reference of fulfillment here # intentionally. If the user of this class knows how to use it, @@ -588,6 +969,16 @@ class Transaction(object): def _sign_threshold_signature_fulfillment(self, fulfillment, index, tx_serialized, key_pairs): + """Signs a ThresholdSha256Fulfillment. + + Args: + fulfillment (:class:`~bigchaindb_common.transaction. + Fulfillment`) The Fulfillment to be signed. + index (int): The index (or `fid`) of the Fulfillment to be + signed. + tx_serialized (str): The Transaction to be used as message. + key_pairs (dict): The keys to sign the Transaction with. + """ fulfillment = deepcopy(fulfillment) for owner_before in fulfillment.owners_before: try: @@ -619,8 +1010,24 @@ class Transaction(object): self.fulfillments[index] = fulfillment def fulfillments_valid(self, input_conditions=None): + """Validates the Fulfillments in the Transaction against given + Conditions. + + Note: + Given a `CREATE` or `GENESIS` Transaction is passed, + dummyvalues for Conditions are submitted for validation that + evaluate parts of the validation-checks to `True`. + + Args: + input_conditions (:obj:`list` of :class:`~bigchaindb_common. + transaction.Condition`): A list of Conditions to check the + Fulfillments against. + + Returns: + bool: If all Fulfillments are valid. + """ if self.operation in (Transaction.CREATE, Transaction.GENESIS): - # NOTE: Since in the case of a create-transaction we do not have + # NOTE: Since in the case of a `CREATE`-transaction we do not have # to check for input_conditions, we're just submitting dummy # values to the actual method. This simplifies it's logic # greatly, as we do not have to check against `None` values. @@ -635,11 +1042,27 @@ class Transaction(object): .format(allowed_ops)) def _fulfillments_valid(self, input_condition_uris): + """Validates a Fulfillment against a given set of Conditions. + + Note: + The number of `input_condition_uris` must be equal to the + number of Fulfillments a Transaction has. + + Args: + input_condition_uris (:obj:`list` of :obj:`str`): A list of + Conditions to check the Fulfillments against. + + Returns: + bool: If all Fulfillments are valid. + """ 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.version) @@ -647,6 +1070,7 @@ class Transaction(object): tx_dict = Transaction._remove_signatures(tx_dict) tx_serialized = Transaction._to_str(tx_dict) + # TODO: Use local reference to class, not `Transaction.` return Transaction._fulfillment_valid(fulfillment, self.operation, tx_serialized, input_condition_uri) @@ -663,6 +1087,24 @@ class Transaction(object): @staticmethod def _fulfillment_valid(fulfillment, operation, tx_serialized, input_condition_uri=None): + """Validates a single Fulfillment against a single Condition. + + Note: + In case of a `CREATE` or `GENESIS` Transaction, this method + does not validate against `input_condition_uri`. + + Args: + fulfillment (:class:`~bigchaindb_common.transaction. + Fulfillment`) The Fulfillment to be signed. + operation (str): The type of Transaction. + tx_serialized (str): The Transaction used as a message when + initially signing it. + input_condition_uri (str, optional): A Condition to check the + Fulfillment against. + + Returns: + bool: If the Fulfillment is valid. + """ ccffill = fulfillment.fulfillment try: parsed_ffill = CCFulfillment.from_uri(ccffill.serialize_uri()) @@ -670,6 +1112,8 @@ class Transaction(object): return False if operation in (Transaction.CREATE, Transaction.GENESIS): + # NOTE: In the case of a `CREATE` or `GENESIS` transaction, the + # input condition is always validate to `True`. input_cond_valid = True else: input_cond_valid = input_condition_uri == ccffill.condition_uri @@ -683,6 +1127,11 @@ class Transaction(object): now=gen_timestamp()) and input_cond_valid def to_dict(self): + """Transforms the object to a Python dictionary. + + Returns: + dict: The Transaction as an alternative serialization format. + """ try: metadata = self.metadata.to_dict() except AttributeError: @@ -692,7 +1141,7 @@ class Transaction(object): if self.operation in (self.__class__.GENESIS, self.__class__.CREATE): asset = self.asset.to_dict() else: - # NOTE: A `asset` in a `TRANSFER` only contains the asset's id + # NOTE: An `asset` in a `TRANSFER` only contains the asset's id asset = {'id': self.asset.data_id} tx_body = { @@ -718,15 +1167,25 @@ class Transaction(object): return tx @staticmethod + # TODO: Remove `_dict` prefix of variable. def _remove_signatures(tx_dict): + """Takes a Transaction dictionary and removes all signatures. + + Args: + tx_dict (dict): The Transaction to remove all signatures from. + + Returns: + dict + + """ # NOTE: We remove the reference since we need `tx_dict` only for the # transaction's hash tx_dict = deepcopy(tx_dict) for fulfillment in tx_dict['transaction']['fulfillments']: # NOTE: Not all Cryptoconditions return a `signature` key (e.g. - # ThresholdSha256Fulfillment), so setting it to `None` in any case - # could yield incorrect signatures. This is why we only set it to - # `None` if it's set in the dict. + # ThresholdSha256Fulfillment), so setting it to `None` in any + # case could yield incorrect signatures. This is why we only + # set it to `None` if it's set in the dict. fulfillment['fulfillment'] = None return tx_dict @@ -745,6 +1204,7 @@ class Transaction(object): def _to_str(value): return serialize(value) + # TODO: This method shouldn't call `_remove_signatures` def __str__(self): tx = Transaction._remove_signatures(self.to_dict()) return Transaction._to_str(tx) @@ -752,6 +1212,14 @@ class Transaction(object): @classmethod # TODO: Make this method more pretty def from_dict(cls, tx_body): + """Transforms a Python dictionary to a Transaction object. + + Args: + tx_body (dict): The Transaction to be transformed. + + Returns: + :class:`~bigchaindb_common.transaction.Transaction` + """ # NOTE: Remove reference to avoid side effects tx_body = deepcopy(tx_body) try: From 50647c997e3da8f870fa060626bbcb4e97909cfe Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Mon, 24 Oct 2016 14:31:39 +0200 Subject: [PATCH 49/98] Extract common implementation --- crypto.py => bigchaindb/common/crypto.py | 0 exceptions.py => bigchaindb/common/exceptions.py | 0 transaction.py => bigchaindb/common/transaction.py | 0 util.py => bigchaindb/common/util.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename crypto.py => bigchaindb/common/crypto.py (100%) rename exceptions.py => bigchaindb/common/exceptions.py (100%) rename transaction.py => bigchaindb/common/transaction.py (100%) rename util.py => bigchaindb/common/util.py (100%) diff --git a/crypto.py b/bigchaindb/common/crypto.py similarity index 100% rename from crypto.py rename to bigchaindb/common/crypto.py diff --git a/exceptions.py b/bigchaindb/common/exceptions.py similarity index 100% rename from exceptions.py rename to bigchaindb/common/exceptions.py diff --git a/transaction.py b/bigchaindb/common/transaction.py similarity index 100% rename from transaction.py rename to bigchaindb/common/transaction.py diff --git a/util.py b/bigchaindb/common/util.py similarity index 100% rename from util.py rename to bigchaindb/common/util.py From a554fab1dca57cc5b562ea95bf8da0299662f9b9 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 27 Jul 2016 15:30:21 +0200 Subject: [PATCH 50/98] Tx model: Add test for empty inputs --- test_transaction.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 test_transaction.py diff --git a/test_transaction.py b/test_transaction.py new file mode 100644 index 00000000..f13eba17 --- /dev/null +++ b/test_transaction.py @@ -0,0 +1,43 @@ +# TODO: Make sense of this test +def test_init_transaction(b, user_vk): + from bigchaindb.transaction import ( + Fulfillment, + Condition, + Transaction, + TransactionType, + ) + from bigchaindb.util import validate_fulfillments + + ffill = Fulfillment([user_vk]) + cond = Condition([user_vk]) + tx = Transaction([ffill], [cond], TransactionType.CREATE) + tx = tx.to_dict() + + assert tx['transaction']['fulfillments'][0]['owners_before'][0] == b.me + # NOTE: Why are we accessing `['']`? + assert tx['transaction']['conditions'][0][''][0] == user_vk + assert validate_fulfillments(tx) + + +def test_create_tx_with_empty_inputs(): + from bigchaindb.transaction import ( + Fulfillment, + Condition, + Transaction, + TransactionType, + ) + + ffill = Fulfillment([]) + cond = Condition([]) + tx = Transaction([ffill], [cond], TransactionType.CREATE).to_dict() + assert 'id' in tx + assert 'transaction' in tx + assert 'version' in tx + assert 'fulfillments' in tx['transaction'] + assert 'conditions' in tx['transaction'] + assert 'operation' in tx['transaction'] + assert 'timestamp' in tx['transaction'] + assert 'data' in tx['transaction'] + assert len(tx['transaction']['fulfillments']) == 1 + assert tx['transaction']['fulfillments'][0] == { + 'owners_before': [], 'input': None, 'fulfillment': None, 'fid': 0} From 7d2707b97342475da1c32a4941a2ccd3a04eee94 Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 28 Jul 2016 16:32:27 +0200 Subject: [PATCH 51/98] WIP: Implement sign tx --- test_transaction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index f13eba17..8ca134d0 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -27,8 +27,8 @@ def test_create_tx_with_empty_inputs(): TransactionType, ) - ffill = Fulfillment([]) - cond = Condition([]) + ffill = Fulfillment(None) + cond = Condition(None) tx = Transaction([ffill], [cond], TransactionType.CREATE).to_dict() assert 'id' in tx assert 'transaction' in tx From 50562fed439421492c1a586d89224384cd53b912 Mon Sep 17 00:00:00 2001 From: tim Date: Mon, 1 Aug 2016 13:59:35 +0200 Subject: [PATCH 52/98] Add tests for: - Conditions; and - Fulfillments Mostly on the (de)serialization part. --- test_transaction.py | 112 +++++++++++++++++++++++++++++++------------- 1 file changed, 79 insertions(+), 33 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 8ca134d0..fcd497f5 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -1,43 +1,89 @@ -# TODO: Make sense of this test -def test_init_transaction(b, user_vk): +from pytest import raises + + +def test_fulfillment_serialization(ffill_uri, user_vk): from bigchaindb.transaction import ( Fulfillment, - Condition, - Transaction, - TransactionType, ) - from bigchaindb.util import validate_fulfillments - ffill = Fulfillment([user_vk]) - cond = Condition([user_vk]) - tx = Transaction([ffill], [cond], TransactionType.CREATE) - tx = tx.to_dict() - - assert tx['transaction']['fulfillments'][0]['owners_before'][0] == b.me - # NOTE: Why are we accessing `['']`? - assert tx['transaction']['conditions'][0][''][0] == user_vk - assert validate_fulfillments(tx) + ffill = Fulfillment(ffill_uri, [user_vk]) + ffill_dict = ffill.to_dict() + assert ffill_dict['owners_before'] == [user_vk] + assert ffill_dict['input'] == None + assert ffill_dict['fulfillment'] == ffill_uri + assert ffill_dict['fid'] == 0 -def test_create_tx_with_empty_inputs(): +def test_fulfillment_deserialization(ffill_uri, user_vk): from bigchaindb.transaction import ( Fulfillment, - Condition, - Transaction, - TransactionType, ) - ffill = Fulfillment(None) - cond = Condition(None) - tx = Transaction([ffill], [cond], TransactionType.CREATE).to_dict() - assert 'id' in tx - assert 'transaction' in tx - assert 'version' in tx - assert 'fulfillments' in tx['transaction'] - assert 'conditions' in tx['transaction'] - assert 'operation' in tx['transaction'] - assert 'timestamp' in tx['transaction'] - assert 'data' in tx['transaction'] - assert len(tx['transaction']['fulfillments']) == 1 - assert tx['transaction']['fulfillments'][0] == { - 'owners_before': [], 'input': None, 'fulfillment': None, 'fid': 0} + ffill_dict = { + 'owners_before': [user_vk], + 'fulfillment': ffill_uri, + 'fid': 0, + 'input': None, + } + + assert Fulfillment.from_dict(ffill_dict).to_dict() == ffill_dict + + +def test_invalid_fulfillment_initialization(ffill_uri, user_vk): + from bigchaindb.transaction import ( + Fulfillment, + ) + with raises(TypeError): + Fulfillment(ffill_uri, user_vk) + + +def test_condition_serialization(cond_uri, user_vk): + from bigchaindb.transaction import ( + Condition, + ) + + cond = Condition(cond_uri, [user_vk]) + cond_dict = cond.to_dict() + assert cond_dict['owners_after'] == [user_vk] + assert cond_dict['condition']['uri'] == cond_uri + assert cond_dict['cid'] == 0 + + +def test_condition_deserialization(cond_uri, user_vk): + from bigchaindb.transaction import ( + Condition, + ) + from cryptoconditions import ( + Condition as CCCondition, + ) + + cond_dict = { + 'condition': { + 'uri': cond_uri, + 'details': CCCondition.from_uri(cond_uri).to_dict() + }, + 'owners_after': [user_vk], + 'cid': 0, + } + + assert Condition.from_dict(cond_dict).to_dict() == cond_dict + + +def test_invalid_condition_initialization(cond_uri, user_vk): + from bigchaindb.transaction import ( + Condition, + ) + with raises(TypeError): + Condition(cond_uri, user_vk) + + +def test_gen_default_condition(user_vk): + from bigchaindb.transaction import ( + Condition, + ) + from cryptoconditions import Ed25519Fulfillment + cond = Condition.gen_default_condition(user_vk) + assert cond.owners_after == [user_vk] + assert cond.cid == 0 + # TODO: Would be nice if Cryptoconditions would implement a `__eq__` method + assert cond.condition.to_dict() == Ed25519Fulfillment(public_key=user_vk).condition.to_dict() From ee0de84a3067e8231946a1ab235d0577371ee150 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 3 Aug 2016 17:22:57 +0200 Subject: [PATCH 53/98] Finalize serialization logic for tx class --- test_transaction.py | 65 ++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index fcd497f5..db34895e 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -1,16 +1,16 @@ from pytest import raises -def test_fulfillment_serialization(ffill_uri, user_vk): +def test_fulfillment_serialization(ffill, user_vk): from bigchaindb.transaction import ( Fulfillment, ) - ffill = Fulfillment(ffill_uri, [user_vk]) + ffill = Fulfillment(ffill, [user_vk]) ffill_dict = ffill.to_dict() assert ffill_dict['owners_before'] == [user_vk] assert ffill_dict['input'] == None - assert ffill_dict['fulfillment'] == ffill_uri + assert ffill_dict['fulfillment'] == ffill.fulfillment.serialize_uri() assert ffill_dict['fid'] == 0 @@ -29,38 +29,36 @@ def test_fulfillment_deserialization(ffill_uri, user_vk): assert Fulfillment.from_dict(ffill_dict).to_dict() == ffill_dict -def test_invalid_fulfillment_initialization(ffill_uri, user_vk): +def test_invalid_fulfillment_initialization(ffill, user_vk): from bigchaindb.transaction import ( Fulfillment, ) with raises(TypeError): - Fulfillment(ffill_uri, user_vk) + Fulfillment(ffill, user_vk) -def test_condition_serialization(cond_uri, user_vk): +def test_condition_serialization(user_vk): from bigchaindb.transaction import ( Condition, ) - cond = Condition(cond_uri, [user_vk]) + cond = Condition.gen_default(user_vk) cond_dict = cond.to_dict() assert cond_dict['owners_after'] == [user_vk] - assert cond_dict['condition']['uri'] == cond_uri + assert cond_dict['condition']['uri'] == cond.condition.condition_uri assert cond_dict['cid'] == 0 -def test_condition_deserialization(cond_uri, user_vk): +def test_condition_deserialization(user_vk): from bigchaindb.transaction import ( Condition, ) - from cryptoconditions import ( - Condition as CCCondition, - ) + cond = Condition.gen_default(user_vk) cond_dict = { 'condition': { - 'uri': cond_uri, - 'details': CCCondition.from_uri(cond_uri).to_dict() + 'uri': cond.condition.condition_uri, + 'details': cond.condition.to_dict() }, 'owners_after': [user_vk], 'cid': 0, @@ -69,12 +67,12 @@ def test_condition_deserialization(cond_uri, user_vk): assert Condition.from_dict(cond_dict).to_dict() == cond_dict -def test_invalid_condition_initialization(cond_uri, user_vk): +def test_invalid_condition_initialization(ffill, user_vk): from bigchaindb.transaction import ( Condition, ) with raises(TypeError): - Condition(cond_uri, user_vk) + Condition(ffill.condition, user_vk) def test_gen_default_condition(user_vk): @@ -82,8 +80,39 @@ def test_gen_default_condition(user_vk): Condition, ) from cryptoconditions import Ed25519Fulfillment - cond = Condition.gen_default_condition(user_vk) + cond = Condition.gen_default(user_vk) assert cond.owners_after == [user_vk] assert cond.cid == 0 # TODO: Would be nice if Cryptoconditions would implement a `__eq__` method - assert cond.condition.to_dict() == Ed25519Fulfillment(public_key=user_vk).condition.to_dict() + # NOTE: This doesn't make sense yet... + assert cond.condition.to_dict() == Ed25519Fulfillment(public_key=user_vk).to_dict() + + +def test_create_default_transaction(user_vk): + from bigchaindb.transaction import ( + Condition, + Transaction, + ) + cond = Condition.gen_default(user_vk) + tx = Transaction([cond]) + assert tx.conditions == [cond] + assert tx.fulfillments == [] + assert tx.inputs is None + assert tx.payload is None + + +def test_sign_default_transaction(user_vk, user_sk): + from bigchaindb.transaction import ( + Fulfillment, + Transaction, + ) + + ffill = Fulfillment.gen_default(user_vk) + ffill.to_dict() + cond = ffill.gen_condition() + tx = Transaction(Transaction.CREATE, [ffill], [cond]) + tx.sign([user_sk]) + tx_dict = tx.to_dict() + # TODO: We need to make sure to serialize the transaction correctly!!! + assert len(tx.fulfillments) > 0 + assert tx_dict['transaction']['conditions'][0] == cond.to_dict() From f459d535e3f277a03780c8487f45537c45c36467 Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 5 Aug 2016 10:13:34 +0200 Subject: [PATCH 54/98] Add Tests for tx serialization logic --- test_transaction.py | 247 +++++++++++++++++++++++++++++--------------- 1 file changed, 162 insertions(+), 85 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index db34895e..624ec17b 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -1,118 +1,195 @@ from pytest import raises -def test_fulfillment_serialization(ffill, user_vk): - from bigchaindb.transaction import ( - Fulfillment, - ) +def test_fulfillment_serialization(ffill_uri, user_vk): + from bigchaindb.transaction import Fulfillment + from cryptoconditions import Fulfillment as CCFulfillment - ffill = Fulfillment(ffill, [user_vk]) - ffill_dict = ffill.to_dict() - assert ffill_dict['owners_before'] == [user_vk] - assert ffill_dict['input'] == None - assert ffill_dict['fulfillment'] == ffill.fulfillment.serialize_uri() - assert ffill_dict['fid'] == 0 - - -def test_fulfillment_deserialization(ffill_uri, user_vk): - from bigchaindb.transaction import ( - Fulfillment, - ) - - ffill_dict = { + expected = { 'owners_before': [user_vk], 'fulfillment': ffill_uri, + 'details': CCFulfillment.from_uri(ffill_uri).to_dict(), 'fid': 0, 'input': None, } + ffill = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_vk]) - assert Fulfillment.from_dict(ffill_dict).to_dict() == ffill_dict + assert ffill.to_dict() == expected -def test_invalid_fulfillment_initialization(ffill, user_vk): - from bigchaindb.transaction import ( - Fulfillment, - ) +def test_fulfillment_deserialization_with_uri(ffill_uri, user_vk): + from bigchaindb.transaction import Fulfillment + from cryptoconditions import Fulfillment as CCFulfillment + + expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_vk]) + ffill = { + 'owners_before': [user_vk], + 'fulfillment': ffill_uri, + 'details': CCFulfillment.from_uri(ffill_uri).to_dict(), + 'fid': 0, + 'input': None, + } + ffill = Fulfillment.from_dict(ffill) + + assert ffill.to_dict() == expected.to_dict() + + +def test_fulfillment_deserialization_with_dict(cc_ffill, user_vk): + from bigchaindb.transaction import Fulfillment + + expected = Fulfillment(cc_ffill, [user_vk]) + + ffill = { + 'owners_before': [user_vk], + 'fulfillment': None, + 'details': cc_ffill.to_dict(), + 'fid': 0, + 'input': None, + } + ffill = Fulfillment.from_dict(ffill) + + assert ffill.to_dict() == expected.to_dict() + + +def test_invalid_fulfillment_initialization(cc_ffill, user_vk): + from bigchaindb.transaction import Fulfillment + with raises(TypeError): - Fulfillment(ffill, user_vk) + Fulfillment(cc_ffill, user_vk) -def test_condition_serialization(user_vk): - from bigchaindb.transaction import ( - Condition, - ) +def test_gen_default_fulfillment(user_vk): + from bigchaindb.transaction import Fulfillment + from cryptoconditions import Ed25519Fulfillment - cond = Condition.gen_default(user_vk) - cond_dict = cond.to_dict() - assert cond_dict['owners_after'] == [user_vk] - assert cond_dict['condition']['uri'] == cond.condition.condition_uri - assert cond_dict['cid'] == 0 + ffill = Fulfillment.gen_default(user_vk) + assert ffill.owners_before == [user_vk] + assert ffill.fid == 0 + assert ffill.fulfillment.to_dict() == Ed25519Fulfillment(public_key=user_vk).to_dict() + assert ffill.tx_input is None -def test_condition_deserialization(user_vk): - from bigchaindb.transaction import ( - Condition, - ) +def test_condition_serialization(cond_uri, user_vk): + from bigchaindb.transaction import Condition - cond = Condition.gen_default(user_vk) - cond_dict = { - 'condition': { - 'uri': cond.condition.condition_uri, - 'details': cond.condition.to_dict() - }, + expected = { + 'condition': cond_uri, 'owners_after': [user_vk], 'cid': 0, } - assert Condition.from_dict(cond_dict).to_dict() == cond_dict + cond = Condition(cond_uri, [user_vk]) + + assert cond.to_dict() == expected -def test_invalid_condition_initialization(ffill, user_vk): - from bigchaindb.transaction import ( - Condition, - ) +def test_condition_deserialization(cond_uri, user_vk): + from bigchaindb.transaction import Condition + + expected = Condition(cond_uri, [user_vk]) + cond = { + 'condition': cond_uri, + 'owners_after': [user_vk], + 'cid': 0, + } + cond = Condition.from_dict(cond) + + assert cond.to_dict() == expected.to_dict() + + +def test_invalid_condition_initialization(cond_uri, user_vk): + from bigchaindb.transaction import Condition + with raises(TypeError): - Condition(ffill.condition, user_vk) + Condition(cond_uri, user_vk) -def test_gen_default_condition(user_vk): - from bigchaindb.transaction import ( - Condition, - ) - from cryptoconditions import Ed25519Fulfillment - cond = Condition.gen_default(user_vk) - assert cond.owners_after == [user_vk] - assert cond.cid == 0 - # TODO: Would be nice if Cryptoconditions would implement a `__eq__` method - # NOTE: This doesn't make sense yet... - assert cond.condition.to_dict() == Ed25519Fulfillment(public_key=user_vk).to_dict() +def test_invalid_data_initialization(): + from bigchaindb.transaction import Data + + with raises(TypeError): + Data([]) -def test_create_default_transaction(user_vk): - from bigchaindb.transaction import ( - Condition, - Transaction, - ) - cond = Condition.gen_default(user_vk) - tx = Transaction([cond]) - assert tx.conditions == [cond] - assert tx.fulfillments == [] - assert tx.inputs is None - assert tx.payload is None +def test_data_serialization(payload, payload_id): + from bigchaindb.transaction import Data + + expected = { + 'payload': payload, + 'hash': payload_id + } + data = Data(payload, payload_id) + + assert data.to_dict() == expected -def test_sign_default_transaction(user_vk, user_sk): - from bigchaindb.transaction import ( - Fulfillment, - Transaction, - ) +def test_data_deserialization(payload, payload_id): + from bigchaindb.transaction import Data - ffill = Fulfillment.gen_default(user_vk) - ffill.to_dict() - cond = ffill.gen_condition() - tx = Transaction(Transaction.CREATE, [ffill], [cond]) - tx.sign([user_sk]) - tx_dict = tx.to_dict() - # TODO: We need to make sure to serialize the transaction correctly!!! - assert len(tx.fulfillments) > 0 - assert tx_dict['transaction']['conditions'][0] == cond.to_dict() + expected = Data(payload, payload_id) + data = Data.from_dict({'payload': payload, 'hash': payload_id}) + + assert data.to_dict() == expected.to_dict() + + +def test_transaction_serialization(default_ffill, default_cond): + from bigchaindb.transaction import Transaction + + tx_id = 'l0l' + timestamp = '66666666666' + + expected = { + 'id': tx_id, + 'version': Transaction.VERSION, + 'transaction': { + # NOTE: This test assumes that Fulfillments and Conditions can successfully be serialized + 'fulfillments': [default_ffill.to_dict()], + 'conditions': [default_cond.to_dict()], + 'operation': Transaction.CREATE, + 'timestamp': timestamp, + 'data': None, + } + } + + tx_dict = Transaction(Transaction.CREATE, [default_ffill], [default_cond]).to_dict() + tx_dict['id'] = tx_id + tx_dict['transaction']['timestamp'] = timestamp + + assert tx_dict == expected + + +def test_transaction_deserialization(default_ffill, default_cond): + from bigchaindb.transaction import Transaction + + tx_id = 'l0l' + timestamp = '66666666666' + + expected = Transaction(Transaction.CREATE, [default_ffill], [default_cond], None, timestamp, Transaction.VERSION) + + tx = { + 'id': tx_id, + 'version': Transaction.VERSION, + 'transaction': { + # NOTE: This test assumes that Fulfillments and Conditions can successfully be serialized + 'fulfillments': [default_ffill.to_dict()], + 'conditions': [default_cond.to_dict()], + 'operation': Transaction.CREATE, + 'timestamp': timestamp, + 'data': None, + } + } + tx = Transaction.from_dict(tx) + + assert tx.to_dict() == expected.to_dict() + + +def test_invalid_tx_initialization(): + from bigchaindb.transaction import Transaction + + wrong_data_type = {'payload': 'a totally wrong datatype'} + with raises(TypeError): + Transaction(Transaction.CREATE, wrong_data_type) + with raises(TypeError): + Transaction(Transaction.CREATE, [], wrong_data_type) + with raises(TypeError): + Transaction(Transaction.CREATE, [], [], wrong_data_type) From 9a2b6a05502ab56bba556aa04cac1ac41bcde74d Mon Sep 17 00:00:00 2001 From: tim Date: Mon, 8 Aug 2016 15:06:00 +0200 Subject: [PATCH 55/98] Add fulfillment validation --- test_transaction.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test_transaction.py b/test_transaction.py index 624ec17b..c6d7de96 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -193,3 +193,21 @@ def test_invalid_tx_initialization(): Transaction(Transaction.CREATE, [], wrong_data_type) with raises(TypeError): Transaction(Transaction.CREATE, [], [], wrong_data_type) + with raises(TypeError): + Transaction('RANSFER', [], []) + with raises(TypeError): + Transaction('TRANSFER', [], []) + + +def test_validate_tx_signature(default_ffill, default_cond, user_vk, user_sk): + from copy import deepcopy + from bigchaindb.transaction import Transaction + from bigchaindb.crypto import SigningKey + + tx = Transaction(Transaction.CREATE, [default_ffill], [default_cond]) + expected = deepcopy(default_ffill) + expected.fulfillment.sign(str(tx), SigningKey(user_sk)) + + tx.sign([user_sk]) + assert tx.fulfillments[0].fulfillment.to_dict()['signature'] == expected.fulfillment.to_dict()['signature'] + assert tx.fulfillments_valid() is True From d51edeece4ade469b1292242491058ac52702066 Mon Sep 17 00:00:00 2001 From: tim Date: Mon, 15 Aug 2016 15:29:20 +0200 Subject: [PATCH 56/98] Add ThresholdCondition support --- test_transaction.py | 76 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index c6d7de96..ec863577 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -58,15 +58,40 @@ def test_invalid_fulfillment_initialization(cc_ffill, user_vk): Fulfillment(cc_ffill, user_vk) -def test_gen_default_fulfillment(user_vk): +def test_gen_default_fulfillment_with_single_owner_after(user_vk): from bigchaindb.transaction import Fulfillment from cryptoconditions import Ed25519Fulfillment - ffill = Fulfillment.gen_default(user_vk) + ffill = Fulfillment.gen_default([user_vk]) assert ffill.owners_before == [user_vk] assert ffill.fid == 0 - assert ffill.fulfillment.to_dict() == Ed25519Fulfillment(public_key=user_vk).to_dict() assert ffill.tx_input is None + assert ffill.fulfillment.to_dict() == Ed25519Fulfillment(public_key=user_vk).to_dict() + + +def test_gen_default_fulfillment_with_multiple_owners_after(user_vks): + from bigchaindb.transaction import Fulfillment + from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment + + ffill = Fulfillment.gen_default(user_vks) + expected_ffill = ThresholdSha256Fulfillment(threshold=len(user_vks)) + # NOTE: Does it make sense to have the exact same logic as in the tested method here? + for user_vk in user_vks: + expected_ffill.add_subfulfillment(Ed25519Fulfillment(public_key=user_vk)) + + assert ffill.owners_before == user_vks + assert ffill.fid == 0 + assert ffill.tx_input is None + assert ffill.fulfillment.to_dict() == expected_ffill.to_dict() + + +def test_invalid_gen_default_arguments(): + from bigchaindb.transaction import Fulfillment + + with raises(TypeError): + Fulfillment.gen_default({}) + with raises(NotImplementedError): + Fulfillment.gen_default([]) def test_condition_serialization(cond_uri, user_vk): @@ -132,7 +157,7 @@ def test_data_deserialization(payload, payload_id): assert data.to_dict() == expected.to_dict() -def test_transaction_serialization(default_ffill, default_cond): +def test_transaction_serialization(default_single_ffill, default_single_cond): from bigchaindb.transaction import Transaction tx_id = 'l0l' @@ -143,36 +168,36 @@ def test_transaction_serialization(default_ffill, default_cond): 'version': Transaction.VERSION, 'transaction': { # NOTE: This test assumes that Fulfillments and Conditions can successfully be serialized - 'fulfillments': [default_ffill.to_dict()], - 'conditions': [default_cond.to_dict()], + 'fulfillments': [default_single_ffill.to_dict()], + 'conditions': [default_single_cond.to_dict()], 'operation': Transaction.CREATE, 'timestamp': timestamp, 'data': None, } } - tx_dict = Transaction(Transaction.CREATE, [default_ffill], [default_cond]).to_dict() + tx_dict = Transaction(Transaction.CREATE, [default_single_ffill], [default_single_cond]).to_dict() tx_dict['id'] = tx_id tx_dict['transaction']['timestamp'] = timestamp assert tx_dict == expected -def test_transaction_deserialization(default_ffill, default_cond): +def test_transaction_deserialization(default_single_ffill, default_single_cond): from bigchaindb.transaction import Transaction tx_id = 'l0l' timestamp = '66666666666' - expected = Transaction(Transaction.CREATE, [default_ffill], [default_cond], None, timestamp, Transaction.VERSION) + expected = Transaction(Transaction.CREATE, [default_single_ffill], [default_single_cond], None, timestamp, Transaction.VERSION) tx = { 'id': tx_id, 'version': Transaction.VERSION, 'transaction': { # NOTE: This test assumes that Fulfillments and Conditions can successfully be serialized - 'fulfillments': [default_ffill.to_dict()], - 'conditions': [default_cond.to_dict()], + 'fulfillments': [default_single_ffill.to_dict()], + 'conditions': [default_single_cond.to_dict()], 'operation': Transaction.CREATE, 'timestamp': timestamp, 'data': None, @@ -195,19 +220,34 @@ def test_invalid_tx_initialization(): Transaction(Transaction.CREATE, [], [], wrong_data_type) with raises(TypeError): Transaction('RANSFER', [], []) - with raises(TypeError): - Transaction('TRANSFER', [], []) -def test_validate_tx_signature(default_ffill, default_cond, user_vk, user_sk): +def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, user_vk, user_sk): from copy import deepcopy - from bigchaindb.transaction import Transaction + from bigchaindb.crypto import SigningKey + from bigchaindb.transaction import Transaction - tx = Transaction(Transaction.CREATE, [default_ffill], [default_cond]) - expected = deepcopy(default_ffill) + tx = Transaction(Transaction.CREATE, [default_single_ffill], [default_single_cond]) + expected = deepcopy(default_single_ffill) expected.fulfillment.sign(str(tx), SigningKey(user_sk)) - tx.sign([user_sk]) + assert tx.fulfillments[0].fulfillment.to_dict()['signature'] == expected.fulfillment.to_dict()['signature'] assert tx.fulfillments_valid() is True + + +def test_validate_tx_threshold_signature(default_threshold_ffill, default_threshold_cond, user_vks, user_sks): + from copy import deepcopy + + from bigchaindb.crypto import SigningKey + from bigchaindb.transaction import Transaction + + tx = Transaction(Transaction.CREATE, [default_threshold_ffill], [default_threshold_cond]) + expected = deepcopy(default_threshold_ffill) + expected.fulfillment.subconditions[0]['body'].sign(str(tx), SigningKey(user_sks[0])) + expected.fulfillment.subconditions[1]['body'].sign(str(tx), SigningKey(user_sks[1])) + tx.sign(user_sks) + + assert tx.fulfillments[0].to_dict()['fulfillment'] == expected.to_dict()['fulfillment'] + assert tx.fulfillments_valid() is True From ef61c2d76a7d87de716da8d2f645869d351ba9e8 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 17 Aug 2016 09:58:20 +0200 Subject: [PATCH 57/98] WIP transfer --- test_transaction.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test_transaction.py b/test_transaction.py index ec863577..9347f3cd 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -222,6 +222,14 @@ def test_invalid_tx_initialization(): Transaction('RANSFER', [], []) +def test_transaction_link_serialization(): + pass + + +def test_transaction_link_deserialization(): + pass + + def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, user_vk, user_sk): from copy import deepcopy @@ -251,3 +259,23 @@ def test_validate_tx_threshold_signature(default_threshold_ffill, default_thresh assert tx.fulfillments[0].to_dict()['fulfillment'] == expected.to_dict()['fulfillment'] assert tx.fulfillments_valid() is True + + +def test_transfer(tx, user_vk, user_sk, user2_vk): + from copy import deepcopy + + from bigchaindb.crypto import SigningKey + from bigchaindb.transaction import Condition + from cryptoconditions import Ed25519Fulfillment + + import ipdb; ipdb.set_trace() + cond = Condition(Ed25519Fulfillment(public_key=user2_vk).condition_uri, [user2_vk]) + transfer_tx = tx.transfer([cond]) + + expected = deepcopy(transfer_tx.fulfillments[0]) + expected.fulfillment.sign(str(transfer_tx), SigningKey(user_sk)) + + transfer_tx.sign([user_sk]) + + assert tx.fulfillments[0].fulfillment.to_dict()['signature'] == expected.fulfillment.to_dict()['signature'] + assert transfer_tx.fulfillments_valid() is True From 66341963ff1b5177eed0aad96aed0ede05e31f4d Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 17 Aug 2016 14:40:10 +0200 Subject: [PATCH 58/98] Clean up after move --- test_bigchaindb_common.py | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 test_bigchaindb_common.py diff --git a/test_bigchaindb_common.py b/test_bigchaindb_common.py deleted file mode 100644 index ed70f9ea..00000000 --- a/test_bigchaindb_common.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -test_bigchaindb_common ----------------------------------- - -Tests for `bigchaindb_common` module. -""" - -import pytest - - -from bigchaindb_common import bigchaindb_common - - -class TestBigchaindb_common(object): - - @classmethod - def setup_class(cls): - pass - - def test_something(self): - pass - - @classmethod - def teardown_class(cls): - pass - From 30486cc5e00987999d076604a24f8afa4b50b865 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 17 Aug 2016 15:05:16 +0200 Subject: [PATCH 59/98] Adjust setup to package structure --- test_transaction.py | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 9347f3cd..4d2569ec 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -2,7 +2,7 @@ from pytest import raises def test_fulfillment_serialization(ffill_uri, user_vk): - from bigchaindb.transaction import Fulfillment + from bigchaindb_common_common.transaction import Fulfillment from cryptoconditions import Fulfillment as CCFulfillment expected = { @@ -18,7 +18,7 @@ def test_fulfillment_serialization(ffill_uri, user_vk): def test_fulfillment_deserialization_with_uri(ffill_uri, user_vk): - from bigchaindb.transaction import Fulfillment + from bigchaindb_common.transaction import Fulfillment from cryptoconditions import Fulfillment as CCFulfillment expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_vk]) @@ -35,7 +35,7 @@ def test_fulfillment_deserialization_with_uri(ffill_uri, user_vk): def test_fulfillment_deserialization_with_dict(cc_ffill, user_vk): - from bigchaindb.transaction import Fulfillment + from bigchaindb_common.transaction import Fulfillment expected = Fulfillment(cc_ffill, [user_vk]) @@ -52,14 +52,14 @@ def test_fulfillment_deserialization_with_dict(cc_ffill, user_vk): def test_invalid_fulfillment_initialization(cc_ffill, user_vk): - from bigchaindb.transaction import Fulfillment + from bigchaindb_common.transaction import Fulfillment with raises(TypeError): Fulfillment(cc_ffill, user_vk) def test_gen_default_fulfillment_with_single_owner_after(user_vk): - from bigchaindb.transaction import Fulfillment + from bigchaindb_common.transaction import Fulfillment from cryptoconditions import Ed25519Fulfillment ffill = Fulfillment.gen_default([user_vk]) @@ -70,7 +70,7 @@ def test_gen_default_fulfillment_with_single_owner_after(user_vk): def test_gen_default_fulfillment_with_multiple_owners_after(user_vks): - from bigchaindb.transaction import Fulfillment + from bigchaindb_common.transaction import Fulfillment from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment ffill = Fulfillment.gen_default(user_vks) @@ -86,7 +86,7 @@ def test_gen_default_fulfillment_with_multiple_owners_after(user_vks): def test_invalid_gen_default_arguments(): - from bigchaindb.transaction import Fulfillment + from bigchaindb_common.transaction import Fulfillment with raises(TypeError): Fulfillment.gen_default({}) @@ -95,7 +95,7 @@ def test_invalid_gen_default_arguments(): def test_condition_serialization(cond_uri, user_vk): - from bigchaindb.transaction import Condition + from bigchaindb_common.transaction import Condition expected = { 'condition': cond_uri, @@ -109,7 +109,7 @@ def test_condition_serialization(cond_uri, user_vk): def test_condition_deserialization(cond_uri, user_vk): - from bigchaindb.transaction import Condition + from bigchaindb_common.transaction import Condition expected = Condition(cond_uri, [user_vk]) cond = { @@ -123,21 +123,21 @@ def test_condition_deserialization(cond_uri, user_vk): def test_invalid_condition_initialization(cond_uri, user_vk): - from bigchaindb.transaction import Condition + from bigchaindb_common.transaction import Condition with raises(TypeError): Condition(cond_uri, user_vk) def test_invalid_data_initialization(): - from bigchaindb.transaction import Data + from bigchaindb_common.transaction import Data with raises(TypeError): Data([]) def test_data_serialization(payload, payload_id): - from bigchaindb.transaction import Data + from bigchaindb_common.transaction import Data expected = { 'payload': payload, @@ -149,7 +149,7 @@ def test_data_serialization(payload, payload_id): def test_data_deserialization(payload, payload_id): - from bigchaindb.transaction import Data + from bigchaindb_common.transaction import Data expected = Data(payload, payload_id) data = Data.from_dict({'payload': payload, 'hash': payload_id}) @@ -158,7 +158,7 @@ def test_data_deserialization(payload, payload_id): def test_transaction_serialization(default_single_ffill, default_single_cond): - from bigchaindb.transaction import Transaction + from bigchaindb_common.transaction import Transaction tx_id = 'l0l' timestamp = '66666666666' @@ -184,7 +184,7 @@ def test_transaction_serialization(default_single_ffill, default_single_cond): def test_transaction_deserialization(default_single_ffill, default_single_cond): - from bigchaindb.transaction import Transaction + from bigchaindb_common.transaction import Transaction tx_id = 'l0l' timestamp = '66666666666' @@ -209,7 +209,7 @@ def test_transaction_deserialization(default_single_ffill, default_single_cond): def test_invalid_tx_initialization(): - from bigchaindb.transaction import Transaction + from bigchaindb_common.transaction import Transaction wrong_data_type = {'payload': 'a totally wrong datatype'} with raises(TypeError): @@ -233,8 +233,8 @@ def test_transaction_link_deserialization(): def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, user_vk, user_sk): from copy import deepcopy - from bigchaindb.crypto import SigningKey - from bigchaindb.transaction import Transaction + from bigchaindb_common.crypto import SigningKey + from bigchaindb_common.transaction import Transaction tx = Transaction(Transaction.CREATE, [default_single_ffill], [default_single_cond]) expected = deepcopy(default_single_ffill) @@ -248,8 +248,8 @@ def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, def test_validate_tx_threshold_signature(default_threshold_ffill, default_threshold_cond, user_vks, user_sks): from copy import deepcopy - from bigchaindb.crypto import SigningKey - from bigchaindb.transaction import Transaction + from bigchaindb_common.crypto import SigningKey + from bigchaindb_common.transaction import Transaction tx = Transaction(Transaction.CREATE, [default_threshold_ffill], [default_threshold_cond]) expected = deepcopy(default_threshold_ffill) @@ -264,11 +264,10 @@ def test_validate_tx_threshold_signature(default_threshold_ffill, default_thresh def test_transfer(tx, user_vk, user_sk, user2_vk): from copy import deepcopy - from bigchaindb.crypto import SigningKey - from bigchaindb.transaction import Condition + from bigchaindb_common.crypto import SigningKey + from bigchaindb_common.transaction import Condition from cryptoconditions import Ed25519Fulfillment - import ipdb; ipdb.set_trace() cond = Condition(Ed25519Fulfillment(public_key=user2_vk).condition_uri, [user2_vk]) transfer_tx = tx.transfer([cond]) From 01f1be4faa6c0878fede76f8eabc88a587c8ee2d Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 17 Aug 2016 15:29:13 +0200 Subject: [PATCH 60/98] Fix tests --- test_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_transaction.py b/test_transaction.py index 4d2569ec..0abf33a6 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -2,7 +2,7 @@ from pytest import raises def test_fulfillment_serialization(ffill_uri, user_vk): - from bigchaindb_common_common.transaction import Fulfillment + from bigchaindb_common.transaction import Fulfillment from cryptoconditions import Fulfillment as CCFulfillment expected = { From 4d1efd60c5fac2f69e90813bf4cd1bc7ca7d88ca Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 18 Aug 2016 10:44:05 +0200 Subject: [PATCH 61/98] Add test coverage --- test_transaction.py | 78 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 0abf33a6..3d7e9712 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -56,6 +56,8 @@ def test_invalid_fulfillment_initialization(cc_ffill, user_vk): with raises(TypeError): Fulfillment(cc_ffill, user_vk) + with raises(TypeError): + Fulfillment(cc_ffill, [], tx_input='somethingthatiswrong') def test_gen_default_fulfillment_with_single_owner_after(user_vk): @@ -223,14 +225,58 @@ def test_invalid_tx_initialization(): def test_transaction_link_serialization(): - pass + from bigchaindb_common.transaction import TransactionLink + + tx_id = 'a transaction id' + expected = { + 'tx_id': tx_id, + 'cid': 0, + } + tx_link = TransactionLink(tx_id, 0) + + assert tx_link.to_dict() == expected + + +def test_transaction_link_serialization_with_empty_payload(): + from bigchaindb_common.transaction import TransactionLink + + expected = None + tx_link = TransactionLink() + + assert tx_link.to_dict() == expected def test_transaction_link_deserialization(): - pass + from bigchaindb_common.transaction import TransactionLink + + tx_id = 'a transaction id' + expected = TransactionLink(tx_id, 0) + tx_link = { + 'tx_id': tx_id, + 'cid': 0, + } + tx_link = TransactionLink.from_dict(tx_link) + + assert tx_link.to_dict() == expected.to_dict() -def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, user_vk, user_sk): +def test_transaction_link_deserialization_with_empty_payload(): + from bigchaindb_common.transaction import TransactionLink + + expected = TransactionLink() + tx_link = TransactionLink.from_dict(None) + + assert tx_link.to_dict() == expected.to_dict() + + +def test_sign_with_invalid_parameters(tx, user_sk): + with raises(TypeError): + tx.sign(None) + with raises(TypeError): + tx.sign(user_sk) + + +def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, user_sk): from copy import deepcopy from bigchaindb_common.crypto import SigningKey @@ -245,6 +291,32 @@ def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, assert tx.fulfillments_valid() is True +def test_validating_multiple_fulfillments(default_single_ffill, default_single_cond, user_sk): + from copy import deepcopy + + from bigchaindb_common.crypto import SigningKey + from bigchaindb_common.transaction import Transaction + + tx = Transaction(Transaction.CREATE, + [default_single_ffill, deepcopy(default_single_ffill)], + [default_single_cond, deepcopy(default_single_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.fulfillments[0].fulfillment.sign(str(expected_first), SigningKey(user_sk)) + expected_second.fulfillments[0].fulfillment.sign(str(expected_second), SigningKey(user_sk)) + tx.sign([user_sk]) + + assert tx.fulfillments[0].fulfillment.to_dict() == expected_first.fulfillments[0].fulfillment.to_dict() + assert tx.fulfillments[1].fulfillment.to_dict() == expected_second.fulfillments[0].fulfillment.to_dict() + assert tx.fulfillments_valid() is True + + def test_validate_tx_threshold_signature(default_threshold_ffill, default_threshold_cond, user_vks, user_sks): from copy import deepcopy From 60d2ee1cf6ea88eb11bc4d79926144960b4ba7ea Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 18 Aug 2016 14:54:44 +0200 Subject: [PATCH 62/98] Add test coverage --- test_transaction.py | 152 ++++++++++++++++++++++++++++++-------------- 1 file changed, 105 insertions(+), 47 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 3d7e9712..2d64bb5b 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -1,29 +1,29 @@ from pytest import raises -def test_fulfillment_serialization(ffill_uri, user_vk): +def test_fulfillment_serialization(ffill_uri, user_pub): from bigchaindb_common.transaction import Fulfillment from cryptoconditions import Fulfillment as CCFulfillment expected = { - 'owners_before': [user_vk], + 'owners_before': [user_pub], 'fulfillment': ffill_uri, 'details': CCFulfillment.from_uri(ffill_uri).to_dict(), 'fid': 0, 'input': None, } - ffill = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_vk]) + ffill = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) assert ffill.to_dict() == expected -def test_fulfillment_deserialization_with_uri(ffill_uri, user_vk): +def test_fulfillment_deserialization_with_uri(ffill_uri, user_pub): from bigchaindb_common.transaction import Fulfillment from cryptoconditions import Fulfillment as CCFulfillment - expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_vk]) + expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) ffill = { - 'owners_before': [user_vk], + 'owners_before': [user_pub], 'fulfillment': ffill_uri, 'details': CCFulfillment.from_uri(ffill_uri).to_dict(), 'fid': 0, @@ -34,13 +34,13 @@ def test_fulfillment_deserialization_with_uri(ffill_uri, user_vk): assert ffill.to_dict() == expected.to_dict() -def test_fulfillment_deserialization_with_dict(cc_ffill, user_vk): +def test_fulfillment_deserialization_with_dict(cc_ffill, user_pub): from bigchaindb_common.transaction import Fulfillment - expected = Fulfillment(cc_ffill, [user_vk]) + expected = Fulfillment(cc_ffill, [user_pub]) ffill = { - 'owners_before': [user_vk], + 'owners_before': [user_pub], 'fulfillment': None, 'details': cc_ffill.to_dict(), 'fid': 0, @@ -51,37 +51,37 @@ def test_fulfillment_deserialization_with_dict(cc_ffill, user_vk): assert ffill.to_dict() == expected.to_dict() -def test_invalid_fulfillment_initialization(cc_ffill, user_vk): +def test_invalid_fulfillment_initialization(cc_ffill, user_pub): from bigchaindb_common.transaction import Fulfillment with raises(TypeError): - Fulfillment(cc_ffill, user_vk) + Fulfillment(cc_ffill, user_pub) with raises(TypeError): Fulfillment(cc_ffill, [], tx_input='somethingthatiswrong') -def test_gen_default_fulfillment_with_single_owner_after(user_vk): +def test_gen_default_fulfillment_with_single_owner_after(user_pub): from bigchaindb_common.transaction import Fulfillment from cryptoconditions import Ed25519Fulfillment - ffill = Fulfillment.gen_default([user_vk]) - assert ffill.owners_before == [user_vk] + ffill = Fulfillment.gen_default([user_pub]) + assert ffill.owners_before == [user_pub] assert ffill.fid == 0 assert ffill.tx_input is None - assert ffill.fulfillment.to_dict() == Ed25519Fulfillment(public_key=user_vk).to_dict() + assert ffill.fulfillment.to_dict() == Ed25519Fulfillment(public_key=user_pub).to_dict() -def test_gen_default_fulfillment_with_multiple_owners_after(user_vks): +def test_gen_default_fulfillment_with_multiple_owners_after(user_pub_keys): from bigchaindb_common.transaction import Fulfillment from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment - ffill = Fulfillment.gen_default(user_vks) - expected_ffill = ThresholdSha256Fulfillment(threshold=len(user_vks)) + ffill = Fulfillment.gen_default(user_pub_keys) + expected_ffill = ThresholdSha256Fulfillment(threshold=len(user_pub_keys)) # NOTE: Does it make sense to have the exact same logic as in the tested method here? - for user_vk in user_vks: - expected_ffill.add_subfulfillment(Ed25519Fulfillment(public_key=user_vk)) + for user_pub in user_pub_keys: + expected_ffill.add_subfulfillment(Ed25519Fulfillment(public_key=user_pub)) - assert ffill.owners_before == user_vks + assert ffill.owners_before == user_pub_keys assert ffill.fid == 0 assert ffill.tx_input is None assert ffill.fulfillment.to_dict() == expected_ffill.to_dict() @@ -96,27 +96,27 @@ def test_invalid_gen_default_arguments(): Fulfillment.gen_default([]) -def test_condition_serialization(cond_uri, user_vk): +def test_condition_serialization(cond_uri, user_pub): from bigchaindb_common.transaction import Condition expected = { 'condition': cond_uri, - 'owners_after': [user_vk], + 'owners_after': [user_pub], 'cid': 0, } - cond = Condition(cond_uri, [user_vk]) + cond = Condition(cond_uri, [user_pub]) assert cond.to_dict() == expected -def test_condition_deserialization(cond_uri, user_vk): +def test_condition_deserialization(cond_uri, user_pub): from bigchaindb_common.transaction import Condition - expected = Condition(cond_uri, [user_vk]) + expected = Condition(cond_uri, [user_pub]) cond = { 'condition': cond_uri, - 'owners_after': [user_vk], + 'owners_after': [user_pub], 'cid': 0, } cond = Condition.from_dict(cond) @@ -124,11 +124,11 @@ def test_condition_deserialization(cond_uri, user_vk): assert cond.to_dict() == expected.to_dict() -def test_invalid_condition_initialization(cond_uri, user_vk): +def test_invalid_condition_initialization(cond_uri, user_pub): from bigchaindb_common.transaction import Condition with raises(TypeError): - Condition(cond_uri, user_vk) + Condition(cond_uri, user_pub) def test_invalid_data_initialization(): @@ -191,7 +191,8 @@ def test_transaction_deserialization(default_single_ffill, default_single_cond): tx_id = 'l0l' timestamp = '66666666666' - expected = Transaction(Transaction.CREATE, [default_single_ffill], [default_single_cond], None, timestamp, Transaction.VERSION) + expected = Transaction(Transaction.CREATE, [default_single_ffill], [default_single_cond], None, timestamp, + Transaction.VERSION) tx = { 'id': tx_id, @@ -269,14 +270,48 @@ def test_transaction_link_deserialization_with_empty_payload(): assert tx_link.to_dict() == expected.to_dict() -def test_sign_with_invalid_parameters(tx, user_sk): +def test_add_fulfillment_to_tx(default_single_ffill): + from bigchaindb_common.transaction import Transaction + + tx = Transaction(Transaction.CREATE, [], []) + tx.add_fulfillment(default_single_ffill) + + assert len(tx.fulfillments) == 1 + + +def test_add_fulfillment_to_tx_with_invalid_parameters(): + from bigchaindb_common.transaction import Transaction + + tx = Transaction(Transaction.CREATE) + with raises(TypeError): + tx.add_fulfillment('somewronginput') + + +def test_add_condition_to_tx(default_single_cond): + from bigchaindb_common.transaction import Transaction + + tx = Transaction(Transaction.CREATE) + tx.add_condition(default_single_cond) + + assert len(tx.conditions) == 1 + + +def test_add_condition_to_tx_with_invalid_parameters(): + from bigchaindb_common.transaction import Transaction + + tx = Transaction(Transaction.CREATE, [], []) + with raises(TypeError): + tx.add_condition('somewronginput') + + +def test_sign_with_invalid_parameters(tx, user_priv): with raises(TypeError): tx.sign(None) with raises(TypeError): - tx.sign(user_sk) + tx.sign(user_priv) -def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, user_sk): +def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, user_priv): from copy import deepcopy from bigchaindb_common.crypto import SigningKey @@ -284,14 +319,36 @@ def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, tx = Transaction(Transaction.CREATE, [default_single_ffill], [default_single_cond]) expected = deepcopy(default_single_ffill) - expected.fulfillment.sign(str(tx), SigningKey(user_sk)) - tx.sign([user_sk]) + expected.fulfillment.sign(str(tx), SigningKey(user_priv)) + tx.sign([user_priv]) assert tx.fulfillments[0].fulfillment.to_dict()['signature'] == expected.fulfillment.to_dict()['signature'] assert tx.fulfillments_valid() is True -def test_validating_multiple_fulfillments(default_single_ffill, default_single_cond, user_sk): +def test_invoke_simple_signature_fulfillment_with_invalid_parameters(tx, default_single_ffill): + from bigchaindb_common.exceptions import KeypairMismatchException + + with raises(KeypairMismatchException): + tx._sign_simple_signature_fulfillment(default_single_ffill, 'somemessage', {'wrong_pub_key': 'wrong_priv_key'}) + + +def test_invoke_threshold_signature_fulfillment_with_invalid_parameters(tx, default_threshold_ffill, user3_pub, + user3_priv): + from bigchaindb_common.exceptions import KeypairMismatchException + + with raises(KeypairMismatchException): + default_threshold_ffill.owners_before = ['somewrongvalue'] + tx._sign_threshold_signature_fulfillment(default_threshold_ffill, 'somemessage', None) + with raises(KeypairMismatchException): + tx._sign_threshold_signature_fulfillment(default_threshold_ffill, 'somemessage', [{user3_pub: user3_priv}]) + + +def test_validate_fulfillment_with_invalid_parameters(tx): + assert tx._fulfillment_valid() == False + + +def test_validating_multiple_fulfillments(default_single_ffill, default_single_cond, user_priv): from copy import deepcopy from bigchaindb_common.crypto import SigningKey @@ -308,16 +365,17 @@ def test_validating_multiple_fulfillments(default_single_ffill, default_single_c expected_second.fulfillments = [expected_second.fulfillments[1]] expected_second.conditions= [expected_second.conditions[1]] - expected_first.fulfillments[0].fulfillment.sign(str(expected_first), SigningKey(user_sk)) - expected_second.fulfillments[0].fulfillment.sign(str(expected_second), SigningKey(user_sk)) - tx.sign([user_sk]) + expected_first.fulfillments[0].fulfillment.sign(str(expected_first), SigningKey(user_priv)) + expected_second.fulfillments[0].fulfillment.sign(str(expected_second), SigningKey(user_priv)) + tx.sign([user_priv]) assert tx.fulfillments[0].fulfillment.to_dict() == expected_first.fulfillments[0].fulfillment.to_dict() assert tx.fulfillments[1].fulfillment.to_dict() == expected_second.fulfillments[0].fulfillment.to_dict() assert tx.fulfillments_valid() is True -def test_validate_tx_threshold_signature(default_threshold_ffill, default_threshold_cond, user_vks, user_sks): +def test_validate_tx_threshold_signature(default_threshold_ffill, default_threshold_cond, user_pub_keys, + user_priv_keys): from copy import deepcopy from bigchaindb_common.crypto import SigningKey @@ -325,28 +383,28 @@ def test_validate_tx_threshold_signature(default_threshold_ffill, default_thresh tx = Transaction(Transaction.CREATE, [default_threshold_ffill], [default_threshold_cond]) expected = deepcopy(default_threshold_ffill) - expected.fulfillment.subconditions[0]['body'].sign(str(tx), SigningKey(user_sks[0])) - expected.fulfillment.subconditions[1]['body'].sign(str(tx), SigningKey(user_sks[1])) - tx.sign(user_sks) + expected.fulfillment.subconditions[0]['body'].sign(str(tx), SigningKey(user_priv_keys[0])) + expected.fulfillment.subconditions[1]['body'].sign(str(tx), SigningKey(user_priv_keys[1])) + tx.sign(user_priv_keys) assert tx.fulfillments[0].to_dict()['fulfillment'] == expected.to_dict()['fulfillment'] assert tx.fulfillments_valid() is True -def test_transfer(tx, user_vk, user_sk, user2_vk): +def test_transfer(tx, user_pub, user_priv, user2_pub): from copy import deepcopy from bigchaindb_common.crypto import SigningKey from bigchaindb_common.transaction import Condition from cryptoconditions import Ed25519Fulfillment - cond = Condition(Ed25519Fulfillment(public_key=user2_vk).condition_uri, [user2_vk]) + cond = Condition(Ed25519Fulfillment(public_key=user2_pub).condition_uri, [user2_pub]) transfer_tx = tx.transfer([cond]) expected = deepcopy(transfer_tx.fulfillments[0]) - expected.fulfillment.sign(str(transfer_tx), SigningKey(user_sk)) + expected.fulfillment.sign(str(transfer_tx), SigningKey(user_priv)) - transfer_tx.sign([user_sk]) + transfer_tx.sign([user_priv]) assert tx.fulfillments[0].fulfillment.to_dict()['signature'] == expected.fulfillment.to_dict()['signature'] assert transfer_tx.fulfillments_valid() is True From 0b30ca3de5830d6c685df5763a5e00e7638fce70 Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 19 Aug 2016 10:42:20 +0200 Subject: [PATCH 63/98] Transfer-tx fulfillments validation --- test_transaction.py | 48 ++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 2d64bb5b..ca6b9b1c 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -304,11 +304,11 @@ def test_add_condition_to_tx_with_invalid_parameters(): tx.add_condition('somewronginput') -def test_sign_with_invalid_parameters(tx, user_priv): +def test_sign_with_invalid_parameters(utx, user_priv): with raises(TypeError): - tx.sign(None) + utx.sign(None) with raises(TypeError): - tx.sign(user_priv) + utx.sign(user_priv) def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, user_priv): @@ -326,26 +326,28 @@ def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, assert tx.fulfillments_valid() is True -def test_invoke_simple_signature_fulfillment_with_invalid_parameters(tx, default_single_ffill): +def test_invoke_simple_signature_fulfillment_with_invalid_parameters(utx, default_single_ffill): from bigchaindb_common.exceptions import KeypairMismatchException with raises(KeypairMismatchException): - tx._sign_simple_signature_fulfillment(default_single_ffill, 'somemessage', {'wrong_pub_key': 'wrong_priv_key'}) + utx._sign_simple_signature_fulfillment(default_single_ffill, + 'somemessage', + {'wrong_pub_key': 'wrong_priv_key'}) -def test_invoke_threshold_signature_fulfillment_with_invalid_parameters(tx, default_threshold_ffill, user3_pub, +def test_invoke_threshold_signature_fulfillment_with_invalid_parameters(utx, default_threshold_ffill, user3_pub, user3_priv): from bigchaindb_common.exceptions import KeypairMismatchException with raises(KeypairMismatchException): default_threshold_ffill.owners_before = ['somewrongvalue'] - tx._sign_threshold_signature_fulfillment(default_threshold_ffill, 'somemessage', None) + utx._sign_threshold_signature_fulfillment(default_threshold_ffill, 'somemessage', None) with raises(KeypairMismatchException): - tx._sign_threshold_signature_fulfillment(default_threshold_ffill, 'somemessage', [{user3_pub: user3_priv}]) + utx._sign_threshold_signature_fulfillment(default_threshold_ffill, 'somemessage', [{user3_pub: user3_priv}]) -def test_validate_fulfillment_with_invalid_parameters(tx): - assert tx._fulfillment_valid() == False +def test_validate_fulfillment_with_invalid_parameters(utx): + assert utx._fulfillment_valid() == False def test_validating_multiple_fulfillments(default_single_ffill, default_single_cond, user_priv): @@ -391,7 +393,7 @@ def test_validate_tx_threshold_signature(default_threshold_ffill, default_thresh assert tx.fulfillments_valid() is True -def test_transfer(tx, user_pub, user_priv, user2_pub): +def test_transfer(utx, user_pub, user_priv, user2_pub, cond_uri): from copy import deepcopy from bigchaindb_common.crypto import SigningKey @@ -399,12 +401,30 @@ def test_transfer(tx, user_pub, user_priv, user2_pub): from cryptoconditions import Ed25519Fulfillment cond = Condition(Ed25519Fulfillment(public_key=user2_pub).condition_uri, [user2_pub]) - transfer_tx = tx.transfer([cond]) + transfer_tx = utx.transfer([cond]) expected = deepcopy(transfer_tx.fulfillments[0]) expected.fulfillment.sign(str(transfer_tx), SigningKey(user_priv)) transfer_tx.sign([user_priv]) - assert tx.fulfillments[0].fulfillment.to_dict()['signature'] == expected.fulfillment.to_dict()['signature'] - assert transfer_tx.fulfillments_valid() is True + assert utx.fulfillments[0].fulfillment.to_dict()['signature'] == expected.fulfillment.to_dict()['signature'] + assert transfer_tx.fulfillments_valid([utx.conditions[0].condition_uri]) is True + + +def test_validate_fulfillments_of_transfer_tx_with_invalid_parameters(transfer_tx, cond_uri, utx, user_priv): + assert transfer_tx.fulfillments_valid(['Incorrect condition uri']) is False + assert transfer_tx.fulfillments_valid([cond_uri]) is False + assert transfer_tx.fulfillments_valid([utx.conditions[0].condition_uri]) is True + + with raises(TypeError): + transfer_tx.fulfillments_valid('a string and not a list') + with raises(ValueError): + transfer_tx.fulfillments_valid([]) + with raises(TypeError): + transfer_tx.operation = "Operation that doens't exist" + transfer_tx.fulfillments_valid([utx.conditions[0].condition_uri]) + with raises(ValueError): + tx = utx.sign([user_priv]) + tx.conditions = [] + tx.fulfillments_valid() From 00b47fa986a8d3f5d0642d8b05a581e18cee162c Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 19 Aug 2016 11:39:34 +0200 Subject: [PATCH 64/98] Remove condition and fulfillment ids --- test_transaction.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index ca6b9b1c..9b041c3d 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -9,7 +9,6 @@ def test_fulfillment_serialization(ffill_uri, user_pub): 'owners_before': [user_pub], 'fulfillment': ffill_uri, 'details': CCFulfillment.from_uri(ffill_uri).to_dict(), - 'fid': 0, 'input': None, } ffill = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) @@ -26,7 +25,6 @@ def test_fulfillment_deserialization_with_uri(ffill_uri, user_pub): 'owners_before': [user_pub], 'fulfillment': ffill_uri, 'details': CCFulfillment.from_uri(ffill_uri).to_dict(), - 'fid': 0, 'input': None, } ffill = Fulfillment.from_dict(ffill) @@ -43,7 +41,6 @@ def test_fulfillment_deserialization_with_dict(cc_ffill, user_pub): 'owners_before': [user_pub], 'fulfillment': None, 'details': cc_ffill.to_dict(), - 'fid': 0, 'input': None, } ffill = Fulfillment.from_dict(ffill) @@ -66,7 +63,6 @@ def test_gen_default_fulfillment_with_single_owner_after(user_pub): ffill = Fulfillment.gen_default([user_pub]) assert ffill.owners_before == [user_pub] - assert ffill.fid == 0 assert ffill.tx_input is None assert ffill.fulfillment.to_dict() == Ed25519Fulfillment(public_key=user_pub).to_dict() @@ -82,7 +78,6 @@ def test_gen_default_fulfillment_with_multiple_owners_after(user_pub_keys): expected_ffill.add_subfulfillment(Ed25519Fulfillment(public_key=user_pub)) assert ffill.owners_before == user_pub_keys - assert ffill.fid == 0 assert ffill.tx_input is None assert ffill.fulfillment.to_dict() == expected_ffill.to_dict() @@ -102,7 +97,6 @@ def test_condition_serialization(cond_uri, user_pub): expected = { 'condition': cond_uri, 'owners_after': [user_pub], - 'cid': 0, } cond = Condition(cond_uri, [user_pub]) @@ -117,7 +111,6 @@ def test_condition_deserialization(cond_uri, user_pub): cond = { 'condition': cond_uri, 'owners_after': [user_pub], - 'cid': 0, } cond = Condition.from_dict(cond) @@ -230,8 +223,8 @@ def test_transaction_link_serialization(): tx_id = 'a transaction id' expected = { - 'tx_id': tx_id, - 'cid': 0, + 'transaction_id': tx_id, + 'condition_id': 0, } tx_link = TransactionLink(tx_id, 0) @@ -253,8 +246,8 @@ def test_transaction_link_deserialization(): tx_id = 'a transaction id' expected = TransactionLink(tx_id, 0) tx_link = { - 'tx_id': tx_id, - 'cid': 0, + 'transaction_id': tx_id, + 'condition_id': 0, } tx_link = TransactionLink.from_dict(tx_link) From d3ac54e8399272bf47607cffa35a8cca04c0f9fd Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 19 Aug 2016 13:59:49 +0200 Subject: [PATCH 65/98] Fix signing logic Specifically for transfer-tx with multiple inputs and outputs. --- test_transaction.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test_transaction.py b/test_transaction.py index 9b041c3d..d9b5f771 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -405,6 +405,25 @@ def test_transfer(utx, user_pub, user_priv, user2_pub, cond_uri): assert transfer_tx.fulfillments_valid([utx.conditions[0].condition_uri]) is True +def test_multiple_fulfillment_validation_of_transfer_tx(default_single_ffill, default_single_cond, user_priv, + user2_pub, user2_priv, user3_pub, user3_priv): + from copy import deepcopy + + from bigchaindb_common.transaction import Transaction, Condition + from cryptoconditions import Ed25519Fulfillment + + tx = Transaction(Transaction.CREATE, + [default_single_ffill, deepcopy(default_single_ffill)], + [default_single_cond, deepcopy(default_single_cond)]) + tx.sign([user_priv]) + conditions = [Condition(Ed25519Fulfillment(public_key=user2_pub).condition_uri, [user2_pub]), + Condition(Ed25519Fulfillment(public_key=user3_pub).condition_uri, [user3_pub])] + transfer_utx = tx.transfer(conditions) + transfer_tx = transfer_utx.sign([user_priv]) + + assert transfer_tx.fulfillments_valid([cond.condition_uri for cond in tx.conditions]) is True + + def test_validate_fulfillments_of_transfer_tx_with_invalid_parameters(transfer_tx, cond_uri, utx, user_priv): assert transfer_tx.fulfillments_valid(['Incorrect condition uri']) is False assert transfer_tx.fulfillments_valid([cond_uri]) is False From 42655f1cfcc7cf4ce4b87640a7c1404744491af3 Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 19 Aug 2016 14:14:47 +0200 Subject: [PATCH 66/98] Fix test case --- test_transaction.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index d9b5f771..a270165e 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -310,6 +310,7 @@ def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, from bigchaindb_common.crypto import SigningKey from bigchaindb_common.transaction import Transaction + tx = Transaction(Transaction.CREATE, [default_single_ffill], [default_single_cond]) expected = deepcopy(default_single_ffill) expected.fulfillment.sign(str(tx), SigningKey(user_priv)) @@ -329,14 +330,14 @@ def test_invoke_simple_signature_fulfillment_with_invalid_parameters(utx, defaul def test_invoke_threshold_signature_fulfillment_with_invalid_parameters(utx, default_threshold_ffill, user3_pub, - user3_priv): + user3_priv, user_pub_keys): from bigchaindb_common.exceptions import KeypairMismatchException + with raises(KeypairMismatchException): + utx._sign_threshold_signature_fulfillment(default_threshold_ffill, 'somemessage', {user3_pub: user3_priv}) with raises(KeypairMismatchException): default_threshold_ffill.owners_before = ['somewrongvalue'] utx._sign_threshold_signature_fulfillment(default_threshold_ffill, 'somemessage', None) - with raises(KeypairMismatchException): - utx._sign_threshold_signature_fulfillment(default_threshold_ffill, 'somemessage', [{user3_pub: user3_priv}]) def test_validate_fulfillment_with_invalid_parameters(utx): From 4699e69f63b602d4f93e09ec65b286086f05648c Mon Sep 17 00:00:00 2001 From: tim Date: Tue, 23 Aug 2016 20:08:51 +0200 Subject: [PATCH 67/98] Compliance to legacy BDB models --- test_transaction.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index a270165e..28f116a2 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -136,7 +136,7 @@ def test_data_serialization(payload, payload_id): expected = { 'payload': payload, - 'hash': payload_id + 'uuid': payload_id } data = Data(payload, payload_id) @@ -147,7 +147,7 @@ def test_data_deserialization(payload, payload_id): from bigchaindb_common.transaction import Data expected = Data(payload, payload_id) - data = Data.from_dict({'payload': payload, 'hash': payload_id}) + data = Data.from_dict({'payload': payload, 'uuid': payload_id}) assert data.to_dict() == expected.to_dict() @@ -223,8 +223,8 @@ def test_transaction_link_serialization(): tx_id = 'a transaction id' expected = { - 'transaction_id': tx_id, - 'condition_id': 0, + 'txid': tx_id, + 'cid': 0, } tx_link = TransactionLink(tx_id, 0) @@ -246,8 +246,8 @@ def test_transaction_link_deserialization(): tx_id = 'a transaction id' expected = TransactionLink(tx_id, 0) tx_link = { - 'transaction_id': tx_id, - 'condition_id': 0, + 'txid': tx_id, + 'cid': 0, } tx_link = TransactionLink.from_dict(tx_link) From f65baae833e2be722a3dcc9a7a0f4fd913b2ba8b Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 24 Aug 2016 11:57:41 +0200 Subject: [PATCH 68/98] Adjust fulfillment validation interface --- test_transaction.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 28f116a2..9a054af6 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -403,7 +403,7 @@ def test_transfer(utx, user_pub, user_priv, user2_pub, cond_uri): transfer_tx.sign([user_priv]) assert utx.fulfillments[0].fulfillment.to_dict()['signature'] == expected.fulfillment.to_dict()['signature'] - assert transfer_tx.fulfillments_valid([utx.conditions[0].condition_uri]) is True + assert transfer_tx.fulfillments_valid(utx.conditions) is True def test_multiple_fulfillment_validation_of_transfer_tx(default_single_ffill, default_single_cond, user_priv, @@ -422,21 +422,29 @@ def test_multiple_fulfillment_validation_of_transfer_tx(default_single_ffill, de transfer_utx = tx.transfer(conditions) transfer_tx = transfer_utx.sign([user_priv]) - assert transfer_tx.fulfillments_valid([cond.condition_uri for cond in tx.conditions]) is True + assert transfer_tx.fulfillments_valid(tx.conditions) is True -def test_validate_fulfillments_of_transfer_tx_with_invalid_parameters(transfer_tx, cond_uri, utx, user_priv): - assert transfer_tx.fulfillments_valid(['Incorrect condition uri']) is False - assert transfer_tx.fulfillments_valid([cond_uri]) is False - assert transfer_tx.fulfillments_valid([utx.conditions[0].condition_uri]) is True +def test_validate_fulfillments_of_transfer_tx_with_invalid_parameters(transfer_tx, + cond_uri, + utx, + user2_pub, + user_priv): + from bigchaindb_common.transaction import Condition + assert transfer_tx.fulfillments_valid([Condition('invalidly formed condition uri', + ['invalid'])]) is False + assert transfer_tx.fulfillments_valid([Condition(cond_uri, [user2_pub])]) is False + + with raises(ValueError): + assert transfer_tx.fulfillments_valid(None) is False with raises(TypeError): - transfer_tx.fulfillments_valid('a string and not a list') + transfer_tx.fulfillments_valid('not a list') with raises(ValueError): transfer_tx.fulfillments_valid([]) with raises(TypeError): - transfer_tx.operation = "Operation that doens't exist" - transfer_tx.fulfillments_valid([utx.conditions[0].condition_uri]) + 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 = [] From 52f541279cbda92b68dcdc18013a5e7fe236388c Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 24 Aug 2016 18:29:20 +0200 Subject: [PATCH 69/98] Add serialization validation for txids --- test_transaction.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 9a054af6..82e995f7 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -181,14 +181,12 @@ def test_transaction_serialization(default_single_ffill, default_single_cond): def test_transaction_deserialization(default_single_ffill, default_single_cond): from bigchaindb_common.transaction import Transaction - tx_id = 'l0l' timestamp = '66666666666' expected = Transaction(Transaction.CREATE, [default_single_ffill], [default_single_cond], None, timestamp, Transaction.VERSION) tx = { - 'id': tx_id, 'version': Transaction.VERSION, 'transaction': { # NOTE: This test assumes that Fulfillments and Conditions can successfully be serialized @@ -199,11 +197,28 @@ def test_transaction_deserialization(default_single_ffill, default_single_cond): 'data': None, } } + tx['id'] = Transaction._to_hash(Transaction._to_str(Transaction._remove_signatures(tx))) tx = Transaction.from_dict(tx) assert tx.to_dict() == expected.to_dict() +def test_tx_serialization_with_incorrect_hash(utx): + from bigchaindb_common.transaction import Transaction + from bigchaindb_common.exceptions import InvalidHash + + utx_dict = utx.to_dict() + utx_dict['id'] = 'abc' + with raises(InvalidHash): + Transaction.from_dict(utx_dict) + utx_dict.pop('id') + with raises(InvalidHash): + Transaction.from_dict(utx_dict) + utx_dict['id'] = [] + with raises(InvalidHash): + Transaction.from_dict(utx_dict) + + def test_invalid_tx_initialization(): from bigchaindb_common.transaction import Transaction @@ -310,7 +325,6 @@ def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, from bigchaindb_common.crypto import SigningKey from bigchaindb_common.transaction import Transaction - tx = Transaction(Transaction.CREATE, [default_single_ffill], [default_single_cond]) expected = deepcopy(default_single_ffill) expected.fulfillment.sign(str(tx), SigningKey(user_priv)) From 2631aa17c715f7ad26c10ae1e258ca98443e8a29 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 24 Aug 2016 19:12:32 +0200 Subject: [PATCH 70/98] Use __eq__ to compare objects --- test_transaction.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 82e995f7..35926f61 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -29,7 +29,7 @@ def test_fulfillment_deserialization_with_uri(ffill_uri, user_pub): } ffill = Fulfillment.from_dict(ffill) - assert ffill.to_dict() == expected.to_dict() + assert ffill == expected def test_fulfillment_deserialization_with_dict(cc_ffill, user_pub): @@ -45,7 +45,7 @@ def test_fulfillment_deserialization_with_dict(cc_ffill, user_pub): } ffill = Fulfillment.from_dict(ffill) - assert ffill.to_dict() == expected.to_dict() + assert ffill == expected def test_invalid_fulfillment_initialization(cc_ffill, user_pub): @@ -114,7 +114,7 @@ def test_condition_deserialization(cond_uri, user_pub): } cond = Condition.from_dict(cond) - assert cond.to_dict() == expected.to_dict() + assert cond == expected def test_invalid_condition_initialization(cond_uri, user_pub): @@ -149,7 +149,7 @@ def test_data_deserialization(payload, payload_id): expected = Data(payload, payload_id) data = Data.from_dict({'payload': payload, 'uuid': payload_id}) - assert data.to_dict() == expected.to_dict() + assert data == expected def test_transaction_serialization(default_single_ffill, default_single_cond): @@ -200,7 +200,7 @@ def test_transaction_deserialization(default_single_ffill, default_single_cond): tx['id'] = Transaction._to_hash(Transaction._to_str(Transaction._remove_signatures(tx))) tx = Transaction.from_dict(tx) - assert tx.to_dict() == expected.to_dict() + assert tx == expected def test_tx_serialization_with_incorrect_hash(utx): @@ -266,7 +266,7 @@ def test_transaction_link_deserialization(): } tx_link = TransactionLink.from_dict(tx_link) - assert tx_link.to_dict() == expected.to_dict() + assert tx_link == expected def test_transaction_link_deserialization_with_empty_payload(): @@ -275,7 +275,7 @@ def test_transaction_link_deserialization_with_empty_payload(): expected = TransactionLink() tx_link = TransactionLink.from_dict(None) - assert tx_link.to_dict() == expected.to_dict() + assert tx_link == expected def test_add_fulfillment_to_tx(default_single_ffill): From c2d20573d0e36fd6643908b7f6b46f5eb6676a0b Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 25 Aug 2016 19:29:16 +0200 Subject: [PATCH 71/98] Heavy refactor to comply with current implementation --- test_transaction.py | 291 ++++++++++++++++++++------------------------ 1 file changed, 134 insertions(+), 157 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 35926f61..eeb82ddf 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -8,11 +8,9 @@ def test_fulfillment_serialization(ffill_uri, user_pub): expected = { 'owners_before': [user_pub], 'fulfillment': ffill_uri, - 'details': CCFulfillment.from_uri(ffill_uri).to_dict(), 'input': None, } ffill = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) - assert ffill.to_dict() == expected @@ -24,7 +22,6 @@ def test_fulfillment_deserialization_with_uri(ffill_uri, user_pub): ffill = { 'owners_before': [user_pub], 'fulfillment': ffill_uri, - 'details': CCFulfillment.from_uri(ffill_uri).to_dict(), 'input': None, } ffill = Fulfillment.from_dict(ffill) @@ -32,15 +29,14 @@ def test_fulfillment_deserialization_with_uri(ffill_uri, user_pub): assert ffill == expected -def test_fulfillment_deserialization_with_dict(cc_ffill, user_pub): +def test_fulfillment_deserialization_with_dict(user_pub): from bigchaindb_common.transaction import Fulfillment - expected = Fulfillment(cc_ffill, [user_pub]) + expected = Fulfillment(None, [user_pub]) ffill = { 'owners_before': [user_pub], 'fulfillment': None, - 'details': cc_ffill.to_dict(), 'input': None, } ffill = Fulfillment.from_dict(ffill) @@ -48,68 +44,31 @@ def test_fulfillment_deserialization_with_dict(cc_ffill, user_pub): assert ffill == expected -def test_invalid_fulfillment_initialization(cc_ffill, user_pub): - from bigchaindb_common.transaction import Fulfillment - - with raises(TypeError): - Fulfillment(cc_ffill, user_pub) - with raises(TypeError): - Fulfillment(cc_ffill, [], tx_input='somethingthatiswrong') - - -def test_gen_default_fulfillment_with_single_owner_after(user_pub): - from bigchaindb_common.transaction import Fulfillment - from cryptoconditions import Ed25519Fulfillment - - ffill = Fulfillment.gen_default([user_pub]) - assert ffill.owners_before == [user_pub] - assert ffill.tx_input is None - assert ffill.fulfillment.to_dict() == Ed25519Fulfillment(public_key=user_pub).to_dict() - - -def test_gen_default_fulfillment_with_multiple_owners_after(user_pub_keys): - from bigchaindb_common.transaction import Fulfillment - from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment - - ffill = Fulfillment.gen_default(user_pub_keys) - expected_ffill = ThresholdSha256Fulfillment(threshold=len(user_pub_keys)) - # NOTE: Does it make sense to have the exact same logic as in the tested method here? - for user_pub in user_pub_keys: - expected_ffill.add_subfulfillment(Ed25519Fulfillment(public_key=user_pub)) - - assert ffill.owners_before == user_pub_keys - assert ffill.tx_input is None - assert ffill.fulfillment.to_dict() == expected_ffill.to_dict() - - -def test_invalid_gen_default_arguments(): - from bigchaindb_common.transaction import Fulfillment - - with raises(TypeError): - Fulfillment.gen_default({}) - with raises(NotImplementedError): - Fulfillment.gen_default([]) - - -def test_condition_serialization(cond_uri, user_pub): +def test_condition_serialization(user_Ed25519, user_pub): from bigchaindb_common.transaction import Condition expected = { - 'condition': cond_uri, + 'condition': { + 'uri': user_Ed25519.condition_uri, + 'details': user_Ed25519.to_dict(), + }, 'owners_after': [user_pub], } - cond = Condition(cond_uri, [user_pub]) + cond = Condition(user_Ed25519, [user_pub]) assert cond.to_dict() == expected -def test_condition_deserialization(cond_uri, user_pub): +def test_condition_deserialization(user_Ed25519, user_pub): from bigchaindb_common.transaction import Condition - expected = Condition(cond_uri, [user_pub]) + expected = Condition(user_Ed25519, [user_pub]) cond = { - 'condition': cond_uri, + 'condition': { + 'uri': user_Ed25519.condition_uri, + 'details': user_Ed25519.to_dict() + }, 'owners_after': [user_pub], } cond = Condition.from_dict(cond) @@ -124,35 +83,7 @@ def test_invalid_condition_initialization(cond_uri, user_pub): Condition(cond_uri, user_pub) -def test_invalid_data_initialization(): - from bigchaindb_common.transaction import Data - - with raises(TypeError): - Data([]) - - -def test_data_serialization(payload, payload_id): - from bigchaindb_common.transaction import Data - - expected = { - 'payload': payload, - 'uuid': payload_id - } - data = Data(payload, payload_id) - - assert data.to_dict() == expected - - -def test_data_deserialization(payload, payload_id): - from bigchaindb_common.transaction import Data - - expected = Data(payload, payload_id) - data = Data.from_dict({'payload': payload, 'uuid': payload_id}) - - assert data == expected - - -def test_transaction_serialization(default_single_ffill, default_single_cond): +def test_transaction_serialization(user_ffill, user_cond): from bigchaindb_common.transaction import Transaction tx_id = 'l0l' @@ -163,35 +94,35 @@ def test_transaction_serialization(default_single_ffill, default_single_cond): 'version': Transaction.VERSION, 'transaction': { # NOTE: This test assumes that Fulfillments and Conditions can successfully be serialized - 'fulfillments': [default_single_ffill.to_dict()], - 'conditions': [default_single_cond.to_dict()], + 'fulfillments': [user_ffill.to_dict(0)], + 'conditions': [user_cond.to_dict(0)], 'operation': Transaction.CREATE, 'timestamp': timestamp, 'data': None, } } - tx_dict = Transaction(Transaction.CREATE, [default_single_ffill], [default_single_cond]).to_dict() + tx_dict = Transaction(Transaction.CREATE, [user_ffill], [user_cond]).to_dict() tx_dict['id'] = tx_id tx_dict['transaction']['timestamp'] = timestamp assert tx_dict == expected -def test_transaction_deserialization(default_single_ffill, default_single_cond): +def test_transaction_deserialization(user_ffill, user_cond): from bigchaindb_common.transaction import Transaction timestamp = '66666666666' - expected = Transaction(Transaction.CREATE, [default_single_ffill], [default_single_cond], None, timestamp, + expected = Transaction(Transaction.CREATE, [user_ffill], [user_cond], None, timestamp, Transaction.VERSION) tx = { 'version': Transaction.VERSION, 'transaction': { # NOTE: This test assumes that Fulfillments and Conditions can successfully be serialized - 'fulfillments': [default_single_ffill.to_dict()], - 'conditions': [default_single_cond.to_dict()], + 'fulfillments': [user_ffill.to_dict()], + 'conditions': [user_cond.to_dict()], 'operation': Transaction.CREATE, 'timestamp': timestamp, 'data': None, @@ -233,6 +164,43 @@ def test_invalid_tx_initialization(): Transaction('RANSFER', [], []) +def test_invalid_fulfillment_initialization(user_ffill, user_pub): + from bigchaindb_common.transaction import Fulfillment + + with raises(TypeError): + Fulfillment(user_ffill, user_pub) + with raises(TypeError): + Fulfillment(user_ffill, [], tx_input='somethingthatiswrong') + + +def test_invalid_data_initialization(): + from bigchaindb_common.transaction import Data + + with raises(TypeError): + Data([]) + + +def test_data_serialization(payload, payload_id): + from bigchaindb_common.transaction import Data + + expected = { + 'payload': payload, + 'uuid': payload_id + } + data = Data(payload, payload_id) + + assert data.to_dict() == expected + + +def test_data_deserialization(payload, payload_id): + from bigchaindb_common.transaction import Data + + expected = Data(payload, payload_id) + data = Data.from_dict({'payload': payload, 'uuid': payload_id}) + + assert data == expected + + def test_transaction_link_serialization(): from bigchaindb_common.transaction import TransactionLink @@ -278,11 +246,11 @@ def test_transaction_link_deserialization_with_empty_payload(): assert tx_link == expected -def test_add_fulfillment_to_tx(default_single_ffill): +def test_add_fulfillment_to_tx(user_ffill): from bigchaindb_common.transaction import Transaction tx = Transaction(Transaction.CREATE, [], []) - tx.add_fulfillment(default_single_ffill) + tx.add_fulfillment(user_ffill) assert len(tx.fulfillments) == 1 @@ -295,11 +263,11 @@ def test_add_fulfillment_to_tx_with_invalid_parameters(): tx.add_fulfillment('somewronginput') -def test_add_condition_to_tx(default_single_cond): +def test_add_condition_to_tx(user_cond): from bigchaindb_common.transaction import Transaction tx = Transaction(Transaction.CREATE) - tx.add_condition(default_single_cond) + tx.add_condition(user_cond) assert len(tx.conditions) == 1 @@ -319,122 +287,128 @@ def test_sign_with_invalid_parameters(utx, user_priv): utx.sign(user_priv) -def test_validate_tx_simple_signature(default_single_ffill, default_single_cond, user_priv): +def test_validate_tx_simple_create_signature(user_ffill, user_cond, user_priv): from copy import deepcopy - from bigchaindb_common.crypto import SigningKey from bigchaindb_common.transaction import Transaction - tx = Transaction(Transaction.CREATE, [default_single_ffill], [default_single_cond]) - expected = deepcopy(default_single_ffill) + tx = Transaction(Transaction.CREATE, [user_ffill], [user_cond]) + expected = deepcopy(user_cond) expected.fulfillment.sign(str(tx), SigningKey(user_priv)) tx.sign([user_priv]) - assert tx.fulfillments[0].fulfillment.to_dict()['signature'] == expected.fulfillment.to_dict()['signature'] + assert tx.fulfillments[0].to_dict()['fulfillment'] == expected.fulfillment.serialize_uri() assert tx.fulfillments_valid() is True -def test_invoke_simple_signature_fulfillment_with_invalid_parameters(utx, default_single_ffill): +def test_invoke_simple_signature_fulfillment_with_invalid_parameters(utx, user_ffill): from bigchaindb_common.exceptions import KeypairMismatchException with raises(KeypairMismatchException): - utx._sign_simple_signature_fulfillment(default_single_ffill, + utx._sign_simple_signature_fulfillment(user_ffill, + 0, 'somemessage', {'wrong_pub_key': 'wrong_priv_key'}) -def test_invoke_threshold_signature_fulfillment_with_invalid_parameters(utx, default_threshold_ffill, user3_pub, - user3_priv, user_pub_keys): +def test_invoke_threshold_signature_fulfillment_with_invalid_parameters(utx, + user_user2_threshold_ffill, + user3_pub, + user3_priv): from bigchaindb_common.exceptions import KeypairMismatchException with raises(KeypairMismatchException): - utx._sign_threshold_signature_fulfillment(default_threshold_ffill, 'somemessage', {user3_pub: user3_priv}) + utx._sign_threshold_signature_fulfillment(user_user2_threshold_ffill, + 0, + 'somemessage', + {user3_pub: user3_priv}) with raises(KeypairMismatchException): - default_threshold_ffill.owners_before = ['somewrongvalue'] - utx._sign_threshold_signature_fulfillment(default_threshold_ffill, 'somemessage', None) + user_user2_threshold_ffill.owners_before = ['somewrongvalue'] + utx._sign_threshold_signature_fulfillment(user_user2_threshold_ffill, + 0, + 'somemessage', + None) def test_validate_fulfillment_with_invalid_parameters(utx): - assert utx._fulfillment_valid() == False + from bigchaindb_common.transaction import Transaction + input_conditions = [cond.fulfillment.condition_uri for cond + in utx.conditions] + tx_serialized = Transaction._to_str(Transaction._remove_signatures(utx.to_dict())) + assert utx._fulfillment_valid(utx.fulfillments[0], + tx_serialized, + input_conditions) == False -def test_validating_multiple_fulfillments(default_single_ffill, default_single_cond, user_priv): +def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv): from copy import deepcopy from bigchaindb_common.crypto import SigningKey from bigchaindb_common.transaction import Transaction tx = Transaction(Transaction.CREATE, - [default_single_ffill, deepcopy(default_single_ffill)], - [default_single_cond, deepcopy(default_single_cond)]) + [user_ffill, deepcopy(user_ffill)], + [user_ffill, deepcopy(user_cond)]) expected_first = deepcopy(tx) - expected_second= deepcopy(tx) + expected_second = deepcopy(tx) expected_first.fulfillments = [expected_first.fulfillments[0]] - expected_first.conditions= [expected_first.conditions[0]] + expected_first.conditions = [expected_first.conditions[0]] expected_second.fulfillments = [expected_second.fulfillments[1]] - expected_second.conditions= [expected_second.conditions[1]] + expected_second.conditions = [expected_second.conditions[1]] expected_first.fulfillments[0].fulfillment.sign(str(expected_first), SigningKey(user_priv)) expected_second.fulfillments[0].fulfillment.sign(str(expected_second), SigningKey(user_priv)) tx.sign([user_priv]) - assert tx.fulfillments[0].fulfillment.to_dict() == expected_first.fulfillments[0].fulfillment.to_dict() - assert tx.fulfillments[1].fulfillment.to_dict() == expected_second.fulfillments[0].fulfillment.to_dict() + assert tx.fulfillments[0].to_dict()['fulfillment'] == expected_first.fulfillments[0].fulfillment.serialize_uri() + assert tx.fulfillments[1].to_dict()['fulfillment'] == expected_second.fulfillments[0].fulfillment.serialize_uri() assert tx.fulfillments_valid() is True -def test_validate_tx_threshold_signature(default_threshold_ffill, default_threshold_cond, user_pub_keys, - user_priv_keys): +def test_validate_tx_threshold_create_signature(user_user2_threshold_ffill, + user_user2_threshold_cond, + user_pub, + user2_pub, + user_priv, + user2_priv): from copy import deepcopy from bigchaindb_common.crypto import SigningKey from bigchaindb_common.transaction import Transaction - tx = Transaction(Transaction.CREATE, [default_threshold_ffill], [default_threshold_cond]) - expected = deepcopy(default_threshold_ffill) - expected.fulfillment.subconditions[0]['body'].sign(str(tx), SigningKey(user_priv_keys[0])) - expected.fulfillment.subconditions[1]['body'].sign(str(tx), SigningKey(user_priv_keys[1])) - tx.sign(user_priv_keys) + tx = Transaction(Transaction.CREATE, [user_user2_threshold_ffill], [user_user2_threshold_cond]) + expected = deepcopy(user_user2_threshold_cond) + expected.fulfillment.subconditions[0]['body'].sign(str(tx), SigningKey(user_priv)) + expected.fulfillment.subconditions[1]['body'].sign(str(tx), SigningKey(user2_priv)) + tx.sign([user_priv, user2_priv]) - assert tx.fulfillments[0].to_dict()['fulfillment'] == expected.to_dict()['fulfillment'] + assert tx.fulfillments[0].to_dict()['fulfillment'] == expected.fulfillment.serialize_uri() assert tx.fulfillments_valid() is True -def test_transfer(utx, user_pub, user_priv, user2_pub, cond_uri): +def test_multiple_fulfillment_validation_of_transfer_tx(user_ffill, user_cond, + user_priv, user2_pub, + user2_priv, user3_pub, + user3_priv): from copy import deepcopy - - from bigchaindb_common.crypto import SigningKey - from bigchaindb_common.transaction import Condition - from cryptoconditions import Ed25519Fulfillment - - cond = Condition(Ed25519Fulfillment(public_key=user2_pub).condition_uri, [user2_pub]) - transfer_tx = utx.transfer([cond]) - - expected = deepcopy(transfer_tx.fulfillments[0]) - expected.fulfillment.sign(str(transfer_tx), SigningKey(user_priv)) - - transfer_tx.sign([user_priv]) - - assert utx.fulfillments[0].fulfillment.to_dict()['signature'] == expected.fulfillment.to_dict()['signature'] - assert transfer_tx.fulfillments_valid(utx.conditions) is True - - -def test_multiple_fulfillment_validation_of_transfer_tx(default_single_ffill, default_single_cond, user_priv, - user2_pub, user2_priv, user3_pub, user3_priv): - from copy import deepcopy - - from bigchaindb_common.transaction import Transaction, Condition + from bigchaindb_common.transaction import (Transaction, TransactionLink, + Fulfillment, Condition) from cryptoconditions import Ed25519Fulfillment tx = Transaction(Transaction.CREATE, - [default_single_ffill, deepcopy(default_single_ffill)], - [default_single_cond, deepcopy(default_single_cond)]) + [user_ffill, deepcopy(user_ffill)], + [user_cond, deepcopy(user_cond)]) tx.sign([user_priv]) - conditions = [Condition(Ed25519Fulfillment(public_key=user2_pub).condition_uri, [user2_pub]), - Condition(Ed25519Fulfillment(public_key=user3_pub).condition_uri, [user3_pub])] - transfer_utx = tx.transfer(conditions) - transfer_tx = transfer_utx.sign([user_priv]) + + fulfillments = [Fulfillment(cond.fulfillment, cond.owners_after, + TransactionLink(tx.id, index)) + for index, cond in enumerate(tx.conditions)] + conditions = [Condition(Ed25519Fulfillment(public_key=user3_pub), [user3_pub]), + Condition(Ed25519Fulfillment(public_key=user3_pub), [user3_pub])] + transfer_tx = Transaction('TRANSFER', fulfillments, conditions) + + transfer_tx = transfer_tx.sign([user_priv]) assert transfer_tx.fulfillments_valid(tx.conditions) is True @@ -445,14 +419,17 @@ def test_validate_fulfillments_of_transfer_tx_with_invalid_parameters(transfer_t user2_pub, user_priv): from bigchaindb_common.transaction import Condition + from cryptoconditions import Ed25519Fulfillment - assert transfer_tx.fulfillments_valid([Condition('invalidly formed condition uri', + assert transfer_tx.fulfillments_valid([Condition(Ed25519Fulfillment.from_uri('cf:0:'), ['invalid'])]) is False - assert transfer_tx.fulfillments_valid([Condition(cond_uri, [user2_pub])]) is False + invalid_cond = utx.conditions[0] + invalid_cond.owners_after = 'invalid' + assert transfer_tx.fulfillments_valid([invalid_cond]) is True - with raises(ValueError): - assert transfer_tx.fulfillments_valid(None) is False with raises(TypeError): + assert transfer_tx.fulfillments_valid(None) is False + with raises(AttributeError): transfer_tx.fulfillments_valid('not a list') with raises(ValueError): transfer_tx.fulfillments_valid([]) From e60916f82374e89a4eb8f0a369c4cb1e834d068b Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 25 Aug 2016 21:29:08 +0200 Subject: [PATCH 72/98] Add Transaction.create --- test_transaction.py | 116 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/test_transaction.py b/test_transaction.py index eeb82ddf..88f76301 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -440,3 +440,119 @@ def test_validate_fulfillments_of_transfer_tx_with_invalid_parameters(transfer_t tx = utx.sign([user_priv]) tx.conditions = [] tx.fulfillments_valid() + + +def test_create_create_transaction_single_io(user_cond, user_pub): + from bigchaindb_common.transaction import Transaction + + expected = { + 'transaction': { + 'conditions': [user_cond.to_dict(0)], + 'data': { + 'payload': { + 'message': 'hello' + } + }, + 'fulfillments': [ + { + 'owners_before': [ + user_pub + ], + 'fid': 0, + 'fulfillment': None, + 'input': None + } + ], + 'operation': 'CREATE', + }, + 'version': 1 + } + + tx = Transaction.create([user_pub], [user_pub], {'message': 'hello'}).to_dict() + # TODO: Fix this with monkeypatching + tx.pop('id') + tx['transaction']['data'].pop('uuid') + tx['transaction'].pop('timestamp') + + assert tx == expected + + +def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, + user2_pub): + from bigchaindb_common.transaction import Transaction + + expected = { + 'transaction': { + 'conditions': [user_cond.to_dict(0), user2_cond.to_dict(1)], + 'data': { + 'payload': { + 'message': 'hello' + } + }, + 'fulfillments': [ + { + 'owners_before': [ + user_pub, + ], + 'fid': 0, + 'fulfillment': None, + 'input': None + }, + { + 'owners_before': [ + user2_pub, + ], + 'fid': 1, + 'fulfillment': None, + 'input': None + } + ], + 'operation': 'CREATE', + }, + 'version': 1 + } + tx = Transaction.create([user_pub, user2_pub], [user_pub, user2_pub], + {'message': 'hello'}).to_dict() + # TODO: Fix this with monkeypatching + tx.pop('id') + tx['transaction']['data'].pop('uuid') + tx['transaction'].pop('timestamp') + + assert tx == expected + + +def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, + user_user2_threshold_cond, + user_user2_threshold_ffill): + from bigchaindb_common.transaction import Transaction + + expected = { + 'transaction': { + 'conditions': [user_user2_threshold_cond.to_dict(0)], + 'data': { + 'payload': { + 'message': 'hello' + } + }, + 'fulfillments': [ + { + 'owners_before': [ + user_pub, + ], + 'fid': 0, + 'fulfillment': None, + 'input': None + }, + ], + 'operation': 'CREATE', + }, + 'version': 1 + } + tx = Transaction.create([user_pub], [user_pub, user2_pub], + {'message': 'hello'}).to_dict() + # TODO: Fix this with monkeypatching + tx.pop('id') + tx['transaction']['data'].pop('uuid') + tx['transaction'].pop('timestamp') + + assert tx == expected From c4f64359cdf154b58417f27b51bb96921783b416 Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 25 Aug 2016 21:57:20 +0200 Subject: [PATCH 73/98] Add validation tests --- test_transaction.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test_transaction.py b/test_transaction.py index 88f76301..d04fac20 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -477,6 +477,14 @@ def test_create_create_transaction_single_io(user_cond, user_pub): assert tx == expected +def test_validate_single_io_create_transaction(user_cond, user_pub, user_priv): + from bigchaindb_common.transaction import Transaction + + tx = Transaction.create([user_pub], [user_pub], {'message': 'hello'}) + tx = tx.sign([user_priv]) + assert tx.fulfillments_valid() is True + + def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, user2_pub): from bigchaindb_common.transaction import Transaction @@ -521,6 +529,16 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, assert tx == expected +def test_validate_multiple_io_create_transaction(user_cond, user_pub, user_priv, + user2_pub, user2_priv): + from bigchaindb_common.transaction import Transaction + + tx = Transaction.create([user_pub, user2_pub], [user_pub, user2_pub], + {'message': 'hello'}) + tx = tx.sign([user_priv, user2_priv]) + assert tx.fulfillments_valid() is True + + def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, user_user2_threshold_cond, user_user2_threshold_ffill): @@ -556,3 +574,13 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, tx['transaction'].pop('timestamp') assert tx == expected + + +def test_validate_threshold_create_transaction(user_cond, user_pub, user_priv, + user2_pub): + from bigchaindb_common.transaction import Transaction + + tx = Transaction.create([user_pub], [user_pub, user2_pub], + {'message': 'hello'}) + tx = tx.sign([user_priv]) + assert tx.fulfillments_valid() is True From f0b8baed8d968fc9ade7e3479a012524663c068c Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 25 Aug 2016 22:21:49 +0200 Subject: [PATCH 74/98] Add Transaction.create for hashlock conditions --- test_transaction.py | 57 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index d04fac20..d119e89d 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -477,7 +477,7 @@ def test_create_create_transaction_single_io(user_cond, user_pub): assert tx == expected -def test_validate_single_io_create_transaction(user_cond, user_pub, user_priv): +def test_validate_single_io_create_transaction(user_pub, user_priv): from bigchaindb_common.transaction import Transaction tx = Transaction.create([user_pub], [user_pub], {'message': 'hello'}) @@ -529,7 +529,7 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, assert tx == expected -def test_validate_multiple_io_create_transaction(user_cond, user_pub, user_priv, +def test_validate_multiple_io_create_transaction(user_pub, user_priv, user2_pub, user2_priv): from bigchaindb_common.transaction import Transaction @@ -576,11 +576,60 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, assert tx == expected -def test_validate_threshold_create_transaction(user_cond, user_pub, user_priv, - user2_pub): +def test_validate_threshold_create_transaction(user_pub, user_priv, user2_pub): from bigchaindb_common.transaction import Transaction tx = Transaction.create([user_pub], [user_pub, user2_pub], {'message': 'hello'}) tx = tx.sign([user_priv]) assert tx.fulfillments_valid() is True + + +def test_create_create_transaction_hashlock(user_pub): + from bigchaindb_common.transaction import Transaction, Condition + from cryptoconditions import PreimageSha256Fulfillment + + secret = b'much secret, wow' + hashlock = PreimageSha256Fulfillment(preimage=secret) + cond = Condition(hashlock) + + expected = { + 'transaction': { + 'conditions': [cond.to_dict(0)], + 'data': { + 'payload': { + 'message': 'hello' + } + }, + 'fulfillments': [ + { + 'owners_before': [ + user_pub, + ], + 'fid': 0, + 'fulfillment': None, + 'input': None + }, + ], + 'operation': 'CREATE', + }, + 'version': 1 + } + + tx = Transaction.create([user_pub], [], {'message': 'hello'}, + secret).to_dict() + # TODO: Fix this with monkeypatching + tx.pop('id') + tx['transaction']['data'].pop('uuid') + tx['transaction'].pop('timestamp') + + assert tx == expected + + +def test_validate_hashlock_create_transaction(user_pub, user_priv): + from bigchaindb_common.transaction import Transaction + + tx = Transaction.create([user_pub], [], {'message': 'hello'}, + b'much secret, wow') + tx = tx.sign([user_priv]) + assert tx.fulfillments_valid() is True From d8e971d4126f91d843fc215f82d3a2ab2863c483 Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 25 Aug 2016 23:27:57 +0200 Subject: [PATCH 75/98] Add hashlock condition serialization --- test_transaction.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/test_transaction.py b/test_transaction.py index d119e89d..2aacf391 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -76,6 +76,43 @@ def test_condition_deserialization(user_Ed25519, user_pub): assert cond == expected +def test_condition_hashlock_serialization(): + from bigchaindb_common.transaction import Condition + from cryptoconditions import PreimageSha256Fulfillment + + secret = b'wow much secret' + hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri + + expected = { + 'condition': { + 'uri': hashlock, + }, + 'owners_after': None, + } + cond = Condition(hashlock) + + assert cond.to_dict() == expected + + +def test_condition_hashlock_deserialization(): + from bigchaindb_common.transaction import Condition + from cryptoconditions import PreimageSha256Fulfillment + + secret = b'wow much secret' + hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri + expected = Condition(hashlock) + + cond = { + 'condition': { + 'uri': hashlock + }, + 'owners_after': None, + } + cond = Condition.from_dict(cond) + + assert cond == expected + + def test_invalid_condition_initialization(cond_uri, user_pub): from bigchaindb_common.transaction import Condition @@ -590,7 +627,7 @@ def test_create_create_transaction_hashlock(user_pub): from cryptoconditions import PreimageSha256Fulfillment secret = b'much secret, wow' - hashlock = PreimageSha256Fulfillment(preimage=secret) + hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri cond = Condition(hashlock) expected = { From d4864f89adcb24e77dc661b085527f0671cd28b8 Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 26 Aug 2016 15:43:06 +0200 Subject: [PATCH 76/98] Transaction.transfer add single input and outputs --- test_transaction.py | 57 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 2aacf391..62d4b3f5 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -506,7 +506,6 @@ def test_create_create_transaction_single_io(user_cond, user_pub): } tx = Transaction.create([user_pub], [user_pub], {'message': 'hello'}).to_dict() - # TODO: Fix this with monkeypatching tx.pop('id') tx['transaction']['data'].pop('uuid') tx['transaction'].pop('timestamp') @@ -558,7 +557,6 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, } tx = Transaction.create([user_pub, user2_pub], [user_pub, user2_pub], {'message': 'hello'}).to_dict() - # TODO: Fix this with monkeypatching tx.pop('id') tx['transaction']['data'].pop('uuid') tx['transaction'].pop('timestamp') @@ -605,7 +603,6 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, } tx = Transaction.create([user_pub], [user_pub, user2_pub], {'message': 'hello'}).to_dict() - # TODO: Fix this with monkeypatching tx.pop('id') tx['transaction']['data'].pop('uuid') tx['transaction'].pop('timestamp') @@ -655,7 +652,6 @@ def test_create_create_transaction_hashlock(user_pub): tx = Transaction.create([user_pub], [], {'message': 'hello'}, secret).to_dict() - # TODO: Fix this with monkeypatching tx.pop('id') tx['transaction']['data'].pop('uuid') tx['transaction'].pop('timestamp') @@ -670,3 +666,56 @@ def test_validate_hashlock_create_transaction(user_pub, user_priv): b'much secret, wow') tx = tx.sign([user_priv]) assert tx.fulfillments_valid() is True + + +def test_conditions_to_inputs(tx): + ffills = tx.to_spendable_fulfillments([0]) + assert len(ffills) == 1 + ffill= ffills.pop() + assert ffill.fulfillment == tx.conditions[0].fulfillment + assert ffill.owners_before == tx.conditions[0].owners_after + assert ffill.tx_input.txid == tx.id + assert ffill.tx_input.cid == 0 + + +def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, + user2_cond, user_priv): + from copy import deepcopy + from bigchaindb_common.transaction import Transaction + from bigchaindb_common.util import serialize + from bigchaindb_common.crypto import SigningKey + + expected = { + 'transaction': { + 'conditions': [user2_cond.to_dict(0)], + 'data': None, + 'fulfillments': [ + { + 'owners_before': [ + user_pub + ], + 'fid': 0, + 'fulfillment': None, + 'input': { + 'txid': tx.id, + 'cid': 0 + } + } + ], + 'operation': 'TRANSFER', + }, + 'version': 1 + } + inputs = tx.to_spendable_fulfillments([0]) + transfer_tx = Transaction.transfer(inputs, [user2_pub], None) + transfer_tx = transfer_tx.sign([user_priv]) + transfer_tx = transfer_tx.to_dict() + + expected_input = deepcopy(inputs[0]) + expected['id'] = transfer_tx['id'] + expected['transaction']['timestamp'] = transfer_tx['transaction']['timestamp'] + expected_input.fulfillment.sign(serialize(expected), SigningKey(user_priv)) + expected_ffill = expected_input.fulfillment.serialize_uri() + transfer_ffill = transfer_tx['transaction']['fulfillments'][0]['fulfillment'] + assert transfer_ffill == expected_ffill + assert Transaction.from_dict(transfer_tx).fulfillments_valid([tx.conditions[0]]) is True From 706c8923c8aa018b82a90038f79c2ca1fe41236c Mon Sep 17 00:00:00 2001 From: tim Date: Mon, 29 Aug 2016 14:45:55 +0200 Subject: [PATCH 77/98] Small adjustments to transfer-tx interface --- test_transaction.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 62d4b3f5..6ac1813c 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -669,7 +669,7 @@ def test_validate_hashlock_create_transaction(user_pub, user_priv): def test_conditions_to_inputs(tx): - ffills = tx.to_spendable_fulfillments([0]) + ffills = tx.to_inputs([0]) assert len(ffills) == 1 ffill= ffills.pop() assert ffill.fulfillment == tx.conditions[0].fulfillment @@ -706,8 +706,8 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, }, 'version': 1 } - inputs = tx.to_spendable_fulfillments([0]) - transfer_tx = Transaction.transfer(inputs, [user2_pub], None) + inputs = tx.to_inputs([0]) + transfer_tx = Transaction.transfer(inputs, [user2_pub]) transfer_tx = transfer_tx.sign([user_priv]) transfer_tx = transfer_tx.to_dict() From 22ac574caee8fecb9fd505e14eb8f443b0e82341 Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 2 Sep 2016 13:55:54 +0200 Subject: [PATCH 78/98] Create transfer-tx interface --- test_transaction.py | 197 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 194 insertions(+), 3 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 6ac1813c..960dde20 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -1,4 +1,4 @@ -from pytest import raises +from pytest import raises, mark def test_fulfillment_serialization(ffill_uri, user_pub): @@ -120,6 +120,122 @@ def test_invalid_condition_initialization(cond_uri, user_pub): Condition(cond_uri, user_pub) +def test_generate_conditions_split_half_recursive(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=2) + expected.add_subfulfillment(expected_simple1) + expected_threshold = ThresholdSha256Fulfillment(threshold=2) + 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]]) + assert cond.fulfillment.to_dict() == expected.to_dict() + + +def test_generate_conditions_split_half_single_owner(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=2) + expected_threshold = ThresholdSha256Fulfillment(threshold=2) + expected_threshold.add_subfulfillment(expected_simple2) + expected_threshold.add_subfulfillment(expected_simple3) + expected.add_subfulfillment(expected_threshold) + expected.add_subfulfillment(expected_simple1) + + cond = Condition.generate([[expected_simple2, user3_pub], user_pub]) + assert cond.fulfillment.to_dict() == expected.to_dict() + + +def test_generate_conditions_flat_ownage(user_pub, user2_pub, user3_pub): + from bigchaindb_common.transaction import Condition + 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=3) + expected.add_subfulfillment(expected_simple1) + expected.add_subfulfillment(expected_simple2) + expected.add_subfulfillment(expected_simple3) + + cond = Condition.generate([user_pub, user2_pub, expected_simple3]) + assert cond.fulfillment.to_dict() == expected.to_dict() + + +def test_generate_conditions_single_owner(user_pub): + from bigchaindb_common.transaction import Condition + from cryptoconditions import Ed25519Fulfillment + + expected = Ed25519Fulfillment(public_key=user_pub) + cond = Condition.generate([user_pub]) + + assert cond.fulfillment.to_dict() == expected.to_dict() + + +def test_generate_conditions_single_owner_with_condition(user_pub): + from bigchaindb_common.transaction import Condition + from cryptoconditions import Ed25519Fulfillment + + expected = Ed25519Fulfillment(public_key=user_pub) + cond = Condition.generate([expected]) + + 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([]) + with raises(TypeError): + Condition.generate() + with raises(ValueError): + Condition.generate([[user_pub, [user2_pub, [user3_pub]]]]) + with raises(ValueError): + Condition.generate([[user_pub]]) + + def test_transaction_serialization(user_ffill, user_cond): from bigchaindb_common.transaction import Transaction @@ -521,6 +637,7 @@ def test_validate_single_io_create_transaction(user_pub, user_priv): assert tx.fulfillments_valid() is True +@mark.skip(reason='Multiple inputs and outputs in CREATE not supported') def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, user2_pub): from bigchaindb_common.transaction import Transaction @@ -564,6 +681,7 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, assert tx == expected +@mark.skip(reason='Multiple inputs and outputs in CREATE not supported') def test_validate_multiple_io_create_transaction(user_pub, user_priv, user2_pub, user2_priv): from bigchaindb_common.transaction import Transaction @@ -671,7 +789,7 @@ def test_validate_hashlock_create_transaction(user_pub, user_priv): def test_conditions_to_inputs(tx): ffills = tx.to_inputs([0]) assert len(ffills) == 1 - ffill= ffills.pop() + ffill = ffills.pop() assert ffill.fulfillment == tx.conditions[0].fulfillment assert ffill.owners_before == tx.conditions[0].owners_after assert ffill.tx_input.txid == tx.id @@ -681,9 +799,9 @@ def test_conditions_to_inputs(tx): def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, user2_cond, user_priv): from copy import deepcopy + from bigchaindb_common.crypto import SigningKey from bigchaindb_common.transaction import Transaction from bigchaindb_common.util import serialize - from bigchaindb_common.crypto import SigningKey expected = { 'transaction': { @@ -719,3 +837,76 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, transfer_ffill = transfer_tx['transaction']['fulfillments'][0]['fulfillment'] assert transfer_ffill == expected_ffill assert Transaction.from_dict(transfer_tx).fulfillments_valid([tx.conditions[0]]) is True + + +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 + + 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]) + + expected = { + 'transaction': { + 'conditions': [user2_cond.to_dict(0), user2_cond.to_dict(1)], + 'data': None, + 'fulfillments': [ + { + 'owners_before': [ + user_pub + ], + 'fid': 0, + 'fulfillment': None, + 'input': { + 'txid': tx1.id, + 'cid': 0 + } + }, { + 'owners_before': [ + user2_pub + ], + 'fid': 1, + 'fulfillment': None, + 'input': { + 'txid': tx2.id, + 'cid': 0 + } + } + ], + 'operation': 'TRANSFER', + }, + '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 = transfer_tx.sign([user_priv, user2_priv]) + transfer_tx = transfer_tx + + assert len(transfer_tx.fulfillments) == 2 + assert len(transfer_tx.conditions) == 2 + assert transfer_tx.fulfillments_valid(tx1.conditions + tx2.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') + assert expected == transfer_tx + + +def test_create_transfer_with_invalid_parameters(): + from bigchaindb_common.transaction import Transaction + + with raises(TypeError): + Transaction.transfer({}, []) + with raises(ValueError): + Transaction.transfer([], []) + with raises(TypeError): + Transaction.transfer(['fulfillment'], {}) + with raises(ValueError): + Transaction.transfer(['fulfillment'], []) From 421b817683c5da3fb2b10f4231cc7fef216905d7 Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 2 Sep 2016 14:51:33 +0200 Subject: [PATCH 79/98] Increase test coverage --- test_transaction.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/test_transaction.py b/test_transaction.py index 960dde20..cccaf66a 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -229,7 +229,7 @@ def test_generate_conditions_invalid_parameters(user_pub, user2_pub, with raises(ValueError): Condition.generate([]) with raises(TypeError): - Condition.generate() + Condition.generate('not a list') with raises(ValueError): Condition.generate([[user_pub, [user2_pub, [user3_pub]]]]) with raises(ValueError): @@ -399,6 +399,16 @@ def test_transaction_link_deserialization_with_empty_payload(): assert tx_link == expected +def test_cast_transaction_link_to_boolean(): + from bigchaindb_common.transaction import TransactionLink + + assert bool(TransactionLink()) is False + assert bool(TransactionLink('a', None)) is False + assert bool(TransactionLink(None, 'b')) is False + assert bool(TransactionLink('a', 'b')) is True + assert bool(TransactionLink(False, False)) is True + + def test_add_fulfillment_to_tx(user_ffill): from bigchaindb_common.transaction import Transaction @@ -786,6 +796,23 @@ def test_validate_hashlock_create_transaction(user_pub, user_priv): assert tx.fulfillments_valid() is True +def test_create_create_transaction_with_invalid_parameters(): + 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) + with raises(ValueError): + Transaction.create([], [], secret='wow, much secret') + + def test_conditions_to_inputs(tx): ffills = tx.to_inputs([0]) assert len(ffills) == 1 From b653c1f8d2621501172e082acc0304b420e5325a Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 14 Sep 2016 13:46:17 +0200 Subject: [PATCH 80/98] Adjust fulfillment (de)serialization --- test_transaction.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index cccaf66a..dc3498ab 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -29,16 +29,29 @@ def test_fulfillment_deserialization_with_uri(ffill_uri, user_pub): assert ffill == expected -def test_fulfillment_deserialization_with_dict(user_pub): +def test_fulfillment_deserialization_with_invalid_fulfillment(user_pub): from bigchaindb_common.transaction import Fulfillment - expected = Fulfillment(None, [user_pub]) - ffill = { 'owners_before': [user_pub], 'fulfillment': None, 'input': None, } + with raises(TypeError): + Fulfillment.from_dict(ffill) + + +def test_fulfillment_deserialization_with_unsigned_fulfillment(ffill_uri, + user_pub): + from bigchaindb_common.transaction import Fulfillment + from cryptoconditions import Fulfillment as CCFulfillment + + expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) + ffill = { + 'owners_before': [user_pub], + 'fulfillment': CCFulfillment.from_uri(ffill_uri), + 'input': None, + } ffill = Fulfillment.from_dict(ffill) assert ffill == expected @@ -631,10 +644,12 @@ def test_create_create_transaction_single_io(user_cond, user_pub): 'version': 1 } - tx = Transaction.create([user_pub], [user_pub], {'message': 'hello'}).to_dict() + payload = {'message': 'hello'} + tx = Transaction.create([user_pub], [user_pub], payload).to_dict() tx.pop('id') tx['transaction']['data'].pop('uuid') tx['transaction'].pop('timestamp') + tx['transaction']['fulfillments'][0]['fulfillment'] = None assert tx == expected @@ -734,6 +749,7 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, tx.pop('id') tx['transaction']['data'].pop('uuid') tx['transaction'].pop('timestamp') + tx['transaction']['fulfillments'][0]['fulfillment'] = None assert tx == expected @@ -783,6 +799,7 @@ def test_create_create_transaction_hashlock(user_pub): tx.pop('id') tx['transaction']['data'].pop('uuid') tx['transaction'].pop('timestamp') + tx['transaction']['fulfillments'][0]['fulfillment'] = None assert tx == expected From 4eb6a8ee276a0929eeb5f701371fd0a7244663bb Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 16 Sep 2016 11:12:55 +0200 Subject: [PATCH 81/98] Catch CC Error for Fulfillment --- test_transaction.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test_transaction.py b/test_transaction.py index dc3498ab..fe1c342c 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -41,6 +41,19 @@ def test_fulfillment_deserialization_with_invalid_fulfillment(user_pub): Fulfillment.from_dict(ffill) +def test_fulfillment_deserialization_with_invalid_fulfillment_uri(user_pub): + from bigchaindb_common.exceptions import InvalidSignature + from bigchaindb_common.transaction import Fulfillment + + ffill = { + 'owners_before': [user_pub], + 'fulfillment': 'an invalid fulfillment', + 'input': None, + } + with raises(InvalidSignature): + Fulfillment.from_dict(ffill) + + def test_fulfillment_deserialization_with_unsigned_fulfillment(ffill_uri, user_pub): from bigchaindb_common.transaction import Fulfillment From b41b2981394c0416b836b98f3f817e2923b7181a Mon Sep 17 00:00:00 2001 From: tim Date: Tue, 20 Sep 2016 18:31:38 +0200 Subject: [PATCH 82/98] Allow custom thresholds --- test_transaction.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test_transaction.py b/test_transaction.py index fe1c342c..7b1de579 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -166,6 +166,27 @@ def test_generate_conditions_split_half_recursive(user_pub, user2_pub, 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)) + assert cond.fulfillment.to_dict() == expected.to_dict() + + def test_generate_conditions_split_half_single_owner(user_pub, user2_pub, user3_pub): from bigchaindb_common.transaction import Condition From 529cf99bc248b269b339922f73823dc77c8adeec Mon Sep 17 00:00:00 2001 From: tim Date: Tue, 11 Oct 2016 16:24:39 +0200 Subject: [PATCH 83/98] Make tests comply to 79 chars per line --- test_transaction.py | 101 +++++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 38 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 7b1de579..fa61ab20 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -183,7 +183,8 @@ def test_generate_conditions_split_half_recursive_custom_threshold(user_pub, 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)], + 1)) assert cond.fulfillment.to_dict() == expected.to_dict() @@ -293,7 +294,8 @@ def test_transaction_serialization(user_ffill, user_cond): 'id': tx_id, 'version': Transaction.VERSION, 'transaction': { - # NOTE: This test assumes that Fulfillments and Conditions can successfully be serialized + # NOTE: This test assumes that Fulfillments and Conditions can + # successfully be serialized 'fulfillments': [user_ffill.to_dict(0)], 'conditions': [user_cond.to_dict(0)], 'operation': Transaction.CREATE, @@ -302,7 +304,8 @@ def test_transaction_serialization(user_ffill, user_cond): } } - tx_dict = Transaction(Transaction.CREATE, [user_ffill], [user_cond]).to_dict() + tx = Transaction(Transaction.CREATE, [user_ffill], [user_cond]) + tx_dict = tx.to_dict() tx_dict['id'] = tx_id tx_dict['transaction']['timestamp'] = timestamp @@ -314,13 +317,14 @@ def test_transaction_deserialization(user_ffill, user_cond): timestamp = '66666666666' - expected = Transaction(Transaction.CREATE, [user_ffill], [user_cond], None, timestamp, - Transaction.VERSION) + expected = Transaction(Transaction.CREATE, [user_ffill], [user_cond], None, + timestamp, Transaction.VERSION) tx = { 'version': Transaction.VERSION, 'transaction': { - # NOTE: This test assumes that Fulfillments and Conditions can successfully be serialized + # NOTE: This test assumes that Fulfillments and Conditions can + # successfully be serialized 'fulfillments': [user_ffill.to_dict()], 'conditions': [user_cond.to_dict()], 'operation': Transaction.CREATE, @@ -328,7 +332,8 @@ def test_transaction_deserialization(user_ffill, user_cond): 'data': None, } } - tx['id'] = Transaction._to_hash(Transaction._to_str(Transaction._remove_signatures(tx))) + tx_no_signatures = Transaction._remove_signatures(tx) + tx['id'] = Transaction._to_hash(Transaction._to_str(tx_no_signatures)) tx = Transaction.from_dict(tx) assert tx == expected @@ -507,24 +512,25 @@ def test_validate_tx_simple_create_signature(user_ffill, user_cond, user_priv): expected.fulfillment.sign(str(tx), SigningKey(user_priv)) tx.sign([user_priv]) - assert tx.fulfillments[0].to_dict()['fulfillment'] == expected.fulfillment.serialize_uri() + expected_uri = expected.fulfillment.serialize_uri() + assert tx.fulfillments[0].to_dict()['fulfillment'] == expected_uri assert tx.fulfillments_valid() is True -def test_invoke_simple_signature_fulfillment_with_invalid_parameters(utx, user_ffill): +def test_invoke_simple_signature_fulfillment_with_invalid_params(utx, + user_ffill): from bigchaindb_common.exceptions import KeypairMismatchException with raises(KeypairMismatchException): + invalid_key_pair = {'wrong_pub_key': 'wrong_priv_key'} utx._sign_simple_signature_fulfillment(user_ffill, 0, 'somemessage', - {'wrong_pub_key': 'wrong_priv_key'}) + invalid_key_pair) -def test_invoke_threshold_signature_fulfillment_with_invalid_parameters(utx, - user_user2_threshold_ffill, - user3_pub, - user3_priv): +def test_sign_threshold_with_invalid_params(utx, user_user2_threshold_ffill, + user3_pub, user3_priv): from bigchaindb_common.exceptions import KeypairMismatchException with raises(KeypairMismatchException): @@ -544,10 +550,12 @@ def test_validate_fulfillment_with_invalid_parameters(utx): from bigchaindb_common.transaction import Transaction input_conditions = [cond.fulfillment.condition_uri for cond in utx.conditions] - tx_serialized = Transaction._to_str(Transaction._remove_signatures(utx.to_dict())) + tx_dict = utx.to_dict() + tx_dict = Transaction._remove_signatures(tx_dict) + tx_serialized = Transaction._to_str(tx_dict) assert utx._fulfillment_valid(utx.fulfillments[0], tx_serialized, - input_conditions) == False + input_conditions) is False def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv): @@ -567,12 +575,16 @@ def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv): expected_second.fulfillments = [expected_second.fulfillments[1]] expected_second.conditions = [expected_second.conditions[1]] - expected_first.fulfillments[0].fulfillment.sign(str(expected_first), SigningKey(user_priv)) - expected_second.fulfillments[0].fulfillment.sign(str(expected_second), SigningKey(user_priv)) + expected_first.fulfillments[0].fulfillment.sign(str(expected_first), + SigningKey(user_priv)) + expected_second.fulfillments[0].fulfillment.sign(str(expected_second), + SigningKey(user_priv)) tx.sign([user_priv]) - assert tx.fulfillments[0].to_dict()['fulfillment'] == expected_first.fulfillments[0].fulfillment.serialize_uri() - assert tx.fulfillments[1].to_dict()['fulfillment'] == expected_second.fulfillments[0].fulfillment.serialize_uri() + assert tx.fulfillments[0].to_dict()['fulfillment'] == \ + expected_first.fulfillments[0].fulfillment.serialize_uri() + assert tx.fulfillments[1].to_dict()['fulfillment'] == \ + expected_second.fulfillments[0].fulfillment.serialize_uri() assert tx.fulfillments_valid() is True @@ -587,13 +599,17 @@ def test_validate_tx_threshold_create_signature(user_user2_threshold_ffill, from bigchaindb_common.crypto import SigningKey from bigchaindb_common.transaction import Transaction - tx = Transaction(Transaction.CREATE, [user_user2_threshold_ffill], [user_user2_threshold_cond]) + tx = Transaction(Transaction.CREATE, [user_user2_threshold_ffill], + [user_user2_threshold_cond]) expected = deepcopy(user_user2_threshold_cond) - expected.fulfillment.subconditions[0]['body'].sign(str(tx), SigningKey(user_priv)) - expected.fulfillment.subconditions[1]['body'].sign(str(tx), SigningKey(user2_priv)) + expected.fulfillment.subconditions[0]['body'].sign(str(tx), + SigningKey(user_priv)) + expected.fulfillment.subconditions[1]['body'].sign(str(tx), + SigningKey(user2_priv)) tx.sign([user_priv, user2_priv]) - assert tx.fulfillments[0].to_dict()['fulfillment'] == expected.fulfillment.serialize_uri() + assert tx.fulfillments[0].to_dict()['fulfillment'] == \ + expected.fulfillment.serialize_uri() assert tx.fulfillments_valid() is True @@ -614,25 +630,26 @@ def test_multiple_fulfillment_validation_of_transfer_tx(user_ffill, user_cond, fulfillments = [Fulfillment(cond.fulfillment, cond.owners_after, TransactionLink(tx.id, index)) for index, cond in enumerate(tx.conditions)] - conditions = [Condition(Ed25519Fulfillment(public_key=user3_pub), [user3_pub]), - Condition(Ed25519Fulfillment(public_key=user3_pub), [user3_pub])] + conditions = [Condition(Ed25519Fulfillment(public_key=user3_pub), + [user3_pub]), + Condition(Ed25519Fulfillment(public_key=user3_pub), + [user3_pub])] transfer_tx = Transaction('TRANSFER', fulfillments, conditions) - transfer_tx = transfer_tx.sign([user_priv]) assert transfer_tx.fulfillments_valid(tx.conditions) is True -def test_validate_fulfillments_of_transfer_tx_with_invalid_parameters(transfer_tx, - cond_uri, - utx, - user2_pub, - user_priv): +def test_validate_fulfillments_of_transfer_tx_with_invalid_params(transfer_tx, + cond_uri, + utx, + user2_pub, + user_priv): from bigchaindb_common.transaction import Condition from cryptoconditions import Ed25519Fulfillment - assert transfer_tx.fulfillments_valid([Condition(Ed25519Fulfillment.from_uri('cf:0:'), - ['invalid'])]) is False + invalid_cond = Condition(Ed25519Fulfillment.from_uri('cf:0:'), ['invalid']) + assert transfer_tx.fulfillments_valid([invalid_cond]) is False invalid_cond = utx.conditions[0] invalid_cond.owners_after = 'invalid' assert transfer_tx.fulfillments_valid([invalid_cond]) is True @@ -906,15 +923,19 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, transfer_tx = Transaction.transfer(inputs, [user2_pub]) transfer_tx = transfer_tx.sign([user_priv]) transfer_tx = transfer_tx.to_dict() + transfer_tx_body = transfer_tx['transaction'] expected_input = deepcopy(inputs[0]) expected['id'] = transfer_tx['id'] - expected['transaction']['timestamp'] = transfer_tx['transaction']['timestamp'] + expected['transaction']['timestamp'] = transfer_tx_body['timestamp'] expected_input.fulfillment.sign(serialize(expected), SigningKey(user_priv)) expected_ffill = expected_input.fulfillment.serialize_uri() - transfer_ffill = transfer_tx['transaction']['fulfillments'][0]['fulfillment'] + transfer_ffill = transfer_tx_body['fulfillments'][0]['fulfillment'] + assert transfer_ffill == expected_ffill - assert Transaction.from_dict(transfer_tx).fulfillments_valid([tx.conditions[0]]) is True + + transfer_tx = Transaction.from_dict(transfer_tx) + assert transfer_tx.fulfillments_valid([tx.conditions[0]]) is True def test_create_transfer_transaction_multiple_io(user_pub, user_priv, @@ -968,12 +989,16 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, assert len(transfer_tx.fulfillments) == 2 assert len(transfer_tx.conditions) == 2 - assert transfer_tx.fulfillments_valid(tx1.conditions + tx2.conditions) is True + + combined_conditions = tx1.conditions + tx2.conditions + assert transfer_tx.fulfillments_valid(combined_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') + assert expected == transfer_tx From e348e5e4f542144b0db8b86a257cd6e63176d624 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 28 Sep 2016 10:24:55 +0200 Subject: [PATCH 84/98] Rename Data to Metadata --- test_transaction.py | 72 ++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index fa61ab20..bd5fb285 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -300,7 +300,7 @@ def test_transaction_serialization(user_ffill, user_cond): 'conditions': [user_cond.to_dict(0)], 'operation': Transaction.CREATE, 'timestamp': timestamp, - 'data': None, + 'metadata': None, } } @@ -329,7 +329,7 @@ def test_transaction_deserialization(user_ffill, user_cond): 'conditions': [user_cond.to_dict()], 'operation': Transaction.CREATE, 'timestamp': timestamp, - 'data': None, + 'metadata': None, } } tx_no_signatures = Transaction._remove_signatures(tx) @@ -358,13 +358,13 @@ def test_tx_serialization_with_incorrect_hash(utx): def test_invalid_tx_initialization(): from bigchaindb_common.transaction import Transaction - wrong_data_type = {'payload': 'a totally wrong datatype'} + wrong_metadata_type = {'data': 'a totally wrong metadatatype'} with raises(TypeError): - Transaction(Transaction.CREATE, wrong_data_type) + Transaction(Transaction.CREATE, wrong_metadata_type) with raises(TypeError): - Transaction(Transaction.CREATE, [], wrong_data_type) + Transaction(Transaction.CREATE, [], wrong_metadata_type) with raises(TypeError): - Transaction(Transaction.CREATE, [], [], wrong_data_type) + Transaction(Transaction.CREATE, [], [], wrong_metadata_type) with raises(TypeError): Transaction('RANSFER', [], []) @@ -378,32 +378,32 @@ def test_invalid_fulfillment_initialization(user_ffill, user_pub): Fulfillment(user_ffill, [], tx_input='somethingthatiswrong') -def test_invalid_data_initialization(): - from bigchaindb_common.transaction import Data +def test_invalid_metadata_initialization(): + from bigchaindb_common.transaction import Metadata with raises(TypeError): - Data([]) + Metadata([]) -def test_data_serialization(payload, payload_id): - from bigchaindb_common.transaction import Data +def test_metadata_serialization(data, data_id): + from bigchaindb_common.transaction import Metadata expected = { - 'payload': payload, - 'uuid': payload_id + 'data': data, + 'id': data_id, } - data = Data(payload, payload_id) + metadata = Metadata(data, data_id) - assert data.to_dict() == expected + assert metadata.to_dict() == expected -def test_data_deserialization(payload, payload_id): - from bigchaindb_common.transaction import Data +def test_metadata_deserialization(data, data_id): + from bigchaindb_common.transaction import Metadata - expected = Data(payload, payload_id) - data = Data.from_dict({'payload': payload, 'uuid': payload_id}) + expected = Metadata(data, data_id) + metadata = Metadata.from_dict({'data': data, 'id': data_id}) - assert data == expected + assert metadata == expected def test_transaction_link_serialization(): @@ -675,8 +675,8 @@ def test_create_create_transaction_single_io(user_cond, user_pub): expected = { 'transaction': { 'conditions': [user_cond.to_dict(0)], - 'data': { - 'payload': { + 'metadata': { + 'data': { 'message': 'hello' } }, @@ -695,10 +695,10 @@ def test_create_create_transaction_single_io(user_cond, user_pub): 'version': 1 } - payload = {'message': 'hello'} - tx = Transaction.create([user_pub], [user_pub], payload).to_dict() + data = {'message': 'hello'} + tx = Transaction.create([user_pub], [user_pub], data).to_dict() tx.pop('id') - tx['transaction']['data'].pop('uuid') + tx['transaction']['metadata'].pop('id') tx['transaction'].pop('timestamp') tx['transaction']['fulfillments'][0]['fulfillment'] = None @@ -721,8 +721,8 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, expected = { 'transaction': { 'conditions': [user_cond.to_dict(0), user2_cond.to_dict(1)], - 'data': { - 'payload': { + 'metadata': { + 'data': { 'message': 'hello' } }, @@ -751,7 +751,7 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, tx = Transaction.create([user_pub, user2_pub], [user_pub, user2_pub], {'message': 'hello'}).to_dict() tx.pop('id') - tx['transaction']['data'].pop('uuid') + tx['transaction']['metadata'].pop('id') tx['transaction'].pop('timestamp') assert tx == expected @@ -776,8 +776,8 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, expected = { 'transaction': { 'conditions': [user_user2_threshold_cond.to_dict(0)], - 'data': { - 'payload': { + 'metadata': { + 'data': { 'message': 'hello' } }, @@ -798,7 +798,7 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, tx = Transaction.create([user_pub], [user_pub, user2_pub], {'message': 'hello'}).to_dict() tx.pop('id') - tx['transaction']['data'].pop('uuid') + tx['transaction']['metadata'].pop('id') tx['transaction'].pop('timestamp') tx['transaction']['fulfillments'][0]['fulfillment'] = None @@ -825,8 +825,8 @@ def test_create_create_transaction_hashlock(user_pub): expected = { 'transaction': { 'conditions': [cond.to_dict(0)], - 'data': { - 'payload': { + 'metadata': { + 'data': { 'message': 'hello' } }, @@ -848,7 +848,7 @@ def test_create_create_transaction_hashlock(user_pub): tx = Transaction.create([user_pub], [], {'message': 'hello'}, secret).to_dict() tx.pop('id') - tx['transaction']['data'].pop('uuid') + tx['transaction']['metadata'].pop('id') tx['transaction'].pop('timestamp') tx['transaction']['fulfillments'][0]['fulfillment'] = None @@ -901,7 +901,7 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, expected = { 'transaction': { 'conditions': [user2_cond.to_dict(0)], - 'data': None, + 'metadata': None, 'fulfillments': [ { 'owners_before': [ @@ -951,7 +951,7 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, expected = { 'transaction': { 'conditions': [user2_cond.to_dict(0), user2_cond.to_dict(1)], - 'data': None, + 'metadata': None, 'fulfillments': [ { 'owners_before': [ From 34e7afbdf5332c9f120a32e0df2cda7b34712346 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 28 Sep 2016 12:03:54 +0200 Subject: [PATCH 85/98] Add basic Asset model --- test_transaction.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test_transaction.py b/test_transaction.py index bd5fb285..1dca7f63 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -378,6 +378,48 @@ def test_invalid_fulfillment_initialization(user_ffill, user_pub): Fulfillment(user_ffill, [], tx_input='somethingthatiswrong') +def test_asset_invalid_asset_initialization(): + from bigchaindb_common.transaction import Asset + + with raises(TypeError): + Asset(data='some wrong type') + + +def test_invalid_asset_comparison(data, data_id): + from bigchaindb_common.transaction import Asset + + assert Asset(data, data_id) != 'invalid comparison' + + +def test_asset_serialization(data, data_id): + from bigchaindb_common.transaction import Asset + + expected = { + 'id': data_id, + 'divisible': False, + 'updatable': False, + 'refillable': False, + 'data': data, + } + asset = Asset(data, data_id) + assert asset.to_dict() == expected + + +def test_asset_deserialization(data, data_id): + from bigchaindb_common.transaction import Asset + + asset_dict = { + 'id': data_id, + 'divisible': False, + 'updatable': False, + 'refillable': False, + 'data': data, + } + asset = Asset.from_dict(asset_dict) + expected = Asset(data, data_id) + assert asset == expected + + def test_invalid_metadata_initialization(): from bigchaindb_common.transaction import Metadata From 01337eb9222f5eef5f7797589258d84530efe0c9 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 28 Sep 2016 16:03:43 +0200 Subject: [PATCH 86/98] Add Asset into work-flow-functions --- test_transaction.py | 215 ++++++++++++++++++++++++++++++-------------- 1 file changed, 149 insertions(+), 66 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 1dca7f63..7cd9e15d 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -284,8 +284,38 @@ def test_generate_conditions_invalid_parameters(user_pub, user2_pub, Condition.generate([[user_pub]]) -def test_transaction_serialization(user_ffill, user_cond): - from bigchaindb_common.transaction import Transaction +def test_invalid_transaction_initialization(): + from bigchaindb_common.transaction import Transaction, Asset + + with raises(TypeError): + Transaction(operation='invalid operation') + with raises(TypeError): + Transaction(operation='CREATE', asset='invalid asset') + with raises(TypeError): + Transaction( + operation='CREATE', + asset=Asset(), + conditions='invalid conditions' + ) + with raises(TypeError): + Transaction( + operation='CREATE', + asset=Asset(), + conditions=[], + fulfillments='invalid fulfillments' + ) + with raises(TypeError): + Transaction( + operation='CREATE', + asset=Asset(), + conditions=[], + fulfillments=[], + metadata='invalid metadata' + ) + + +def test_transaction_serialization(user_ffill, user_cond, data, data_id): + from bigchaindb_common.transaction import Transaction, Asset tx_id = 'l0l' timestamp = '66666666666' @@ -301,24 +331,34 @@ def test_transaction_serialization(user_ffill, user_cond): 'operation': Transaction.CREATE, 'timestamp': timestamp, 'metadata': None, + 'asset': { + 'id': data_id, + 'divisible': False, + 'updatable': False, + 'refillable': False, + 'data': data, + } } } - tx = Transaction(Transaction.CREATE, [user_ffill], [user_cond]) + tx = Transaction(Transaction.CREATE, Asset(data, data_id), [user_ffill], + [user_cond]) tx_dict = tx.to_dict() tx_dict['id'] = tx_id + tx_dict['transaction']['asset']['id'] = data_id tx_dict['transaction']['timestamp'] = timestamp assert tx_dict == expected -def test_transaction_deserialization(user_ffill, user_cond): - from bigchaindb_common.transaction import Transaction +def test_transaction_deserialization(user_ffill, user_cond, data, data_id): + from bigchaindb_common.transaction import Transaction, Asset timestamp = '66666666666' - expected = Transaction(Transaction.CREATE, [user_ffill], [user_cond], None, - timestamp, Transaction.VERSION) + expected_asset = Asset(data, data_id) + expected = Transaction(Transaction.CREATE, expected_asset, [user_ffill], + [user_cond], None, timestamp, Transaction.VERSION) tx = { 'version': Transaction.VERSION, @@ -330,6 +370,13 @@ def test_transaction_deserialization(user_ffill, user_cond): 'operation': Transaction.CREATE, 'timestamp': timestamp, 'metadata': None, + 'asset': { + 'id': data_id, + 'divisible': False, + 'updatable': False, + 'refillable': False, + 'data': data, + } } } tx_no_signatures = Transaction._remove_signatures(tx) @@ -378,6 +425,17 @@ def test_invalid_fulfillment_initialization(user_ffill, user_pub): Fulfillment(user_ffill, [], tx_input='somethingthatiswrong') +def test_asset_default_values(): + from bigchaindb_common.transaction import Asset + + asset = Asset() + assert asset.data is None + assert asset.data_id + assert asset.divisible is False + assert asset.updatable is False + assert asset.refillable is False + + def test_asset_invalid_asset_initialization(): from bigchaindb_common.transaction import Asset @@ -504,35 +562,35 @@ def test_cast_transaction_link_to_boolean(): def test_add_fulfillment_to_tx(user_ffill): - from bigchaindb_common.transaction import Transaction + from bigchaindb_common.transaction import Transaction, Asset - tx = Transaction(Transaction.CREATE, [], []) + tx = Transaction(Transaction.CREATE, Asset(), [], []) tx.add_fulfillment(user_ffill) assert len(tx.fulfillments) == 1 def test_add_fulfillment_to_tx_with_invalid_parameters(): - from bigchaindb_common.transaction import Transaction + from bigchaindb_common.transaction import Transaction, Asset - tx = Transaction(Transaction.CREATE) + tx = Transaction(Transaction.CREATE, Asset()) with raises(TypeError): tx.add_fulfillment('somewronginput') def test_add_condition_to_tx(user_cond): - from bigchaindb_common.transaction import Transaction + from bigchaindb_common.transaction import Transaction, Asset - tx = Transaction(Transaction.CREATE) + tx = Transaction(Transaction.CREATE, Asset()) tx.add_condition(user_cond) assert len(tx.conditions) == 1 def test_add_condition_to_tx_with_invalid_parameters(): - from bigchaindb_common.transaction import Transaction + from bigchaindb_common.transaction import Transaction, Asset - tx = Transaction(Transaction.CREATE, [], []) + tx = Transaction(Transaction.CREATE, Asset(), [], []) with raises(TypeError): tx.add_condition('somewronginput') @@ -547,15 +605,15 @@ def test_sign_with_invalid_parameters(utx, user_priv): def test_validate_tx_simple_create_signature(user_ffill, user_cond, user_priv): from copy import deepcopy from bigchaindb_common.crypto import SigningKey - from bigchaindb_common.transaction import Transaction + from bigchaindb_common.transaction import Transaction, Asset - tx = Transaction(Transaction.CREATE, [user_ffill], [user_cond]) + tx = Transaction(Transaction.CREATE, Asset(), [user_ffill], [user_cond]) expected = deepcopy(user_cond) expected.fulfillment.sign(str(tx), SigningKey(user_priv)) tx.sign([user_priv]) - expected_uri = expected.fulfillment.serialize_uri() - assert tx.fulfillments[0].to_dict()['fulfillment'] == expected_uri + assert tx.fulfillments[0].to_dict()['fulfillment'] == \ + expected.fulfillment.serialize_uri() assert tx.fulfillments_valid() is True @@ -590,6 +648,7 @@ def test_sign_threshold_with_invalid_params(utx, user_user2_threshold_ffill, def test_validate_fulfillment_with_invalid_parameters(utx): from bigchaindb_common.transaction import Transaction + input_conditions = [cond.fulfillment.condition_uri for cond in utx.conditions] tx_dict = utx.to_dict() @@ -604,9 +663,9 @@ def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv): from copy import deepcopy from bigchaindb_common.crypto import SigningKey - from bigchaindb_common.transaction import Transaction + from bigchaindb_common.transaction import Transaction, Asset - tx = Transaction(Transaction.CREATE, + tx = Transaction(Transaction.CREATE, Asset(), [user_ffill, deepcopy(user_ffill)], [user_ffill, deepcopy(user_cond)]) @@ -639,9 +698,9 @@ def test_validate_tx_threshold_create_signature(user_user2_threshold_ffill, from copy import deepcopy from bigchaindb_common.crypto import SigningKey - from bigchaindb_common.transaction import Transaction + from bigchaindb_common.transaction import Transaction, Asset - tx = Transaction(Transaction.CREATE, [user_user2_threshold_ffill], + tx = Transaction(Transaction.CREATE, Asset(), [user_user2_threshold_ffill], [user_user2_threshold_cond]) expected = deepcopy(user_user2_threshold_cond) expected.fulfillment.subconditions[0]['body'].sign(str(tx), @@ -661,10 +720,10 @@ def test_multiple_fulfillment_validation_of_transfer_tx(user_ffill, user_cond, user3_priv): from copy import deepcopy from bigchaindb_common.transaction import (Transaction, TransactionLink, - Fulfillment, Condition) + Fulfillment, Condition, Asset) from cryptoconditions import Ed25519Fulfillment - tx = Transaction(Transaction.CREATE, + tx = Transaction(Transaction.CREATE, Asset(), [user_ffill, deepcopy(user_ffill)], [user_cond, deepcopy(user_cond)]) tx.sign([user_priv]) @@ -676,7 +735,7 @@ def test_multiple_fulfillment_validation_of_transfer_tx(user_ffill, user_cond, [user3_pub]), Condition(Ed25519Fulfillment(public_key=user3_pub), [user3_pub])] - transfer_tx = Transaction('TRANSFER', fulfillments, conditions) + transfer_tx = Transaction('TRANSFER', tx.asset, fulfillments, conditions) transfer_tx = transfer_tx.sign([user_priv]) assert transfer_tx.fulfillments_valid(tx.conditions) is True @@ -711,16 +770,22 @@ def test_validate_fulfillments_of_transfer_tx_with_invalid_params(transfer_tx, tx.fulfillments_valid() -def test_create_create_transaction_single_io(user_cond, user_pub): - from bigchaindb_common.transaction import Transaction +def test_create_create_transaction_single_io(user_cond, user_pub, data, + data_id): + from bigchaindb_common.transaction import Transaction, Asset expected = { 'transaction': { 'conditions': [user_cond.to_dict(0)], 'metadata': { - 'data': { - 'message': 'hello' - } + 'data': data, + }, + 'asset': { + 'id': data_id, + 'divisible': False, + 'updatable': False, + 'refillable': False, + 'data': data, }, 'fulfillments': [ { @@ -737,8 +802,8 @@ def test_create_create_transaction_single_io(user_cond, user_pub): 'version': 1 } - data = {'message': 'hello'} - tx = Transaction.create([user_pub], [user_pub], data).to_dict() + asset = Asset(data, data_id) + tx = Transaction.create([user_pub], [user_pub], data, asset).to_dict() tx.pop('id') tx['transaction']['metadata'].pop('id') tx['transaction'].pop('timestamp') @@ -747,15 +812,16 @@ def test_create_create_transaction_single_io(user_cond, user_pub): assert tx == expected -def test_validate_single_io_create_transaction(user_pub, user_priv): - from bigchaindb_common.transaction import Transaction +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], {'message': 'hello'}) + tx = Transaction.create([user_pub], [user_pub], 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 @@ -800,6 +866,7 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, @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 @@ -812,16 +879,22 @@ def test_validate_multiple_io_create_transaction(user_pub, user_priv, def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, user_user2_threshold_cond, - user_user2_threshold_ffill): - from bigchaindb_common.transaction import Transaction + user_user2_threshold_ffill, data, + data_id): + from bigchaindb_common.transaction import Transaction, Asset expected = { 'transaction': { 'conditions': [user_user2_threshold_cond.to_dict(0)], 'metadata': { - 'data': { - 'message': 'hello' - } + 'data': data, + }, + 'asset': { + 'id': data_id, + 'divisible': False, + 'updatable': False, + 'refillable': False, + 'data': data, }, 'fulfillments': [ { @@ -837,28 +910,29 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, }, 'version': 1 } - tx = Transaction.create([user_pub], [user_pub, user2_pub], - {'message': 'hello'}).to_dict() - tx.pop('id') - tx['transaction']['metadata'].pop('id') - tx['transaction'].pop('timestamp') - tx['transaction']['fulfillments'][0]['fulfillment'] = None + asset = Asset(data, data_id) + tx = Transaction.create([user_pub], [user_pub, user2_pub], data, asset) + tx_dict = tx.to_dict() + tx_dict.pop('id') + tx_dict['transaction']['metadata'].pop('id') + tx_dict['transaction'].pop('timestamp') + tx_dict['transaction']['fulfillments'][0]['fulfillment'] = None - assert tx == expected + assert tx_dict == expected -def test_validate_threshold_create_transaction(user_pub, user_priv, user2_pub): - from bigchaindb_common.transaction import Transaction +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], - {'message': 'hello'}) + tx = Transaction.create([user_pub], [user_pub, user2_pub], data, Asset()) tx = tx.sign([user_priv]) assert tx.fulfillments_valid() is True -def test_create_create_transaction_hashlock(user_pub): - from bigchaindb_common.transaction import Transaction, Condition +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 @@ -868,9 +942,14 @@ def test_create_create_transaction_hashlock(user_pub): 'transaction': { 'conditions': [cond.to_dict(0)], 'metadata': { - 'data': { - 'message': 'hello' - } + 'data': data, + }, + 'asset': { + 'id': data_id, + 'divisible': False, + 'updatable': False, + 'refillable': False, + 'data': data, }, 'fulfillments': [ { @@ -887,8 +966,8 @@ def test_create_create_transaction_hashlock(user_pub): 'version': 1 } - tx = Transaction.create([user_pub], [], {'message': 'hello'}, - secret).to_dict() + 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') @@ -897,11 +976,10 @@ def test_create_create_transaction_hashlock(user_pub): assert tx == expected -def test_validate_hashlock_create_transaction(user_pub, user_priv): - from bigchaindb_common.transaction import Transaction +def test_validate_hashlock_create_transaction(user_pub, user_priv, data): + from bigchaindb_common.transaction import Transaction, Asset - tx = Transaction.create([user_pub], [], {'message': 'hello'}, - b'much secret, wow') + tx = Transaction.create([user_pub], [], data, Asset(), b'much secret, wow') tx = tx.sign([user_priv]) assert tx.fulfillments_valid() is True @@ -934,16 +1012,19 @@ def test_conditions_to_inputs(tx): def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, - user2_cond, user_priv): + user2_cond, user_priv, data_id): from copy import deepcopy from bigchaindb_common.crypto import SigningKey - from bigchaindb_common.transaction import Transaction + from bigchaindb_common.transaction import Transaction, Asset from bigchaindb_common.util import serialize expected = { 'transaction': { 'conditions': [user2_cond.to_dict(0)], 'metadata': None, + 'asset': { + 'id': data_id, + }, 'fulfillments': [ { 'owners_before': [ @@ -962,7 +1043,8 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, 'version': 1 } inputs = tx.to_inputs([0]) - transfer_tx = Transaction.transfer(inputs, [user2_pub]) + asset = Asset(None, data_id) + transfer_tx = Transaction.transfer(inputs, [user2_pub], asset=asset) transfer_tx = transfer_tx.sign([user_priv]) transfer_tx = transfer_tx.to_dict() transfer_tx_body = transfer_tx['transaction'] @@ -980,6 +1062,7 @@ 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 6256361bc6174fb3ab306008c9f7657cd534d17b Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 28 Sep 2016 16:20:36 +0200 Subject: [PATCH 87/98] Add Asset amount to condition --- test_transaction.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 7cd9e15d..5c60c1ca 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -79,9 +79,10 @@ def test_condition_serialization(user_Ed25519, user_pub): 'details': user_Ed25519.to_dict(), }, 'owners_after': [user_pub], + 'amount': 1, } - cond = Condition(user_Ed25519, [user_pub]) + cond = Condition(user_Ed25519, [user_pub], 1) assert cond.to_dict() == expected @@ -89,13 +90,14 @@ def test_condition_serialization(user_Ed25519, user_pub): def test_condition_deserialization(user_Ed25519, user_pub): from bigchaindb_common.transaction import Condition - expected = Condition(user_Ed25519, [user_pub]) + expected = Condition(user_Ed25519, [user_pub], 1) cond = { 'condition': { 'uri': user_Ed25519.condition_uri, 'details': user_Ed25519.to_dict() }, 'owners_after': [user_pub], + 'amount': 1, } cond = Condition.from_dict(cond) @@ -114,8 +116,9 @@ def test_condition_hashlock_serialization(): 'uri': hashlock, }, 'owners_after': None, + 'amount': 1, } - cond = Condition(hashlock) + cond = Condition(hashlock, amount=1) assert cond.to_dict() == expected @@ -126,13 +129,14 @@ def test_condition_hashlock_deserialization(): secret = b'wow much secret' hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri - expected = Condition(hashlock) + expected = Condition(hashlock, amount=1) cond = { 'condition': { 'uri': hashlock }, 'owners_after': None, + 'amount': 1, } cond = Condition.from_dict(cond) From d6759fa745365e1072bddc4177544872aa664f2e Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Fri, 7 Oct 2016 15:46:55 +0200 Subject: [PATCH 88/98] initial integration of asset --- test_asset.py | 253 ++++++++++++++++++++++++++++++++++++++++++++ test_transaction.py | 53 ---------- 2 files changed, 253 insertions(+), 53 deletions(-) create mode 100644 test_asset.py diff --git a/test_asset.py b/test_asset.py new file mode 100644 index 00000000..adc4df5a --- /dev/null +++ b/test_asset.py @@ -0,0 +1,253 @@ +from pytest import raises + +# TODO: Test asset amount + + +def test_asset_default_values(): + from bigchaindb_common.transaction import Asset + + asset = Asset() + assert asset.data is None + assert asset.data_id + assert asset.divisible is False + assert asset.updatable is False + assert asset.refillable is False + + +def test_asset_creation_with_data(data): + from bigchaindb_common.transaction import Asset + + asset = Asset(data) + assert asset.data == data + + +def test_asset_invalid_asset_initialization(): + from bigchaindb_common.transaction import Asset + + with raises(TypeError): + Asset(data='some wrong type') + + +def test_invalid_asset_comparison(data, data_id): + from bigchaindb_common.transaction import Asset + + assert Asset(data, data_id) != 'invalid comparison' + + +def test_asset_serialization(data, data_id): + from bigchaindb_common.transaction import Asset + + expected = { + 'id': data_id, + 'divisible': False, + 'updatable': False, + 'refillable': False, + 'data': data, + } + asset = Asset(data, data_id) + assert asset.to_dict() == expected + + +def test_asset_deserialization(data, data_id): + from bigchaindb_common.transaction import Asset + + asset_dict = { + 'id': data_id, + 'divisible': False, + 'updatable': False, + 'refillable': False, + 'data': data, + } + asset = Asset.from_dict(asset_dict) + expected = Asset(data, data_id) + assert asset == expected + + +""" +@pytest.mark.usefixtures('inputs') +def test_asset_transfer(b, user_vk, user_sk): + tx_input = b.get_owned_ids(user_vk).pop() + tx_create = b.get_transaction(tx_input['txid']) + tx_transfer = b.create_transaction(user_vk, user_vk, tx_input, 'TRANSFER') + tx_transfer_signed = b.sign_transaction(tx_transfer, user_sk) + + assert b.validate_transaction(tx_transfer_signed) == tx_transfer_signed + assert tx_transfer_signed['transaction']['asset']['id'] == tx_create['transaction']['asset']['id'] +""" + +""" +def test_validate_bad_asset_creation(b, user_vk): + from bigchaindb.util import get_hash_data + from bigchaindb_common.exceptions import AmountError + + tx = b.create_transaction(b.me, user_vk, None, 'CREATE') + tx['transaction']['asset'].update({'divisible': 1}) + 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_vk, None, 'CREATE') + tx['transaction']['asset'].update({'refillable': 1}) + 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_vk, None, 'CREATE') + tx['transaction']['asset'].update({'updatable': 1}) + 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_vk, None, 'CREATE') + tx['transaction']['asset'].update({'data': '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_vk, 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_vk, 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_vk, 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_vk, user_sk): + from bigchaindb.util import get_hash_data + from bigchaindb_common.exceptions import AssetIdMismatch + + tx_input = b.get_owned_ids(user_vk).pop() + tx = b.create_transaction(user_vk, user_vk, tx_input, 'TRANFER') + tx['transaction']['asset']['id'] = 'aaa' + tx['id'] = get_hash_data(tx['transaction']) + tx_signed = b.sign_transaction(tx, user_sk) + with pytest.raises(AssetIdMismatch): + b.validate_transaction(tx_signed) +""" + +def test_validate_asset(): + from bigchaindb_common.transaction import 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') + + # TODO: Handle this + # with pytest.raises(TypeError): + # b.create_transaction(b.me, b.me, None, 'CREATE', amount='a') + # with pytest.raises(AmountError): + # b.create_transaction(b.me, b.me, None, 'CREATE', divisible=False, amount=2) + # with pytest.raises(AmountError): + # b.create_transaction(b.me, b.me, None, 'CREATE', amount=0) + + +""" +@pytest.mark.usefixtures('inputs') +def test_get_asset_id_create_transaction(b, user_vk): + from bigchaindb.assets import get_asset_id + + tx_input = b.get_owned_ids(user_vk).pop() + tx_create = b.get_transaction(tx_input['txid']) + asset_id = get_asset_id(tx_create) + + assert asset_id == tx_create['transaction']['asset']['id'] +""" + +""" +@pytest.mark.usefixtures('inputs') +def test_get_asset_id_transfer_transaction(b, user_vk, user_sk): + from bigchaindb.assets import get_asset_id + + tx_input = b.get_owned_ids(user_vk).pop() + # create a transfer transaction + tx_transfer = b.create_transaction(user_vk, user_vk, tx_input, 'TRANSFER') + tx_transfer_signed = b.sign_transaction(tx_transfer, user_sk) + # create a 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) + asset_id = get_asset_id(tx_transfer) + + assert asset_id == tx_transfer['transaction']['asset']['id'] +""" + +""" +@pytest.mark.usefixtures('inputs') +def test_asset_id_mismatch(b, user_vk): + from bigchaindb.assets import get_asset_id + from bigchaindb_common.exceptions import AssetIdMismatch + + tx_input1, tx_input2 = b.get_owned_ids(user_vk)[:2] + tx1 = b.get_transaction(tx_input1['txid']) + tx2 = b.get_transaction(tx_input2['txid']) + + with pytest.raises(AssetIdMismatch): + get_asset_id([tx1, tx2]) +""" + +""" +def test_get_asset_id_transaction_does_not_exist(b, user_vk): + from bigchaindb_common.exceptions import TransactionDoesNotExist + + with pytest.raises(TransactionDoesNotExist): + b.create_transaction(user_vk, user_vk, {'txid': 'bored', 'cid': '0'}, 'TRANSFER') +""" + +""" +@pytest.mark.usefixtures('inputs') +def test_get_txs_by_asset_id(b, user_vk, user_sk): + tx_input = b.get_owned_ids(user_vk).pop() + tx = b.get_transaction(tx_input['txid']) + asset_id = tx['transaction']['asset']['id'] + txs = b.get_txs_by_asset_id(asset_id) + + assert len(txs) == 1 + assert txs[0]['id'] == tx['id'] + assert txs[0]['transaction']['asset']['id'] == asset_id + + # create a transfer transaction + tx_transfer = b.create_transaction(user_vk, user_vk, tx_input, 'TRANSFER') + tx_transfer_signed = b.sign_transaction(tx_transfer, 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 + assert tx['id'] in [t['id'] for t in txs] + assert tx_transfer['id'] in [t['id'] for t in txs] + assert asset_id == txs[0]['transaction']['asset']['id'] + assert asset_id == txs[1]['transaction']['asset']['id'] +""" diff --git a/test_transaction.py b/test_transaction.py index 5c60c1ca..b57f7a53 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -429,59 +429,6 @@ def test_invalid_fulfillment_initialization(user_ffill, user_pub): Fulfillment(user_ffill, [], tx_input='somethingthatiswrong') -def test_asset_default_values(): - from bigchaindb_common.transaction import Asset - - asset = Asset() - assert asset.data is None - assert asset.data_id - assert asset.divisible is False - assert asset.updatable is False - assert asset.refillable is False - - -def test_asset_invalid_asset_initialization(): - from bigchaindb_common.transaction import Asset - - with raises(TypeError): - Asset(data='some wrong type') - - -def test_invalid_asset_comparison(data, data_id): - from bigchaindb_common.transaction import Asset - - assert Asset(data, data_id) != 'invalid comparison' - - -def test_asset_serialization(data, data_id): - from bigchaindb_common.transaction import Asset - - expected = { - 'id': data_id, - 'divisible': False, - 'updatable': False, - 'refillable': False, - 'data': data, - } - asset = Asset(data, data_id) - assert asset.to_dict() == expected - - -def test_asset_deserialization(data, data_id): - from bigchaindb_common.transaction import Asset - - asset_dict = { - 'id': data_id, - 'divisible': False, - 'updatable': False, - 'refillable': False, - 'data': data, - } - asset = Asset.from_dict(asset_dict) - expected = Asset(data, data_id) - assert asset == expected - - def test_invalid_metadata_initialization(): from bigchaindb_common.transaction import Metadata From 0617cdd2e57a5a4f0422cc198520cd07923026cd Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 12 Oct 2016 10:31:30 +0200 Subject: [PATCH 89/98] Fixed tests --- test_asset.py | 15 +++++++++++++++ test_transaction.py | 10 +++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/test_asset.py b/test_asset.py index adc4df5a..3dd526fc 100644 --- a/test_asset.py +++ b/test_asset.py @@ -26,7 +26,22 @@ def test_asset_invalid_asset_initialization(): with raises(TypeError): Asset(data='some wrong type') + with raises(TypeError): + Asset(divisible=1) + with raises(TypeError): + Asset(refillable=1) + with raises(TypeError): + Asset(updatable=1) + # TODO: check where to test amount + """ + with pytest.raises(TypeError): + b.create_transaction(b.me, b.me, None, 'CREATE', amount='a') + with pytest.raises(AmountError): + b.create_transaction(b.me, b.me, None, 'CREATE', divisible=False, amount=2) + with pytest.raises(AmountError): + b.create_transaction(b.me, b.me, None, 'CREATE', amount=0) + """ def test_invalid_asset_comparison(data, data_id): from bigchaindb_common.transaction import Asset diff --git a/test_transaction.py b/test_transaction.py index b57f7a53..9fed7feb 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -1079,13 +1079,13 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, def test_create_transfer_with_invalid_parameters(): - from bigchaindb_common.transaction import Transaction + from bigchaindb_common.transaction import Transaction, Asset with raises(TypeError): - Transaction.transfer({}, []) + Transaction.transfer({}, [], Asset()) with raises(ValueError): - Transaction.transfer([], []) + Transaction.transfer([], [], Asset()) with raises(TypeError): - Transaction.transfer(['fulfillment'], {}) + Transaction.transfer(['fulfillment'], {}, Asset()) with raises(ValueError): - Transaction.transfer(['fulfillment'], []) + Transaction.transfer(['fulfillment'], [], Asset()) From 27e121bf1fdea90700168236f1ab0712d97c3976 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 12 Oct 2016 15:55:50 +0200 Subject: [PATCH 90/98] fix pep8 issues --- test_asset.py | 188 +------------------------------------------------- 1 file changed, 1 insertion(+), 187 deletions(-) diff --git a/test_asset.py b/test_asset.py index 3dd526fc..ddaaaa99 100644 --- a/test_asset.py +++ b/test_asset.py @@ -16,7 +16,7 @@ def test_asset_default_values(): def test_asset_creation_with_data(data): from bigchaindb_common.transaction import Asset - + asset = Asset(data) assert asset.data == data @@ -33,15 +33,6 @@ def test_asset_invalid_asset_initialization(): with raises(TypeError): Asset(updatable=1) - # TODO: check where to test amount - """ - with pytest.raises(TypeError): - b.create_transaction(b.me, b.me, None, 'CREATE', amount='a') - with pytest.raises(AmountError): - b.create_transaction(b.me, b.me, None, 'CREATE', divisible=False, amount=2) - with pytest.raises(AmountError): - b.create_transaction(b.me, b.me, None, 'CREATE', amount=0) - """ def test_invalid_asset_comparison(data, data_id): from bigchaindb_common.transaction import Asset @@ -78,89 +69,6 @@ def test_asset_deserialization(data, data_id): assert asset == expected -""" -@pytest.mark.usefixtures('inputs') -def test_asset_transfer(b, user_vk, user_sk): - tx_input = b.get_owned_ids(user_vk).pop() - tx_create = b.get_transaction(tx_input['txid']) - tx_transfer = b.create_transaction(user_vk, user_vk, tx_input, 'TRANSFER') - tx_transfer_signed = b.sign_transaction(tx_transfer, user_sk) - - assert b.validate_transaction(tx_transfer_signed) == tx_transfer_signed - assert tx_transfer_signed['transaction']['asset']['id'] == tx_create['transaction']['asset']['id'] -""" - -""" -def test_validate_bad_asset_creation(b, user_vk): - from bigchaindb.util import get_hash_data - from bigchaindb_common.exceptions import AmountError - - tx = b.create_transaction(b.me, user_vk, None, 'CREATE') - tx['transaction']['asset'].update({'divisible': 1}) - 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_vk, None, 'CREATE') - tx['transaction']['asset'].update({'refillable': 1}) - 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_vk, None, 'CREATE') - tx['transaction']['asset'].update({'updatable': 1}) - 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_vk, None, 'CREATE') - tx['transaction']['asset'].update({'data': '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_vk, 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_vk, 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_vk, 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_vk, user_sk): - from bigchaindb.util import get_hash_data - from bigchaindb_common.exceptions import AssetIdMismatch - - tx_input = b.get_owned_ids(user_vk).pop() - tx = b.create_transaction(user_vk, user_vk, tx_input, 'TRANFER') - tx['transaction']['asset']['id'] = 'aaa' - tx['id'] = get_hash_data(tx['transaction']) - tx_signed = b.sign_transaction(tx, user_sk) - with pytest.raises(AssetIdMismatch): - b.validate_transaction(tx_signed) -""" - def test_validate_asset(): from bigchaindb_common.transaction import Asset @@ -172,97 +80,3 @@ def test_validate_asset(): Asset(updatable=1) with raises(TypeError): Asset(data='we need more lemon pledge') - - # TODO: Handle this - # with pytest.raises(TypeError): - # b.create_transaction(b.me, b.me, None, 'CREATE', amount='a') - # with pytest.raises(AmountError): - # b.create_transaction(b.me, b.me, None, 'CREATE', divisible=False, amount=2) - # with pytest.raises(AmountError): - # b.create_transaction(b.me, b.me, None, 'CREATE', amount=0) - - -""" -@pytest.mark.usefixtures('inputs') -def test_get_asset_id_create_transaction(b, user_vk): - from bigchaindb.assets import get_asset_id - - tx_input = b.get_owned_ids(user_vk).pop() - tx_create = b.get_transaction(tx_input['txid']) - asset_id = get_asset_id(tx_create) - - assert asset_id == tx_create['transaction']['asset']['id'] -""" - -""" -@pytest.mark.usefixtures('inputs') -def test_get_asset_id_transfer_transaction(b, user_vk, user_sk): - from bigchaindb.assets import get_asset_id - - tx_input = b.get_owned_ids(user_vk).pop() - # create a transfer transaction - tx_transfer = b.create_transaction(user_vk, user_vk, tx_input, 'TRANSFER') - tx_transfer_signed = b.sign_transaction(tx_transfer, user_sk) - # create a 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) - asset_id = get_asset_id(tx_transfer) - - assert asset_id == tx_transfer['transaction']['asset']['id'] -""" - -""" -@pytest.mark.usefixtures('inputs') -def test_asset_id_mismatch(b, user_vk): - from bigchaindb.assets import get_asset_id - from bigchaindb_common.exceptions import AssetIdMismatch - - tx_input1, tx_input2 = b.get_owned_ids(user_vk)[:2] - tx1 = b.get_transaction(tx_input1['txid']) - tx2 = b.get_transaction(tx_input2['txid']) - - with pytest.raises(AssetIdMismatch): - get_asset_id([tx1, tx2]) -""" - -""" -def test_get_asset_id_transaction_does_not_exist(b, user_vk): - from bigchaindb_common.exceptions import TransactionDoesNotExist - - with pytest.raises(TransactionDoesNotExist): - b.create_transaction(user_vk, user_vk, {'txid': 'bored', 'cid': '0'}, 'TRANSFER') -""" - -""" -@pytest.mark.usefixtures('inputs') -def test_get_txs_by_asset_id(b, user_vk, user_sk): - tx_input = b.get_owned_ids(user_vk).pop() - tx = b.get_transaction(tx_input['txid']) - asset_id = tx['transaction']['asset']['id'] - txs = b.get_txs_by_asset_id(asset_id) - - assert len(txs) == 1 - assert txs[0]['id'] == tx['id'] - assert txs[0]['transaction']['asset']['id'] == asset_id - - # create a transfer transaction - tx_transfer = b.create_transaction(user_vk, user_vk, tx_input, 'TRANSFER') - tx_transfer_signed = b.sign_transaction(tx_transfer, 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 - assert tx['id'] in [t['id'] for t in txs] - assert tx_transfer['id'] in [t['id'] for t in txs] - assert asset_id == txs[0]['transaction']['asset']['id'] - assert asset_id == txs[1]['transaction']['asset']['id'] -""" From 50c2c7e6107f8d665e0cefb69ce0a611872bea2c Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 13 Oct 2016 10:46:24 +0200 Subject: [PATCH 91/98] Correct raised error --- test_transaction.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 9fed7feb..36db2af7 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -291,8 +291,8 @@ def test_generate_conditions_invalid_parameters(user_pub, user2_pub, def test_invalid_transaction_initialization(): from bigchaindb_common.transaction import Transaction, Asset - with raises(TypeError): - Transaction(operation='invalid operation') + with raises(ValueError): + Transaction(operation='invalid operation', asset=Asset()) with raises(TypeError): Transaction(operation='CREATE', asset='invalid asset') with raises(TypeError): @@ -406,20 +406,6 @@ def test_tx_serialization_with_incorrect_hash(utx): Transaction.from_dict(utx_dict) -def test_invalid_tx_initialization(): - from bigchaindb_common.transaction import Transaction - - wrong_metadata_type = {'data': 'a totally wrong metadatatype'} - with raises(TypeError): - Transaction(Transaction.CREATE, wrong_metadata_type) - with raises(TypeError): - Transaction(Transaction.CREATE, [], wrong_metadata_type) - with raises(TypeError): - Transaction(Transaction.CREATE, [], [], wrong_metadata_type) - with raises(TypeError): - Transaction('RANSFER', [], []) - - def test_invalid_fulfillment_initialization(user_ffill, user_pub): from bigchaindb_common.transaction import Fulfillment From 1cbab14518f4ae7c3c2c268e06543d8fd5482c36 Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 13 Oct 2016 10:46:49 +0200 Subject: [PATCH 92/98] Add test for asset initialization --- test_transaction.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test_transaction.py b/test_transaction.py index 36db2af7..811b54d8 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -318,6 +318,18 @@ 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) + expected = Asset() + asset = tx.asset + + expected.data_id = None + asset.data_id = None + assert asset == expected + + def test_transaction_serialization(user_ffill, user_cond, data, data_id): from bigchaindb_common.transaction import Transaction, Asset From 5b6f9e222dbe8b5c8c28c2903c52c6bbc7cb9fc0 Mon Sep 17 00:00:00 2001 From: tim Date: Thu, 13 Oct 2016 10:48:52 +0200 Subject: [PATCH 93/98] Remove resolved TODOs --- test_asset.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test_asset.py b/test_asset.py index ddaaaa99..397c68c4 100644 --- a/test_asset.py +++ b/test_asset.py @@ -1,7 +1,5 @@ from pytest import raises -# TODO: Test asset amount - def test_asset_default_values(): from bigchaindb_common.transaction import Asset From db127bea401418884be665142a5d24d7075645d3 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Thu, 13 Oct 2016 16:33:41 +0200 Subject: [PATCH 94/98] Small modifications to support new cryptoconditions --- test_transaction.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test_transaction.py b/test_transaction.py index 811b54d8..897570b0 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -558,7 +558,7 @@ def test_validate_tx_simple_create_signature(user_ffill, user_cond, user_priv): tx = Transaction(Transaction.CREATE, Asset(), [user_ffill], [user_cond]) expected = deepcopy(user_cond) - expected.fulfillment.sign(str(tx), SigningKey(user_priv)) + expected.fulfillment.sign(str(tx).encode(), SigningKey(user_priv)) tx.sign([user_priv]) assert tx.fulfillments[0].to_dict()['fulfillment'] == \ @@ -625,9 +625,11 @@ def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv): expected_second.fulfillments = [expected_second.fulfillments[1]] expected_second.conditions = [expected_second.conditions[1]] - expected_first.fulfillments[0].fulfillment.sign(str(expected_first), + expected_first_bytes = str(expected_first).encode() + expected_first.fulfillments[0].fulfillment.sign(expected_first_bytes, SigningKey(user_priv)) - expected_second.fulfillments[0].fulfillment.sign(str(expected_second), + expected_second_bytes = str(expected_second).encode() + expected_second.fulfillments[0].fulfillment.sign(expected_second_bytes, SigningKey(user_priv)) tx.sign([user_priv]) @@ -652,9 +654,9 @@ def test_validate_tx_threshold_create_signature(user_user2_threshold_ffill, tx = Transaction(Transaction.CREATE, Asset(), [user_user2_threshold_ffill], [user_user2_threshold_cond]) expected = deepcopy(user_user2_threshold_cond) - expected.fulfillment.subconditions[0]['body'].sign(str(tx), + expected.fulfillment.subconditions[0]['body'].sign(str(tx).encode(), SigningKey(user_priv)) - expected.fulfillment.subconditions[1]['body'].sign(str(tx), + expected.fulfillment.subconditions[1]['body'].sign(str(tx).encode(), SigningKey(user2_priv)) tx.sign([user_priv, user2_priv]) @@ -1001,7 +1003,8 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, expected_input = deepcopy(inputs[0]) expected['id'] = transfer_tx['id'] expected['transaction']['timestamp'] = transfer_tx_body['timestamp'] - expected_input.fulfillment.sign(serialize(expected), SigningKey(user_priv)) + expected_input.fulfillment.sign(serialize(expected).encode(), + SigningKey(user_priv)) expected_ffill = expected_input.fulfillment.serialize_uri() transfer_ffill = transfer_tx_body['fulfillments'][0]['fulfillment'] From c007d0d8b4bf94bab1adf89c2c5adb843a212d9e Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Thu, 13 Oct 2016 16:12:27 +0200 Subject: [PATCH 95/98] prevent adding None as fulfillment / condition to Transaction --- test_transaction.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test_transaction.py b/test_transaction.py index 897570b0..c69e5023 100644 --- a/test_transaction.py +++ b/test_transaction.py @@ -1090,3 +1090,17 @@ def test_create_transfer_with_invalid_parameters(): Transaction.transfer(['fulfillment'], {}, Asset()) with raises(ValueError): Transaction.transfer(['fulfillment'], [], Asset()) + + +def test_cant_add_empty_condition(): + from bigchaindb_common.transaction import Transaction + 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) + with raises(TypeError): + tx.add_fulfillment(None) From b240ef79d34d2338d2529e4385f864f4f713d1e1 Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Mon, 24 Oct 2016 14:40:13 +0200 Subject: [PATCH 96/98] Extract common tests --- test_asset.py => tests/common/test_asset.py | 0 test_transaction.py => tests/common/test_transaction.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test_asset.py => tests/common/test_asset.py (100%) rename test_transaction.py => tests/common/test_transaction.py (100%) diff --git a/test_asset.py b/tests/common/test_asset.py similarity index 100% rename from test_asset.py rename to tests/common/test_asset.py diff --git a/test_transaction.py b/tests/common/test_transaction.py similarity index 100% rename from test_transaction.py rename to tests/common/test_transaction.py From ff7cf0863f1f015536eaa928978eb74cc48a6410 Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Mon, 24 Oct 2016 15:59:49 +0200 Subject: [PATCH 97/98] Copy conftest from bigchaindb-common - by @timdaub --- tests/common/conftest.py | 162 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 tests/common/conftest.py diff --git a/tests/common/conftest.py b/tests/common/conftest.py new file mode 100644 index 00000000..4c0343b5 --- /dev/null +++ b/tests/common/conftest.py @@ -0,0 +1,162 @@ +import pytest + + +USER_PRIVATE_KEY = '8eJ8q9ZQpReWyQT5aFCiwtZ5wDZC4eDnCen88p3tQ6ie' +USER_PUBLIC_KEY = 'JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE' + +USER2_PRIVATE_KEY = 'F86PQPiqMTwM2Qi2Sda3U4Vdh3AgadMdX3KNVsu5wNJr' +USER2_PUBLIC_KEY = 'GDxwMFbwdATkQELZbMfW8bd9hbNYMZLyVXA3nur2aNbE' + +USER3_PRIVATE_KEY = '4rNQFzWQbVwuTiDVxwuFMvLG5zd8AhrQKCtVovBvcYsB' +USER3_PUBLIC_KEY = 'Gbrg7JtxdjedQRmr81ZZbh1BozS7fBW88ZyxNDy7WLNC' + + +CC_FULFILLMENT_URI = 'cf:0:' +CC_CONDITION_URI = 'cc:0:3:47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU:0' + +DATA = { + 'msg': 'Hello BigchainDB!' +} +DATA_ID = '872fa6e6f46246cd44afdb2ee9cfae0e72885fb0910e2bcf9a5a2a4eadb417b8' + + +@pytest.fixture +def user_priv(): + return USER_PRIVATE_KEY + + +@pytest.fixture +def user_pub(): + return USER_PUBLIC_KEY + + +@pytest.fixture +def user2_priv(): + return USER2_PRIVATE_KEY + + +@pytest.fixture +def user2_pub(): + return USER2_PUBLIC_KEY + + +@pytest.fixture +def user3_priv(): + return USER3_PRIVATE_KEY + + +@pytest.fixture +def user3_pub(): + return USER3_PUBLIC_KEY + + +@pytest.fixture +def ffill_uri(): + return CC_FULFILLMENT_URI + + +@pytest.fixture +def cond_uri(): + return CC_CONDITION_URI + + +@pytest.fixture +def user_Ed25519(user_pub): + from cryptoconditions import Ed25519Fulfillment + return Ed25519Fulfillment(public_key=user_pub) + + +@pytest.fixture +def user_user2_threshold(user_pub, user2_pub): + from cryptoconditions import (ThresholdSha256Fulfillment, + Ed25519Fulfillment) + user_pub_keys = [user_pub, user2_pub] + threshold = ThresholdSha256Fulfillment(threshold=len(user_pub_keys)) + for user_pub in user_pub_keys: + threshold.add_subfulfillment(Ed25519Fulfillment(public_key=user_pub)) + return threshold + + +@pytest.fixture +def user2_Ed25519(user2_pub): + from cryptoconditions import Ed25519Fulfillment + return Ed25519Fulfillment(public_key=user2_pub) + + +@pytest.fixture +def user_ffill(user_Ed25519, user_pub): + from bigchaindb_common.transaction import Fulfillment + return Fulfillment(user_Ed25519, [user_pub]) + + +@pytest.fixture +def user2_ffill(user2_Ed25519, user2_pub): + from bigchaindb_common.transaction import Fulfillment + return Fulfillment(user2_Ed25519, [user2_pub]) + + +@pytest.fixture +def user_user2_threshold_cond(user_user2_threshold, user_pub, user2_pub): + from bigchaindb_common.transaction import Condition + return Condition(user_user2_threshold, [user_pub, user2_pub]) + + +@pytest.fixture +def user_user2_threshold_ffill(user_user2_threshold, user_pub, user2_pub): + from bigchaindb_common.transaction import Fulfillment + return Fulfillment(user_user2_threshold, [user_pub, user2_pub]) + + +@pytest.fixture +def user_cond(user_Ed25519, user_pub): + from bigchaindb_common.transaction import Condition + return Condition(user_Ed25519, [user_pub]) + + +@pytest.fixture +def user2_cond(user2_Ed25519, user2_pub): + from bigchaindb_common.transaction import Condition + return Condition(user2_Ed25519, [user2_pub]) + + +@pytest.fixture +def data(): + return DATA + + +@pytest.fixture +def data_id(): + return DATA_ID + + +@pytest.fixture +def metadata(data, data_id): + from bigchaindb_common.transaction import Metadata + return Metadata(data, data_id) + + +@pytest.fixture +def utx(user_ffill, user_cond): + from bigchaindb_common.transaction import Transaction, Asset + return Transaction(Transaction.CREATE, Asset(), [user_ffill], [user_cond]) + + +@pytest.fixture +def tx(utx, user_priv): + return utx.sign([user_priv]) + + +@pytest.fixture +def transfer_utx(user_cond, user2_cond, utx): + from bigchaindb_common.transaction import (Fulfillment, TransactionLink, + Transaction, Asset) + user_cond = user_cond.to_dict() + ffill = Fulfillment(utx.conditions[0].fulfillment, + user_cond['owners_after'], + TransactionLink(utx.id, 0)) + return Transaction('TRANSFER', Asset(), [ffill], [user2_cond]) + + +@pytest.fixture +def transfer_tx(transfer_utx, user_priv): + return transfer_utx.sign([user_priv]) From ea9dfaf1e583fd5a89f0dd62caf970c433f1a0fd Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Mon, 24 Oct 2016 17:01:43 +0200 Subject: [PATCH 98/98] Replace bigchaindb_common pkg by bigchaindb.common --- benchmarking-tests/benchmark_utils.py | 2 +- bigchaindb/commands/bigchain.py | 4 +- bigchaindb/commands/utils.py | 4 +- bigchaindb/common/transaction.py | 68 ++++----- bigchaindb/config_utils.py | 2 +- bigchaindb/core.py | 6 +- bigchaindb/db/utils.py | 2 +- bigchaindb/models.py | 8 +- bigchaindb/pipelines/vote.py | 2 +- bigchaindb/util.py | 4 +- bigchaindb/web/views/transactions.py | 2 +- deploy-cluster-aws/write_keypairs_file.py | 4 +- setup.py | 1 - tests/assets/test_digital_assets.py | 4 +- tests/common/conftest.py | 18 +-- tests/common/test_asset.py | 14 +- tests/common/test_transaction.py | 130 +++++++++--------- tests/db/conftest.py | 6 +- tests/db/test_bigchain_api.py | 98 ++++++------- tests/db/test_utils.py | 2 +- .../doc/run_doc_python_server_api_examples.py | 2 +- tests/pipelines/test_election.py | 2 +- tests/pipelines/test_vote.py | 24 ++-- tests/test_commands.py | 10 +- tests/test_config_utils.py | 4 +- tests/test_models.py | 22 +-- tests/web/test_basic_views.py | 2 +- 27 files changed, 223 insertions(+), 224 deletions(-) diff --git a/benchmarking-tests/benchmark_utils.py b/benchmarking-tests/benchmark_utils.py index 86242753..ce462bdf 100644 --- a/benchmarking-tests/benchmark_utils.py +++ b/benchmarking-tests/benchmark_utils.py @@ -8,7 +8,7 @@ import logging import rethinkdb as r from os.path import expanduser -from bigchaindb_common.transaction import Transaction +from bigchaindb.common.transaction import Transaction from bigchaindb import Bigchain from bigchaindb.util import ProcessGroup diff --git a/bigchaindb/commands/bigchain.py b/bigchaindb/commands/bigchain.py index cd5ce6db..40891416 100644 --- a/bigchaindb/commands/bigchain.py +++ b/bigchaindb/commands/bigchain.py @@ -13,8 +13,8 @@ import builtins import logstats -from bigchaindb_common import crypto -from bigchaindb_common.exceptions import (StartupError, +from bigchaindb.common import crypto +from bigchaindb.common.exceptions import (StartupError, DatabaseAlreadyExists, KeypairNotFoundException) import rethinkdb as r diff --git a/bigchaindb/commands/utils.py b/bigchaindb/commands/utils.py index 1391f18f..65459737 100644 --- a/bigchaindb/commands/utils.py +++ b/bigchaindb/commands/utils.py @@ -3,7 +3,7 @@ for ``argparse.ArgumentParser``. """ import argparse -from bigchaindb_common.exceptions import StartupError +from bigchaindb.common.exceptions import StartupError import multiprocessing as mp import subprocess @@ -25,7 +25,7 @@ def start_rethinkdb(): starting the db Raises: - ``bigchaindb_common.exceptions.StartupError`` if RethinkDB cannot + ``bigchaindb.common.exceptions.StartupError`` if RethinkDB cannot be started. """ diff --git a/bigchaindb/common/transaction.py b/bigchaindb/common/transaction.py index e8f7a63d..41d8a30c 100644 --- a/bigchaindb/common/transaction.py +++ b/bigchaindb/common/transaction.py @@ -7,10 +7,10 @@ from cryptoconditions import (Fulfillment as CCFulfillment, PreimageSha256Fulfillment) from cryptoconditions.exceptions import ParsingError -from bigchaindb_common.crypto import SigningKey, hash_data -from bigchaindb_common.exceptions import (KeypairMismatchException, +from bigchaindb.common.crypto import SigningKey, hash_data +from bigchaindb.common.exceptions import (KeypairMismatchException, InvalidHash, InvalidSignature) -from bigchaindb_common.util import serialize, gen_timestamp +from bigchaindb.common.util import serialize, gen_timestamp class Fulfillment(object): @@ -21,7 +21,7 @@ class Fulfillment(object): to be signed with a private key. owners_before (:obj:`list` of :obj:`str`): A list of owners after a Transaction was confirmed. - tx_input (:class:`~bigchaindb_common.transaction. TransactionLink`, + tx_input (:class:`~bigchaindb.common.transaction. TransactionLink`, optional): A link representing the input of a `TRANSFER` Transaction. """ @@ -34,7 +34,7 @@ class Fulfillment(object): Fulfillment to be signed with a private key. owners_before (:obj:`list` of :obj:`str`): A list of owners after a Transaction was confirmed. - tx_input (:class:`~bigchaindb_common.transaction. + tx_input (:class:`~bigchaindb.common.transaction. TransactionLink`, optional): A link representing the input of a `TRANSFER` Transaction. """ @@ -110,7 +110,7 @@ class Fulfillment(object): ffill (dict): The Fulfillment to be transformed. Returns: - :class:`~bigchaindb_common.transaction.Fulfillment` + :class:`~bigchaindb.common.transaction.Fulfillment` Raises: InvalidSignature: If a Fulfillment's URI couldn't be parsed. @@ -170,7 +170,7 @@ class TransactionLink(object): link (dict): The link to be transformed. Returns: - :class:`~bigchaindb_common.transaction.TransactionLink` + :class:`~bigchaindb.common.transaction.TransactionLink` """ try: return cls(link['txid'], link['cid']) @@ -299,8 +299,8 @@ class Condition(object): TypeError: If `owners_after` is an empty list. """ # TODO: We probably want to remove the tuple logic for weights here - # again: https://github.com/bigchaindb/bigchaindb-common/issues/ - # 12#issuecomment-251665325 + # again: + # github.com/bigchaindb/bigchaindb/issues/730#issuecomment-255144756 if isinstance(owners_after, tuple): owners_after, threshold = owners_after else: @@ -387,7 +387,7 @@ class Condition(object): cond (dict): The Condition to be transformed. Returns: - :class:`~bigchaindb_common.transaction.Condition` + :class:`~bigchaindb.common.transaction.Condition` """ try: fulfillment = CCFulfillment.from_dict(cond['condition']['details']) @@ -455,7 +455,7 @@ class Asset(object): asset (dict): The dictionary to be serialized. Returns: - :class:`~bigchaindb_common.transaction.Asset` + :class:`~bigchaindb.common.transaction.Asset` """ return cls(asset.get('data'), asset['id'], asset.get('divisible', False), @@ -516,7 +516,7 @@ class Metadata(object): data (dict): The dictionary to be serialized. Returns: - :class:`~bigchaindb_common.transaction.Metadata` + :class:`~bigchaindb.common.transaction.Metadata` """ try: return cls(data['data'], data['id']) @@ -552,12 +552,12 @@ class Transaction(object): Attributes: operation (str): Defines the operation of the Transaction. - fulfillments (:obj:`list` of :class:`~bigchaindb_common. + fulfillments (:obj:`list` of :class:`~bigchaindb.common. transaction.Fulfillment`, optional): Define the assets to spend. - conditions (:obj:`list` of :class:`~bigchaindb_common. + conditions (:obj:`list` of :class:`~bigchaindb.common. transaction.Condition`, optional): Define the assets to lock. - metadata (:class:`~bigchaindb_common.transaction.Metadata`): + metadata (:class:`~bigchaindb.common.transaction.Metadata`): Metadata to be stored along with the Transaction. timestamp (int): Defines the time a Transaction was created. version (int): Defines the version number of a Transaction. @@ -578,15 +578,15 @@ class Transaction(object): Args: operation (str): Defines the operation of the Transaction. - asset (:class:`~bigchaindb_common.transaction.Asset`): An Asset + asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset to be transferred or created in a Transaction. - fulfillments (:obj:`list` of :class:`~bigchaindb_common. + fulfillments (:obj:`list` of :class:`~bigchaindb.common. transaction.Fulfillment`, optional): Define the assets to spend. - conditions (:obj:`list` of :class:`~bigchaindb_common. + conditions (:obj:`list` of :class:`~bigchaindb.common. transaction.Condition`, optional): Define the assets to lock. - metadata (:class:`~bigchaindb_common.transaction.Metadata`): + metadata (:class:`~bigchaindb.common.transaction.Metadata`): Metadata to be stored along with the Transaction. timestamp (int): Defines the time a Transaction was created. version (int): Defines the version number of a Transaction. @@ -661,7 +661,7 @@ class Transaction(object): represent the receivers of this Transaction. metadata (dict): Python dictionary to be stored along with the Transaction. - asset (:class:`~bigchaindb_common.transaction.Asset`): An Asset + 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. @@ -669,7 +669,7 @@ class Transaction(object): valid. Returns: - :class:`~bigchaindb_common.transaction.Transaction` + :class:`~bigchaindb.common.transaction.Transaction` """ if not isinstance(owners_before, list): raise TypeError('`owners_before` must be a list instance') @@ -749,19 +749,19 @@ class Transaction(object): weight respectively. `inp2` is owned completely by `d`. Args: - inputs (:obj:`list` of :class:`~bigchaindb_common.transaction. + inputs (:obj:`list` of :class:`~bigchaindb.common.transaction. Fulfillment`): Converted "output" Conditions, intended to be used as "input" Fulfillments in the transfer to generate. owners_after (:obj:`list` of :obj:`str`): A list of keys that represent the receivers of this Transaction. - asset (:class:`~bigchaindb_common.transaction.Asset`): An Asset + asset (:class:`~bigchaindb.common.transaction.Asset`): An Asset to be transferred in this Transaction. metadata (dict): Python dictionary to be stored along with the Transaction. Returns: - :class:`~bigchaindb_common.transaction.Transaction` + :class:`~bigchaindb.common.transaction.Transaction` """ if not isinstance(inputs, list): raise TypeError('`inputs` must be a list instance') @@ -809,7 +809,7 @@ class Transaction(object): Conditions should be returned as inputs. Returns: - :obj:`list` of :class:`~bigchaindb_common.transaction. + :obj:`list` of :class:`~bigchaindb.common.transaction. Fulfillment` """ inputs = [] @@ -831,7 +831,7 @@ class Transaction(object): """Adds a Fulfillment to a Transaction's list of Fulfillments. Args: - fulfillment (:class:`~bigchaindb_common.transaction. + fulfillment (:class:`~bigchaindb.common.transaction. Fulfillment`): A Fulfillment to be added to the Transaction. """ @@ -843,7 +843,7 @@ class Transaction(object): """Adds a Condition to a Transaction's list of Conditions. Args: - condition (:class:`~bigchaindb_common.transaction. + condition (:class:`~bigchaindb.common.transaction. Condition`): A Condition to be added to the Transaction. """ @@ -869,7 +869,7 @@ class Transaction(object): Transaction. Returns: - :class:`~bigchaindb_common.transaction.Transaction` + :class:`~bigchaindb.common.transaction.Transaction` """ # TODO: Singing should be possible with at least one of all private # keys supplied to this method. @@ -920,7 +920,7 @@ class Transaction(object): - ThresholdSha256Fulfillment. Args: - fulfillment (:class:`~bigchaindb_common.transaction. + fulfillment (:class:`~bigchaindb.common.transaction. Fulfillment`) The Fulfillment to be signed. index (int): The index (or `fid`) of the Fulfillment to be signed. @@ -943,7 +943,7 @@ class Transaction(object): """Signs a Ed25519Fulfillment. Args: - fulfillment (:class:`~bigchaindb_common.transaction. + fulfillment (:class:`~bigchaindb.common.transaction. Fulfillment`) The Fulfillment to be signed. index (int): The index (or `fid`) of the Fulfillment to be signed. @@ -972,7 +972,7 @@ class Transaction(object): """Signs a ThresholdSha256Fulfillment. Args: - fulfillment (:class:`~bigchaindb_common.transaction. + fulfillment (:class:`~bigchaindb.common.transaction. Fulfillment`) The Fulfillment to be signed. index (int): The index (or `fid`) of the Fulfillment to be signed. @@ -1019,7 +1019,7 @@ class Transaction(object): evaluate parts of the validation-checks to `True`. Args: - input_conditions (:obj:`list` of :class:`~bigchaindb_common. + input_conditions (:obj:`list` of :class:`~bigchaindb.common. transaction.Condition`): A list of Conditions to check the Fulfillments against. @@ -1094,7 +1094,7 @@ class Transaction(object): does not validate against `input_condition_uri`. Args: - fulfillment (:class:`~bigchaindb_common.transaction. + fulfillment (:class:`~bigchaindb.common.transaction. Fulfillment`) The Fulfillment to be signed. operation (str): The type of Transaction. tx_serialized (str): The Transaction used as a message when @@ -1218,7 +1218,7 @@ class Transaction(object): tx_body (dict): The Transaction to be transformed. Returns: - :class:`~bigchaindb_common.transaction.Transaction` + :class:`~bigchaindb.common.transaction.Transaction` """ # NOTE: Remove reference to avoid side effects tx_body = deepcopy(tx_body) diff --git a/bigchaindb/config_utils.py b/bigchaindb/config_utils.py index f72c348b..3b571835 100644 --- a/bigchaindb/config_utils.py +++ b/bigchaindb/config_utils.py @@ -17,7 +17,7 @@ import json import logging import collections -from bigchaindb_common import exceptions +from bigchaindb.common import exceptions import bigchaindb diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 3930496d..2724080f 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -4,9 +4,9 @@ import collections 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, Metadata +from bigchaindb.common import crypto, exceptions +from bigchaindb.common.util import gen_timestamp, serialize +from bigchaindb.common.transaction import TransactionLink, Metadata import rethinkdb as r diff --git a/bigchaindb/db/utils.py b/bigchaindb/db/utils.py index d51563d5..92e0fdd3 100644 --- a/bigchaindb/db/utils.py +++ b/bigchaindb/db/utils.py @@ -3,7 +3,7 @@ import time import logging -from bigchaindb_common import exceptions +from bigchaindb.common import exceptions import rethinkdb as r import bigchaindb diff --git a/bigchaindb/models.py b/bigchaindb/models.py index 7ed81061..1334b2de 100644 --- a/bigchaindb/models.py +++ b/bigchaindb/models.py @@ -1,11 +1,11 @@ -from bigchaindb_common.crypto import hash_data, VerifyingKey, SigningKey -from bigchaindb_common.exceptions import (InvalidHash, InvalidSignature, +from bigchaindb.common.crypto import hash_data, VerifyingKey, SigningKey +from bigchaindb.common.exceptions import (InvalidHash, InvalidSignature, OperationError, DoubleSpend, TransactionDoesNotExist, FulfillmentNotInValidBlock, AssetIdMismatch) -from bigchaindb_common.transaction import Transaction, Asset -from bigchaindb_common.util import gen_timestamp, serialize +from bigchaindb.common.transaction import Transaction, Asset +from bigchaindb.common.util import gen_timestamp, serialize class Asset(Asset): diff --git a/bigchaindb/pipelines/vote.py b/bigchaindb/pipelines/vote.py index 3d79c265..6b12f55b 100644 --- a/bigchaindb/pipelines/vote.py +++ b/bigchaindb/pipelines/vote.py @@ -8,7 +8,7 @@ function. from collections import Counter from multipipes import Pipeline, Node -from bigchaindb_common import exceptions +from bigchaindb.common import exceptions from bigchaindb.consensus import BaseConsensusRules from bigchaindb.models import Transaction, Block diff --git a/bigchaindb/util.py b/bigchaindb/util.py index d8fb13e3..fcaa5e00 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -3,8 +3,8 @@ import threading import queue import multiprocessing as mp -from bigchaindb_common import crypto -from bigchaindb_common.util import serialize +from bigchaindb.common import crypto +from bigchaindb.common.util import serialize class ProcessGroup(object): diff --git a/bigchaindb/web/views/transactions.py b/bigchaindb/web/views/transactions.py index 6439ff4c..8780fda8 100644 --- a/bigchaindb/web/views/transactions.py +++ b/bigchaindb/web/views/transactions.py @@ -6,7 +6,7 @@ For more information please refer to the documentation on ReadTheDocs: from flask import current_app, request, Blueprint from flask_restful import Resource, Api -from bigchaindb_common.exceptions import InvalidHash, InvalidSignature +from bigchaindb.common.exceptions import InvalidHash, InvalidSignature import bigchaindb from bigchaindb.models import Transaction diff --git a/deploy-cluster-aws/write_keypairs_file.py b/deploy-cluster-aws/write_keypairs_file.py index 2037141c..da4fc1b1 100644 --- a/deploy-cluster-aws/write_keypairs_file.py +++ b/deploy-cluster-aws/write_keypairs_file.py @@ -1,5 +1,5 @@ """A Python 3 script to write a file with a specified number -of keypairs, using bigchaindb_common.crypto.generate_key_pair() +of keypairs, using bigchaindb.common.crypto.generate_key_pair() The written file is always named keypairs.py and it should be interpreted as a Python 2 script. @@ -16,7 +16,7 @@ Using the list in other Python scripts: import argparse -from bigchaindb_common import crypto +from bigchaindb.common import crypto # Parse the command-line arguments diff --git a/setup.py b/setup.py index 13c13e93..67a367f6 100644 --- a/setup.py +++ b/setup.py @@ -103,7 +103,6 @@ setup( 'requests~=2.9', 'gunicorn~=19.0', 'multipipes~=0.1.0', - 'bigchaindb-common==0.0.6', ], setup_requires=['pytest-runner'], tests_require=tests_require, diff --git a/tests/assets/test_digital_assets.py b/tests/assets/test_digital_assets.py index 64da4c11..e18684c5 100644 --- a/tests/assets/test_digital_assets.py +++ b/tests/assets/test_digital_assets.py @@ -76,7 +76,7 @@ def test_validate_bad_asset_creation(b, user_vk): @pytest.mark.usefixtures('inputs') def test_validate_transfer_asset_id_mismatch(b, user_vk, user_sk): - from bigchaindb_common.exceptions import AssetIdMismatch + from bigchaindb.common.exceptions import AssetIdMismatch from bigchaindb.models import Transaction tx_create = b.get_owned_ids(user_vk).pop() @@ -121,7 +121,7 @@ def test_get_asset_id_transfer_transaction(b, user_vk, user_sk): def test_asset_id_mismatch(b, user_vk): from bigchaindb.models import Transaction, Asset - from bigchaindb_common.exceptions import AssetIdMismatch + from bigchaindb.common.exceptions import AssetIdMismatch tx1 = Transaction.create([b.me], [user_vk]) tx2 = Transaction.create([b.me], [user_vk]) diff --git a/tests/common/conftest.py b/tests/common/conftest.py index 4c0343b5..8b1a47db 100644 --- a/tests/common/conftest.py +++ b/tests/common/conftest.py @@ -85,37 +85,37 @@ def user2_Ed25519(user2_pub): @pytest.fixture def user_ffill(user_Ed25519, user_pub): - from bigchaindb_common.transaction import Fulfillment + from bigchaindb.common.transaction import Fulfillment return Fulfillment(user_Ed25519, [user_pub]) @pytest.fixture def user2_ffill(user2_Ed25519, user2_pub): - from bigchaindb_common.transaction import Fulfillment + from bigchaindb.common.transaction import Fulfillment return Fulfillment(user2_Ed25519, [user2_pub]) @pytest.fixture def user_user2_threshold_cond(user_user2_threshold, user_pub, user2_pub): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition return Condition(user_user2_threshold, [user_pub, user2_pub]) @pytest.fixture def user_user2_threshold_ffill(user_user2_threshold, user_pub, user2_pub): - from bigchaindb_common.transaction import Fulfillment + from bigchaindb.common.transaction import Fulfillment return Fulfillment(user_user2_threshold, [user_pub, user2_pub]) @pytest.fixture def user_cond(user_Ed25519, user_pub): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition return Condition(user_Ed25519, [user_pub]) @pytest.fixture def user2_cond(user2_Ed25519, user2_pub): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition return Condition(user2_Ed25519, [user2_pub]) @@ -131,13 +131,13 @@ def data_id(): @pytest.fixture def metadata(data, data_id): - from bigchaindb_common.transaction import Metadata + from bigchaindb.common.transaction import Metadata return Metadata(data, data_id) @pytest.fixture def utx(user_ffill, user_cond): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset return Transaction(Transaction.CREATE, Asset(), [user_ffill], [user_cond]) @@ -148,7 +148,7 @@ def tx(utx, user_priv): @pytest.fixture def transfer_utx(user_cond, user2_cond, utx): - from bigchaindb_common.transaction import (Fulfillment, TransactionLink, + from bigchaindb.common.transaction import (Fulfillment, TransactionLink, Transaction, Asset) user_cond = user_cond.to_dict() ffill = Fulfillment(utx.conditions[0].fulfillment, diff --git a/tests/common/test_asset.py b/tests/common/test_asset.py index 397c68c4..edfbcb5f 100644 --- a/tests/common/test_asset.py +++ b/tests/common/test_asset.py @@ -2,7 +2,7 @@ from pytest import raises def test_asset_default_values(): - from bigchaindb_common.transaction import Asset + from bigchaindb.common.transaction import Asset asset = Asset() assert asset.data is None @@ -13,14 +13,14 @@ def test_asset_default_values(): def test_asset_creation_with_data(data): - from bigchaindb_common.transaction import Asset + from bigchaindb.common.transaction import Asset asset = Asset(data) assert asset.data == data def test_asset_invalid_asset_initialization(): - from bigchaindb_common.transaction import Asset + from bigchaindb.common.transaction import Asset with raises(TypeError): Asset(data='some wrong type') @@ -33,13 +33,13 @@ def test_asset_invalid_asset_initialization(): def test_invalid_asset_comparison(data, data_id): - from bigchaindb_common.transaction import Asset + from bigchaindb.common.transaction import Asset assert Asset(data, data_id) != 'invalid comparison' def test_asset_serialization(data, data_id): - from bigchaindb_common.transaction import Asset + from bigchaindb.common.transaction import Asset expected = { 'id': data_id, @@ -53,7 +53,7 @@ def test_asset_serialization(data, data_id): def test_asset_deserialization(data, data_id): - from bigchaindb_common.transaction import Asset + from bigchaindb.common.transaction import Asset asset_dict = { 'id': data_id, @@ -68,7 +68,7 @@ def test_asset_deserialization(data, data_id): def test_validate_asset(): - from bigchaindb_common.transaction import Asset + from bigchaindb.common.transaction import Asset with raises(TypeError): Asset(divisible=1) diff --git a/tests/common/test_transaction.py b/tests/common/test_transaction.py index c69e5023..5f2d58fb 100644 --- a/tests/common/test_transaction.py +++ b/tests/common/test_transaction.py @@ -2,7 +2,7 @@ from pytest import raises, mark def test_fulfillment_serialization(ffill_uri, user_pub): - from bigchaindb_common.transaction import Fulfillment + from bigchaindb.common.transaction import Fulfillment from cryptoconditions import Fulfillment as CCFulfillment expected = { @@ -15,7 +15,7 @@ def test_fulfillment_serialization(ffill_uri, user_pub): def test_fulfillment_deserialization_with_uri(ffill_uri, user_pub): - from bigchaindb_common.transaction import Fulfillment + from bigchaindb.common.transaction import Fulfillment from cryptoconditions import Fulfillment as CCFulfillment expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) @@ -30,7 +30,7 @@ def test_fulfillment_deserialization_with_uri(ffill_uri, user_pub): def test_fulfillment_deserialization_with_invalid_fulfillment(user_pub): - from bigchaindb_common.transaction import Fulfillment + from bigchaindb.common.transaction import Fulfillment ffill = { 'owners_before': [user_pub], @@ -42,8 +42,8 @@ def test_fulfillment_deserialization_with_invalid_fulfillment(user_pub): def test_fulfillment_deserialization_with_invalid_fulfillment_uri(user_pub): - from bigchaindb_common.exceptions import InvalidSignature - from bigchaindb_common.transaction import Fulfillment + from bigchaindb.common.exceptions import InvalidSignature + from bigchaindb.common.transaction import Fulfillment ffill = { 'owners_before': [user_pub], @@ -56,7 +56,7 @@ def test_fulfillment_deserialization_with_invalid_fulfillment_uri(user_pub): def test_fulfillment_deserialization_with_unsigned_fulfillment(ffill_uri, user_pub): - from bigchaindb_common.transaction import Fulfillment + from bigchaindb.common.transaction import Fulfillment from cryptoconditions import Fulfillment as CCFulfillment expected = Fulfillment(CCFulfillment.from_uri(ffill_uri), [user_pub]) @@ -71,7 +71,7 @@ def test_fulfillment_deserialization_with_unsigned_fulfillment(ffill_uri, def test_condition_serialization(user_Ed25519, user_pub): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition expected = { 'condition': { @@ -88,7 +88,7 @@ def test_condition_serialization(user_Ed25519, user_pub): def test_condition_deserialization(user_Ed25519, user_pub): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition expected = Condition(user_Ed25519, [user_pub], 1) cond = { @@ -105,7 +105,7 @@ def test_condition_deserialization(user_Ed25519, user_pub): def test_condition_hashlock_serialization(): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition from cryptoconditions import PreimageSha256Fulfillment secret = b'wow much secret' @@ -124,7 +124,7 @@ def test_condition_hashlock_serialization(): def test_condition_hashlock_deserialization(): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition from cryptoconditions import PreimageSha256Fulfillment secret = b'wow much secret' @@ -144,7 +144,7 @@ def test_condition_hashlock_deserialization(): def test_invalid_condition_initialization(cond_uri, user_pub): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition with raises(TypeError): Condition(cond_uri, user_pub) @@ -152,7 +152,7 @@ def test_invalid_condition_initialization(cond_uri, user_pub): def test_generate_conditions_split_half_recursive(user_pub, user2_pub, user3_pub): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment expected_simple1 = Ed25519Fulfillment(public_key=user_pub) @@ -173,7 +173,7 @@ def test_generate_conditions_split_half_recursive(user_pub, user2_pub, def test_generate_conditions_split_half_recursive_custom_threshold(user_pub, user2_pub, user3_pub): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment expected_simple1 = Ed25519Fulfillment(public_key=user_pub) @@ -194,7 +194,7 @@ def test_generate_conditions_split_half_recursive_custom_threshold(user_pub, def test_generate_conditions_split_half_single_owner(user_pub, user2_pub, user3_pub): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment expected_simple1 = Ed25519Fulfillment(public_key=user_pub) @@ -213,7 +213,7 @@ def test_generate_conditions_split_half_single_owner(user_pub, user2_pub, def test_generate_conditions_flat_ownage(user_pub, user2_pub, user3_pub): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment expected_simple1 = Ed25519Fulfillment(public_key=user_pub) @@ -230,7 +230,7 @@ def test_generate_conditions_flat_ownage(user_pub, user2_pub, user3_pub): def test_generate_conditions_single_owner(user_pub): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition from cryptoconditions import Ed25519Fulfillment expected = Ed25519Fulfillment(public_key=user_pub) @@ -240,7 +240,7 @@ def test_generate_conditions_single_owner(user_pub): def test_generate_conditions_single_owner_with_condition(user_pub): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition from cryptoconditions import Ed25519Fulfillment expected = Ed25519Fulfillment(public_key=user_pub) @@ -253,7 +253,7 @@ def test_generate_conditions_single_owner_with_condition(user_pub): @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 bigchaindb.common.transaction import Condition from cryptoconditions import (PreimageSha256Fulfillment, Ed25519Fulfillment, ThresholdSha256Fulfillment) @@ -276,7 +276,7 @@ def test_generate_threshold_condition_with_hashlock(user_pub, user2_pub, def test_generate_conditions_invalid_parameters(user_pub, user2_pub, user3_pub): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition with raises(ValueError): Condition.generate([]) @@ -289,7 +289,7 @@ def test_generate_conditions_invalid_parameters(user_pub, user2_pub, def test_invalid_transaction_initialization(): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset with raises(ValueError): Transaction(operation='invalid operation', asset=Asset()) @@ -319,7 +319,7 @@ def test_invalid_transaction_initialization(): def test_create_default_asset_on_tx_initialization(): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset tx = Transaction(Transaction.CREATE, None) expected = Asset() @@ -331,7 +331,7 @@ def test_create_default_asset_on_tx_initialization(): def test_transaction_serialization(user_ffill, user_cond, data, data_id): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset tx_id = 'l0l' timestamp = '66666666666' @@ -368,7 +368,7 @@ def test_transaction_serialization(user_ffill, user_cond, data, data_id): def test_transaction_deserialization(user_ffill, user_cond, data, data_id): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset timestamp = '66666666666' @@ -403,8 +403,8 @@ def test_transaction_deserialization(user_ffill, user_cond, data, data_id): def test_tx_serialization_with_incorrect_hash(utx): - from bigchaindb_common.transaction import Transaction - from bigchaindb_common.exceptions import InvalidHash + from bigchaindb.common.transaction import Transaction + from bigchaindb.common.exceptions import InvalidHash utx_dict = utx.to_dict() utx_dict['id'] = 'abc' @@ -419,7 +419,7 @@ def test_tx_serialization_with_incorrect_hash(utx): def test_invalid_fulfillment_initialization(user_ffill, user_pub): - from bigchaindb_common.transaction import Fulfillment + from bigchaindb.common.transaction import Fulfillment with raises(TypeError): Fulfillment(user_ffill, user_pub) @@ -428,14 +428,14 @@ def test_invalid_fulfillment_initialization(user_ffill, user_pub): def test_invalid_metadata_initialization(): - from bigchaindb_common.transaction import Metadata + from bigchaindb.common.transaction import Metadata with raises(TypeError): Metadata([]) def test_metadata_serialization(data, data_id): - from bigchaindb_common.transaction import Metadata + from bigchaindb.common.transaction import Metadata expected = { 'data': data, @@ -447,7 +447,7 @@ def test_metadata_serialization(data, data_id): def test_metadata_deserialization(data, data_id): - from bigchaindb_common.transaction import Metadata + from bigchaindb.common.transaction import Metadata expected = Metadata(data, data_id) metadata = Metadata.from_dict({'data': data, 'id': data_id}) @@ -456,7 +456,7 @@ def test_metadata_deserialization(data, data_id): def test_transaction_link_serialization(): - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common.transaction import TransactionLink tx_id = 'a transaction id' expected = { @@ -469,7 +469,7 @@ def test_transaction_link_serialization(): def test_transaction_link_serialization_with_empty_payload(): - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common.transaction import TransactionLink expected = None tx_link = TransactionLink() @@ -478,7 +478,7 @@ def test_transaction_link_serialization_with_empty_payload(): def test_transaction_link_deserialization(): - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common.transaction import TransactionLink tx_id = 'a transaction id' expected = TransactionLink(tx_id, 0) @@ -492,7 +492,7 @@ def test_transaction_link_deserialization(): def test_transaction_link_deserialization_with_empty_payload(): - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common.transaction import TransactionLink expected = TransactionLink() tx_link = TransactionLink.from_dict(None) @@ -501,7 +501,7 @@ def test_transaction_link_deserialization_with_empty_payload(): def test_cast_transaction_link_to_boolean(): - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common.transaction import TransactionLink assert bool(TransactionLink()) is False assert bool(TransactionLink('a', None)) is False @@ -511,7 +511,7 @@ def test_cast_transaction_link_to_boolean(): def test_add_fulfillment_to_tx(user_ffill): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset tx = Transaction(Transaction.CREATE, Asset(), [], []) tx.add_fulfillment(user_ffill) @@ -520,7 +520,7 @@ def test_add_fulfillment_to_tx(user_ffill): def test_add_fulfillment_to_tx_with_invalid_parameters(): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset tx = Transaction(Transaction.CREATE, Asset()) with raises(TypeError): @@ -528,7 +528,7 @@ def test_add_fulfillment_to_tx_with_invalid_parameters(): def test_add_condition_to_tx(user_cond): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset tx = Transaction(Transaction.CREATE, Asset()) tx.add_condition(user_cond) @@ -537,7 +537,7 @@ def test_add_condition_to_tx(user_cond): def test_add_condition_to_tx_with_invalid_parameters(): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset tx = Transaction(Transaction.CREATE, Asset(), [], []) with raises(TypeError): @@ -553,8 +553,8 @@ def test_sign_with_invalid_parameters(utx, user_priv): def test_validate_tx_simple_create_signature(user_ffill, user_cond, user_priv): from copy import deepcopy - from bigchaindb_common.crypto import SigningKey - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.crypto import SigningKey + from bigchaindb.common.transaction import Transaction, Asset tx = Transaction(Transaction.CREATE, Asset(), [user_ffill], [user_cond]) expected = deepcopy(user_cond) @@ -568,7 +568,7 @@ def test_validate_tx_simple_create_signature(user_ffill, user_cond, user_priv): def test_invoke_simple_signature_fulfillment_with_invalid_params(utx, user_ffill): - from bigchaindb_common.exceptions import KeypairMismatchException + from bigchaindb.common.exceptions import KeypairMismatchException with raises(KeypairMismatchException): invalid_key_pair = {'wrong_pub_key': 'wrong_priv_key'} @@ -580,7 +580,7 @@ def test_invoke_simple_signature_fulfillment_with_invalid_params(utx, def test_sign_threshold_with_invalid_params(utx, user_user2_threshold_ffill, user3_pub, user3_priv): - from bigchaindb_common.exceptions import KeypairMismatchException + from bigchaindb.common.exceptions import KeypairMismatchException with raises(KeypairMismatchException): utx._sign_threshold_signature_fulfillment(user_user2_threshold_ffill, @@ -596,7 +596,7 @@ def test_sign_threshold_with_invalid_params(utx, user_user2_threshold_ffill, def test_validate_fulfillment_with_invalid_parameters(utx): - from bigchaindb_common.transaction import Transaction + from bigchaindb.common.transaction import Transaction input_conditions = [cond.fulfillment.condition_uri for cond in utx.conditions] @@ -611,8 +611,8 @@ def test_validate_fulfillment_with_invalid_parameters(utx): def test_validate_multiple_fulfillments(user_ffill, user_cond, user_priv): from copy import deepcopy - from bigchaindb_common.crypto import SigningKey - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.crypto import SigningKey + from bigchaindb.common.transaction import Transaction, Asset tx = Transaction(Transaction.CREATE, Asset(), [user_ffill, deepcopy(user_ffill)], @@ -648,8 +648,8 @@ def test_validate_tx_threshold_create_signature(user_user2_threshold_ffill, user2_priv): from copy import deepcopy - from bigchaindb_common.crypto import SigningKey - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.crypto import SigningKey + from bigchaindb.common.transaction import Transaction, Asset tx = Transaction(Transaction.CREATE, Asset(), [user_user2_threshold_ffill], [user_user2_threshold_cond]) @@ -670,7 +670,7 @@ def test_multiple_fulfillment_validation_of_transfer_tx(user_ffill, user_cond, user2_priv, user3_pub, user3_priv): from copy import deepcopy - from bigchaindb_common.transaction import (Transaction, TransactionLink, + from bigchaindb.common.transaction import (Transaction, TransactionLink, Fulfillment, Condition, Asset) from cryptoconditions import Ed25519Fulfillment @@ -697,7 +697,7 @@ def test_validate_fulfillments_of_transfer_tx_with_invalid_params(transfer_tx, utx, user2_pub, user_priv): - from bigchaindb_common.transaction import Condition + from bigchaindb.common.transaction import Condition from cryptoconditions import Ed25519Fulfillment invalid_cond = Condition(Ed25519Fulfillment.from_uri('cf:0:'), ['invalid']) @@ -723,7 +723,7 @@ def test_validate_fulfillments_of_transfer_tx_with_invalid_params(transfer_tx, def test_create_create_transaction_single_io(user_cond, user_pub, data, data_id): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset expected = { 'transaction': { @@ -764,7 +764,7 @@ 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 + from bigchaindb.common.transaction import Transaction, Asset tx = Transaction.create([user_pub], [user_pub], data, Asset()) tx = tx.sign([user_priv]) @@ -775,7 +775,7 @@ def test_validate_single_io_create_transaction(user_pub, user_priv, data): # 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 expected = { 'transaction': { @@ -820,7 +820,7 @@ def test_create_create_transaction_multiple_io(user_cond, user2_cond, user_pub, # 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 tx = Transaction.create([user_pub, user2_pub], [user_pub, user2_pub], {'message': 'hello'}) @@ -832,7 +832,7 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, user_user2_threshold_cond, user_user2_threshold_ffill, data, data_id): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset expected = { 'transaction': { @@ -874,7 +874,7 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, def test_validate_threshold_create_transaction(user_pub, user_priv, user2_pub, data): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset tx = Transaction.create([user_pub], [user_pub, user2_pub], data, Asset()) tx = tx.sign([user_priv]) @@ -883,7 +883,7 @@ def test_validate_threshold_create_transaction(user_pub, user_priv, user2_pub, def test_create_create_transaction_hashlock(user_pub, data, data_id): from cryptoconditions import PreimageSha256Fulfillment - from bigchaindb_common.transaction import Transaction, Condition, Asset + from bigchaindb.common.transaction import Transaction, Condition, Asset secret = b'much secret, wow' hashlock = PreimageSha256Fulfillment(preimage=secret).condition_uri @@ -928,7 +928,7 @@ def test_create_create_transaction_hashlock(user_pub, data, data_id): def test_validate_hashlock_create_transaction(user_pub, user_priv, data): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset tx = Transaction.create([user_pub], [], data, Asset(), b'much secret, wow') tx = tx.sign([user_priv]) @@ -936,7 +936,7 @@ def test_validate_hashlock_create_transaction(user_pub, user_priv, data): def test_create_create_transaction_with_invalid_parameters(): - from bigchaindb_common.transaction import Transaction + from bigchaindb.common.transaction import Transaction with raises(TypeError): Transaction.create('not a list') @@ -965,9 +965,9 @@ def test_conditions_to_inputs(tx): def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, user2_cond, user_priv, data_id): from copy import deepcopy - from bigchaindb_common.crypto import SigningKey - from bigchaindb_common.transaction import Transaction, Asset - from bigchaindb_common.util import serialize + from bigchaindb.common.crypto import SigningKey + from bigchaindb.common.transaction import Transaction, Asset + from bigchaindb.common.util import serialize expected = { 'transaction': { @@ -1018,7 +1018,7 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, 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 tx1 = Transaction.create([user_pub], [user_pub], {'message': 'hello'}) tx1 = tx1.sign([user_priv]) @@ -1080,7 +1080,7 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, def test_create_transfer_with_invalid_parameters(): - from bigchaindb_common.transaction import Transaction, Asset + from bigchaindb.common.transaction import Transaction, Asset with raises(TypeError): Transaction.transfer({}, [], Asset()) @@ -1093,14 +1093,14 @@ def test_create_transfer_with_invalid_parameters(): def test_cant_add_empty_condition(): - from bigchaindb_common.transaction import Transaction + from bigchaindb.common.transaction import Transaction tx = Transaction(Transaction.CREATE, None) with raises(TypeError): tx.add_condition(None) def test_cant_add_empty_fulfillment(): - from bigchaindb_common.transaction import Transaction + from bigchaindb.common.transaction import Transaction tx = Transaction(Transaction.CREATE, None) with raises(TypeError): tx.add_fulfillment(None) diff --git a/tests/db/conftest.py b/tests/db/conftest.py index ec4afc9b..f55a4c34 100644 --- a/tests/db/conftest.py +++ b/tests/db/conftest.py @@ -11,7 +11,7 @@ import rethinkdb as r from bigchaindb import Bigchain from bigchaindb.db import get_conn -from bigchaindb_common import crypto +from bigchaindb.common import crypto USER2_SK, USER2_VK = crypto.generate_key_pair() @@ -107,7 +107,7 @@ def cleanup_tables(request, node_config): @pytest.fixture def inputs(user_vk): from bigchaindb.models import Transaction - from bigchaindb_common.exceptions import GenesisBlockAlreadyExistsError + from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError # 1. create the genesis block b = Bigchain() try: @@ -144,7 +144,7 @@ def user2_vk(): @pytest.fixture def inputs_shared(user_vk, user2_vk): from bigchaindb.models import Transaction - from bigchaindb_common.exceptions import GenesisBlockAlreadyExistsError + from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError # 1. create the genesis block b = Bigchain() try: diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 48a87fdc..f0a88c44 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -30,9 +30,9 @@ def dummy_block(): class TestBigchainApi(object): def test_get_last_voted_block_cyclic_blockchain(self, b, monkeypatch): - from bigchaindb_common.crypto import SigningKey - from bigchaindb_common.exceptions import CyclicBlockchainError - from bigchaindb_common.util import serialize + from bigchaindb.common.crypto import SigningKey + from bigchaindb.common.exceptions import CyclicBlockchainError + from bigchaindb.common.util import serialize from bigchaindb.models import Transaction b.create_genesis_block() @@ -55,7 +55,7 @@ class TestBigchainApi(object): def test_try_voting_while_constructing_cyclic_blockchain(self, b, monkeypatch): - from bigchaindb_common.exceptions import CyclicBlockchainError + from bigchaindb.common.exceptions import CyclicBlockchainError from bigchaindb.models import Transaction b.create_genesis_block() @@ -94,7 +94,7 @@ class TestBigchainApi(object): assert not matches def test_get_spent_with_double_spend(self, b, monkeypatch): - from bigchaindb_common.exceptions import DoubleSpend + from bigchaindb.common.exceptions import DoubleSpend from bigchaindb.models import Transaction b.create_genesis_block() @@ -128,7 +128,7 @@ class TestBigchainApi(object): b.get_spent(tx.id, 0) def test_get_block_status_for_tx_with_double_spend(self, b, monkeypatch): - from bigchaindb_common.exceptions import DoubleSpend + from bigchaindb.common.exceptions import DoubleSpend from bigchaindb.models import Transaction b.create_genesis_block() @@ -277,7 +277,7 @@ class TestBigchainApi(object): def test_create_genesis_block_fails_if_table_not_empty(self, b): import rethinkdb as r - from bigchaindb_common.exceptions import GenesisBlockAlreadyExistsError + from bigchaindb.common.exceptions import GenesisBlockAlreadyExistsError from bigchaindb.util import is_genesis_block from bigchaindb.db.utils import get_conn @@ -333,7 +333,7 @@ class TestBigchainApi(object): assert prev_block_id == last_block['id'] def test_create_empty_block(self, b): - from bigchaindb_common.exceptions import OperationError + from bigchaindb.common.exceptions import OperationError with pytest.raises(OperationError) as excinfo: b.create_block([]) @@ -433,7 +433,7 @@ class TestBigchainApi(object): def test_more_votes_than_voters(self, b): import rethinkdb as r - from bigchaindb_common.exceptions import MultipleVotesError + from bigchaindb.common.exceptions import MultipleVotesError from bigchaindb.db.utils import get_conn b.create_genesis_block() @@ -453,7 +453,7 @@ class TestBigchainApi(object): def test_multiple_votes_single_node(self, b): import rethinkdb as r - from bigchaindb_common.exceptions import MultipleVotesError + from bigchaindb.common.exceptions import MultipleVotesError from bigchaindb.db.utils import get_conn genesis = b.create_genesis_block() @@ -475,7 +475,7 @@ class TestBigchainApi(object): def test_improper_vote_error(selfs, b): import rethinkdb as r - from bigchaindb_common.exceptions import ImproperVoteError + from bigchaindb.common.exceptions import ImproperVoteError from bigchaindb.db.utils import get_conn b.create_genesis_block() @@ -512,7 +512,7 @@ class TestBigchainApi(object): @pytest.mark.usefixtures('inputs') def test_assign_transaction_multiple_nodes(self, b, user_vk, user_sk): import rethinkdb as r - from bigchaindb_common.crypto import generate_key_pair + from bigchaindb.common.crypto import generate_key_pair from bigchaindb.models import Transaction from bigchaindb.db.utils import get_conn @@ -539,8 +539,8 @@ class TestBigchainApi(object): @pytest.mark.usefixtures('inputs') def test_non_create_input_not_found(self, b, user_vk): from cryptoconditions import Ed25519Fulfillment - from bigchaindb_common.exceptions import TransactionDoesNotExist - from bigchaindb_common.transaction import (Fulfillment, Asset, + from bigchaindb.common.exceptions import TransactionDoesNotExist + from bigchaindb.common.transaction import (Fulfillment, Asset, TransactionLink) from bigchaindb.models import Transaction from bigchaindb import Bigchain @@ -557,7 +557,7 @@ class TestBigchainApi(object): class TestTransactionValidation(object): def test_create_operation_with_inputs(self, b, user_vk, create_tx): - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common.transaction import TransactionLink # Manipulate fulfillment so that it has a `tx_input` defined even # though it shouldn't have one @@ -575,8 +575,8 @@ class TestTransactionValidation(object): assert excinfo.value.args[0] == 'Only `CREATE` transactions can have null inputs' def test_non_create_input_not_found(self, b, user_vk, signed_transfer_tx): - from bigchaindb_common.exceptions import TransactionDoesNotExist - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common.exceptions import TransactionDoesNotExist + from bigchaindb.common.transaction import TransactionLink signed_transfer_tx.fulfillments[0].tx_input = TransactionLink('c', 0) with pytest.raises(TransactionDoesNotExist): @@ -584,8 +584,8 @@ class TestTransactionValidation(object): @pytest.mark.usefixtures('inputs') def test_non_create_valid_input_wrong_owner(self, b, user_vk): - from bigchaindb_common.crypto import generate_key_pair - from bigchaindb_common.exceptions import InvalidSignature + from bigchaindb.common.crypto import generate_key_pair + from bigchaindb.common.exceptions import InvalidSignature from bigchaindb.models import Transaction input_tx = b.get_owned_ids(user_vk).pop() @@ -602,7 +602,7 @@ class TestTransactionValidation(object): @pytest.mark.usefixtures('inputs') def test_non_create_double_spend(self, b, signed_create_tx, signed_transfer_tx): - from bigchaindb_common.exceptions import DoubleSpend + from bigchaindb.common.exceptions import DoubleSpend block1 = b.create_block([signed_create_tx]) b.write_block(block1) @@ -652,7 +652,7 @@ class TestTransactionValidation(object): @pytest.mark.usefixtures('inputs') def test_fulfillment_not_in_valid_block(self, b, user_vk, user_sk): from bigchaindb.models import Transaction - from bigchaindb_common.exceptions import FulfillmentNotInValidBlock + from bigchaindb.common.exceptions import FulfillmentNotInValidBlock input_tx = b.get_owned_ids(user_vk).pop() input_tx = b.get_transaction(input_tx.txid) @@ -681,9 +681,9 @@ class TestBlockValidation(object): @pytest.mark.skipif(reason='Separated tx validation from block creation.') @pytest.mark.usefixtures('inputs') def test_invalid_transactions_in_block(self, b, user_vk): - from bigchaindb_common import crypto - from bigchaindb_common.exceptions import TransactionOwnerError - from bigchaindb_common.util import gen_timestamp + from bigchaindb.common import crypto + from bigchaindb.common.exceptions import TransactionOwnerError + from bigchaindb.common.util import gen_timestamp from bigchaindb import util @@ -722,8 +722,8 @@ class TestBlockValidation(object): assert excinfo.value.args[0] == 'owner_before `a` does not own the input `{}`'.format(valid_input) def test_invalid_signature(self, b): - from bigchaindb_common.exceptions import InvalidSignature - from bigchaindb_common import crypto + from bigchaindb.common.exceptions import InvalidSignature + from bigchaindb.common import crypto # create a valid block block = dummy_block() @@ -736,8 +736,8 @@ class TestBlockValidation(object): b.validate_block(block) def test_invalid_node_pubkey(self, b): - from bigchaindb_common.exceptions import OperationError - from bigchaindb_common import crypto + from bigchaindb.common.exceptions import OperationError + from bigchaindb.common import crypto # blocks can only be created by a federation node # create a valid block @@ -761,7 +761,7 @@ class TestBlockValidation(object): class TestMultipleInputs(object): def test_transfer_single_owner_single_input(self, b, inputs, user_vk, user_sk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -781,7 +781,7 @@ class TestMultipleInputs(object): 'multiple assets')) @pytest.mark.usefixtures('inputs') def test_transfer_single_owners_multiple_inputs(self, b, user_sk, user_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -804,7 +804,7 @@ class TestMultipleInputs(object): def test_transfer_single_owners_single_input_from_multiple_outputs(self, b, user_sk, user_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -843,7 +843,7 @@ class TestMultipleInputs(object): user_sk, user_vk, inputs): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -866,7 +866,7 @@ class TestMultipleInputs(object): def test_single_owner_before_multiple_owners_after_multiple_inputs(self, b, user_sk, user_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -897,7 +897,7 @@ class TestMultipleInputs(object): def test_multiple_owners_before_single_owner_after_single_input(self, b, user_sk, user_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -930,7 +930,7 @@ class TestMultipleInputs(object): @pytest.mark.usefixtures('inputs_shared') def test_multiple_owners_before_single_owner_after_multiple_inputs(self, b, user_sk, user_vk, user2_vk, user2_sk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction # create a new users @@ -951,7 +951,7 @@ class TestMultipleInputs(object): def test_multiple_owners_before_multiple_owners_after_single_input(self, b, user_sk, user_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -985,7 +985,7 @@ class TestMultipleInputs(object): def test_multiple_owners_before_multiple_owners_after_multiple_inputs(self, b, user_sk, user_vk, user2_sk, user2_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction # create a new users @@ -1004,8 +1004,8 @@ class TestMultipleInputs(object): assert len(tx.conditions) == len(inputs) def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_vk): - from bigchaindb_common import crypto - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common import crypto + from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -1033,8 +1033,8 @@ class TestMultipleInputs(object): def test_get_owned_ids_single_tx_single_output_invalid_block(self, b, user_sk, user_vk): - from bigchaindb_common import crypto - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common import crypto + from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction genesis = b.create_genesis_block() @@ -1078,8 +1078,8 @@ class TestMultipleInputs(object): def test_get_owned_ids_single_tx_multiple_outputs(self, b, user_sk, user_vk): import random - from bigchaindb_common import crypto - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common import crypto + from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -1115,8 +1115,8 @@ class TestMultipleInputs(object): TransactionLink(tx.id, 1)] def test_get_owned_ids_multiple_owners(self, b, user_sk, user_vk): - from bigchaindb_common import crypto - from bigchaindb_common.transaction import TransactionLink + from bigchaindb.common import crypto + from bigchaindb.common.transaction import TransactionLink from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -1145,7 +1145,7 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == [] def test_get_spent_single_tx_single_output(self, b, user_sk, user_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() @@ -1173,7 +1173,7 @@ class TestMultipleInputs(object): assert spent_inputs_user1 == tx def test_get_spent_single_tx_single_output_invalid_block(self, b, user_sk, user_vk): - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction genesis = b.create_genesis_block() @@ -1219,7 +1219,7 @@ class TestMultipleInputs(object): 'multiple assets')) def test_get_spent_single_tx_multiple_outputs(self, b, user_sk, user_vk): import random - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction # create a new users @@ -1259,7 +1259,7 @@ class TestMultipleInputs(object): def test_get_spent_multiple_owners(self, b, user_sk, user_vk): import random - from bigchaindb_common import crypto + from bigchaindb.common import crypto from bigchaindb.models import Transaction user2_sk, user2_vk = crypto.generate_key_pair() diff --git a/tests/db/test_utils.py b/tests/db/test_utils.py index 56f09ac5..75d14c02 100644 --- a/tests/db/test_utils.py +++ b/tests/db/test_utils.py @@ -1,6 +1,6 @@ import builtins -from bigchaindb_common import exceptions +from bigchaindb.common import exceptions import pytest import rethinkdb as r diff --git a/tests/doc/run_doc_python_server_api_examples.py b/tests/doc/run_doc_python_server_api_examples.py index b1f0ed9b..a7bf89d5 100644 --- a/tests/doc/run_doc_python_server_api_examples.py +++ b/tests/doc/run_doc_python_server_api_examples.py @@ -2,7 +2,7 @@ import json from time import sleep import cryptoconditions as cc -from bigchaindb_common.util import gen_timestamp +from bigchaindb.common.util import gen_timestamp from bigchaindb import Bigchain, util, crypto, exceptions diff --git a/tests/pipelines/test_election.py b/tests/pipelines/test_election.py index 7887b4c9..669a75cb 100644 --- a/tests/pipelines/test_election.py +++ b/tests/pipelines/test_election.py @@ -1,7 +1,7 @@ import time from unittest.mock import patch -from bigchaindb_common import crypto +from bigchaindb.common import crypto import rethinkdb as r from multipipes import Pipe, Pipeline diff --git a/tests/pipelines/test_vote.py b/tests/pipelines/test_vote.py index 52ac117e..5bd0eb52 100644 --- a/tests/pipelines/test_vote.py +++ b/tests/pipelines/test_vote.py @@ -19,8 +19,8 @@ def dummy_block(b): def test_vote_creation_valid(b): - from bigchaindb_common import crypto - from bigchaindb_common.util import serialize + from bigchaindb.common import crypto + from bigchaindb.common.util import serialize # create valid block block = dummy_block(b) @@ -38,8 +38,8 @@ def test_vote_creation_valid(b): def test_vote_creation_invalid(b): - from bigchaindb_common import crypto - from bigchaindb_common.util import serialize + from bigchaindb.common import crypto + from bigchaindb.common.util import serialize # create valid block block = dummy_block(b) @@ -154,7 +154,7 @@ def test_vote_accumulates_transactions(b): def test_valid_block_voting_sequential(b, monkeypatch): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.pipelines import vote monkeypatch.setattr('time.time', lambda: 1) @@ -182,7 +182,7 @@ def test_valid_block_voting_sequential(b, monkeypatch): def test_valid_block_voting_multiprocessing(b, monkeypatch): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.pipelines import vote inpipe = Pipe() @@ -216,7 +216,7 @@ def test_valid_block_voting_multiprocessing(b, monkeypatch): def test_valid_block_voting_with_create_transaction(b, monkeypatch): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.models import Transaction from bigchaindb.pipelines import vote @@ -257,7 +257,7 @@ def test_valid_block_voting_with_create_transaction(b, monkeypatch): def test_valid_block_voting_with_transfer_transactions(monkeypatch, b): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.models import Transaction from bigchaindb.pipelines import vote @@ -325,7 +325,7 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b): def test_unsigned_tx_in_block_voting(monkeypatch, b, user_vk): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.models import Transaction from bigchaindb.pipelines import vote @@ -362,7 +362,7 @@ def test_unsigned_tx_in_block_voting(monkeypatch, b, user_vk): def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_vk): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.models import Transaction from bigchaindb.pipelines import vote @@ -401,7 +401,7 @@ def test_invalid_id_tx_in_block_voting(monkeypatch, b, user_vk): def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_vk): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.models import Transaction from bigchaindb.pipelines import vote @@ -440,7 +440,7 @@ def test_invalid_content_in_tx_in_block_voting(monkeypatch, b, user_vk): def test_invalid_block_voting(monkeypatch, b, user_vk): - from bigchaindb_common import crypto, util + from bigchaindb.common import crypto, util from bigchaindb.pipelines import vote inpipe = Pipe() diff --git a/tests/test_commands.py b/tests/test_commands.py index 6cb19d48..95fc4179 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -22,7 +22,7 @@ def mock_write_config(monkeypatch): @pytest.fixture def mock_db_init_with_existing_db(monkeypatch): from bigchaindb import db - from bigchaindb_common.exceptions import DatabaseAlreadyExists + from bigchaindb.common.exceptions import DatabaseAlreadyExists def mockreturn(): raise DatabaseAlreadyExists @@ -48,7 +48,7 @@ def mock_rethink_db_drop(monkeypatch): @pytest.fixture def mock_generate_key_pair(monkeypatch): - monkeypatch.setattr('bigchaindb_common.crypto.generate_key_pair', lambda: ('privkey', 'pubkey')) + monkeypatch.setattr('bigchaindb.common.crypto.generate_key_pair', lambda: ('privkey', 'pubkey')) @pytest.fixture @@ -283,14 +283,14 @@ def test_start_rethinkdb_returns_a_process_when_successful(mock_popen): @patch('subprocess.Popen') def test_start_rethinkdb_exits_when_cannot_start(mock_popen): - from bigchaindb_common import exceptions + from bigchaindb.common import exceptions from bigchaindb.commands import utils mock_popen.return_value = Mock(stdout=['Nopety nope']) with pytest.raises(exceptions.StartupError): utils.start_rethinkdb() -@patch('bigchaindb_common.crypto.generate_key_pair', +@patch('bigchaindb.common.crypto.generate_key_pair', return_value=('private_key', 'public_key')) def test_allow_temp_keypair_generates_one_on_the_fly(mock_gen_keypair, mock_processes_start, @@ -307,7 +307,7 @@ def test_allow_temp_keypair_generates_one_on_the_fly(mock_gen_keypair, assert bigchaindb.config['keypair']['public'] == 'public_key' -@patch('bigchaindb_common.crypto.generate_key_pair', +@patch('bigchaindb.common.crypto.generate_key_pair', return_value=('private_key', 'public_key')) def test_allow_temp_keypair_doesnt_override_if_keypair_found(mock_gen_keypair, mock_processes_start, diff --git a/tests/test_config_utils.py b/tests/test_config_utils.py index 5ffd5b9a..ea0a0528 100644 --- a/tests/test_config_utils.py +++ b/tests/test_config_utils.py @@ -42,7 +42,7 @@ def test_bigchain_instance_is_initialized_when_conf_provided(): def test_bigchain_instance_raises_when_not_configured(monkeypatch): from bigchaindb import config_utils - from bigchaindb_common import exceptions + from bigchaindb.common import exceptions assert 'CONFIGURED' not in bigchaindb.config # We need to disable ``bigchaindb.config_utils.autoconfigure`` to avoid reading @@ -204,7 +204,7 @@ def test_file_config(): def test_invalid_file_config(): from bigchaindb.config_utils import file_config - from bigchaindb_common import exceptions + from bigchaindb.common import exceptions with patch('builtins.open', mock_open(read_data='{_INVALID_JSON_}')): with pytest.raises(exceptions.ConfigurationError): file_config() diff --git a/tests/test_models.py b/tests/test_models.py index faef5353..5033aebb 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -37,8 +37,8 @@ class TestBlockModel(object): 'not a list or None') def test_block_serialization(self, b): - from bigchaindb_common.crypto import hash_data - from bigchaindb_common.util import gen_timestamp, serialize + from bigchaindb.common.crypto import hash_data + from bigchaindb.common.util import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transactions = [Transaction.create([b.me], [b.me])] @@ -61,7 +61,7 @@ class TestBlockModel(object): assert block.to_dict() == expected def test_block_invalid_serializaton(self): - from bigchaindb_common.exceptions import OperationError + from bigchaindb.common.exceptions import OperationError from bigchaindb.models import Block block = Block([]) @@ -69,8 +69,8 @@ class TestBlockModel(object): block.to_dict() def test_block_deserialization(self, b): - from bigchaindb_common.crypto import hash_data - from bigchaindb_common.util import gen_timestamp, serialize + from bigchaindb.common.crypto import hash_data + from bigchaindb.common.util import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transactions = [Transaction.create([b.me], [b.me])] @@ -94,7 +94,7 @@ class TestBlockModel(object): assert expected == Block.from_dict(block_body) def test_block_invalid_id_deserialization(self, b): - from bigchaindb_common.exceptions import InvalidHash + from bigchaindb.common.exceptions import InvalidHash from bigchaindb.models import Block block = { @@ -108,9 +108,9 @@ class TestBlockModel(object): Block.from_dict(block) def test_block_invalid_signature_deserialization(self, b): - from bigchaindb_common.crypto import hash_data - from bigchaindb_common.exceptions import InvalidSignature - from bigchaindb_common.util import gen_timestamp, serialize + from bigchaindb.common.crypto import hash_data + from bigchaindb.common.exceptions import InvalidSignature + from bigchaindb.common.util import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transactions = [Transaction.create([b.me], [b.me])] @@ -142,8 +142,8 @@ class TestBlockModel(object): assert Block(transactions) == Block(transactions) def test_sign_block(self, b): - from bigchaindb_common.crypto import SigningKey, VerifyingKey - from bigchaindb_common.util import gen_timestamp, serialize + from bigchaindb.common.crypto import SigningKey, VerifyingKey + from bigchaindb.common.util import gen_timestamp, serialize from bigchaindb.models import Block, Transaction transactions = [Transaction.create([b.me], [b.me])] diff --git a/tests/web/test_basic_views.py b/tests/web/test_basic_views.py index b941011a..00e40a37 100644 --- a/tests/web/test_basic_views.py +++ b/tests/web/test_basic_views.py @@ -1,7 +1,7 @@ import json import pytest -from bigchaindb_common import crypto +from bigchaindb.common import crypto TX_ENDPOINT = '/api/v1/transactions/'