diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 2747c359..2612c1ed 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -3,7 +3,6 @@ import random import json import rapidjson - import bigchaindb from bigchaindb import util from bigchaindb import config_utils @@ -11,7 +10,6 @@ from bigchaindb import exceptions from bigchaindb import crypto from bigchaindb.monitor import Monitor - monitor = Monitor() @@ -150,7 +148,7 @@ class Bigchain(object): If no transaction with that `txid` was found it returns `None` """ - response = r.table('bigchain').concat_map(lambda doc: doc['block']['transactions'])\ + response = r.table('bigchain').concat_map(lambda doc: doc['block']['transactions']) \ .filter(lambda transaction: transaction['id'] == txid).run(self.conn) # transaction ids should be unique @@ -181,8 +179,8 @@ class Bigchain(object): returns `None` """ - cursor = r.table('bigchain')\ - .get_all(payload_hash, index='payload_hash')\ + cursor = r.table('bigchain') \ + .get_all(payload_hash, index='payload_hash') \ .run(self.conn) transactions = list(cursor) @@ -202,7 +200,7 @@ class Bigchain(object): """ # checks if an input was already spent # checks if the bigchain has any transaction with input `transaction_id` - response = r.table('bigchain').concat_map(lambda doc: doc['block']['transactions'])\ + response = r.table('bigchain').concat_map(lambda doc: doc['block']['transactions']) \ .filter(lambda transaction: transaction['transaction']['inputs'].contains(txid)).run(self.conn) # a transaction_id should have been spent at most one time @@ -226,11 +224,14 @@ class Bigchain(object): list: list of `txids` currently owned by `owner` """ - response = r.table('bigchain')\ - .concat_map(lambda doc: doc['block']['transactions'])\ - .filter({'transaction': {'new_owner': owner}})\ - .pluck('id')['id']\ - .run(self.conn) + response = r.table('bigchain') \ + .concat_map(lambda doc: doc['block']['transactions']) \ + .filter(lambda tx: tx['transaction']['conditions'] + .contains(lambda c: c['new_owners'] + .contains(owner))) \ + .pluck('id')['id'] \ + .run(self.conn) + owned = [] # remove all inputs already spent @@ -439,37 +440,37 @@ class Bigchain(object): if 'block_number' not in block: update['block_number'] = block_number - r.table('bigchain')\ - .get(vote['vote']['voting_for_block'])\ - .update(update)\ - .run(self.conn) + r.table('bigchain') \ + .get(vote['vote']['voting_for_block']) \ + .update(update) \ + .run(self.conn) def get_last_voted_block(self): """Returns the last block that this node voted on.""" # query bigchain for all blocks this node is a voter but didn't voted on - last_voted = r.table('bigchain')\ - .filter(r.row['block']['voters'].contains(self.me))\ - .filter(lambda doc: doc['votes'].contains(lambda vote: vote['node_pubkey'] == self.me))\ - .order_by(r.desc('block_number'))\ - .limit(1)\ + last_voted = r.table('bigchain') \ + .filter(r.row['block']['voters'].contains(self.me)) \ + .filter(lambda doc: doc['votes'].contains(lambda vote: vote['node_pubkey'] == self.me)) \ + .order_by(r.desc('block_number')) \ + .limit(1) \ .run(self.conn) # return last vote if last vote exists else return Genesis block last_voted = list(last_voted) if not last_voted: return list(r.table('bigchain') - .filter(r.row['block_number'] == 0) - .run(self.conn))[0] + .filter(r.row['block_number'] == 0) + .run(self.conn))[0] return last_voted[0] def get_unvoted_blocks(self): """Return all the blocks that has not been voted by this node.""" - unvoted = r.table('bigchain')\ - .filter(lambda doc: doc['votes'].contains(lambda vote: vote['node_pubkey'] == self.me).not_())\ - .order_by(r.asc((r.row['block']['timestamp'])))\ + unvoted = r.table('bigchain') \ + .filter(lambda doc: doc['votes'].contains(lambda vote: vote['node_pubkey'] == self.me).not_()) \ + .order_by(r.asc((r.row['block']['timestamp']))) \ .run(self.conn) if unvoted and unvoted[0].get('block_number') == 0: diff --git a/bigchaindb/util.py b/bigchaindb/util.py index b377fe31..d6f87751 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -144,6 +144,9 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None): }, } """ + current_owners = current_owners if isinstance(current_owners, list) else [current_owners] + new_owners = new_owners if isinstance(new_owners, list) else [new_owners] + inputs = inputs if isinstance(inputs, list) else [inputs] # handle payload data = None @@ -159,17 +162,14 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None): # handle inputs fulfillments = [] - current_owners = current_owners if isinstance(current_owners, list) else [current_owners] + # transfer if inputs: for fid, inp in enumerate(inputs): - fulfillment = ThresholdSha256Fulfillment(threshold=len(current_owners)) - for current_owner in current_owners: - fulfillment.add_subfulfillment(Ed25519Fulfillment(public_key=current_owner)) fulfillments.append({ 'current_owners': current_owners, 'input': inp, - 'fulfillment': fulfillment.serialize_json(), + 'fulfillment': None, 'fid': fid }) # create @@ -184,9 +184,18 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None): # handle outputs conditions = [] for fulfillment in fulfillments: + if len(new_owners) > 1: + for new_owner in new_owners: + condition = ThresholdSha256Fulfillment(threshold=len(new_owners)) + condition.add_subfulfillment(Ed25519Fulfillment(public_key=new_owner)) + elif len(new_owners) == 1: + condition = Ed25519Fulfillment(public_key=new_owners[0]) conditions.append({ 'new_owners': new_owners, - 'condition': None, + 'condition': { + 'details': json.loads(condition.serialize_json()), + 'uri': condition.condition.serialize_uri() + }, 'cid': fulfillment['fid'] }) @@ -220,7 +229,7 @@ def sign_tx(transaction, private_key): Args: transaction (dict): transaction to sign. - private_key (str): base58 encoded private key to create a signature of the transaction. + private_key (base58 str): base58 encoded private key to create a signature of the transaction. Returns: dict: transaction with the `fulfillment` fields populated. diff --git a/setup.py b/setup.py index 1d275304..378e8965 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ setup( 'rethinkdb==2.2.0.post4', 'pysha3==0.3', 'pytz==2015.7', - 'cryptoconditions==0.1.4', + 'cryptoconditions==0.1.5', 'statsd==3.2.1', 'python-rapidjson==0.0.6', 'logstats==0.2.1', diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index c68dd794..01c80438 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -23,17 +23,24 @@ def test_remove_unclosed_sockets(): class TestBigchainApi(object): - def test_create_transaction(self, b, user_sk): + def test_create_transaction_create(self, b, user_sk): tx = b.create_transaction(b.me, user_sk, None, 'CREATE') - assert sorted(tx) == sorted(['id', 'transaction']) - assert sorted(tx['transaction']) == sorted(['current_owner', 'new_owner', 'input', 'operation', - 'timestamp', 'data']) + assert sorted(tx) == sorted(['id', 'transaction', 'version']) + assert sorted(tx['transaction']) == sorted(['conditions', 'data', 'fulfillments', 'operation', 'timestamp']) def test_create_transaction_with_unsupported_payload_raises(self, b): with pytest.raises(TypeError): b.create_transaction('a', 'b', 'c', 'd', payload=[]) + @pytest.mark.usefixtures('inputs') + def test_create_transaction_transfer(self, b, user_vk, user_sk): + input_tx = b.get_owned_ids(user_vk).pop() + tx = b.create_transaction(b.me, user_sk, input_tx, 'TRANSFER') + + assert sorted(tx) == sorted(['id', 'transaction', 'version']) + assert sorted(tx['transaction']) == sorted(['conditions', 'data', 'fulfillments', 'operation', 'timestamp']) + def test_transaction_hash(self, b): payload = {'cats': 'are awesome'} tx = b.create_transaction('a', 'b', 'c', 'd', payload)