tx with conditions

This commit is contained in:
diminator 2016-04-07 15:41:06 +02:00
parent ce945e3409
commit b76bd72ea0
No known key found for this signature in database
GPG Key ID: C3D8590E6D0D439A
4 changed files with 54 additions and 37 deletions

View File

@ -3,7 +3,6 @@ import random
import json import json
import rapidjson import rapidjson
import bigchaindb import bigchaindb
from bigchaindb import util from bigchaindb import util
from bigchaindb import config_utils from bigchaindb import config_utils
@ -11,7 +10,6 @@ from bigchaindb import exceptions
from bigchaindb import crypto from bigchaindb import crypto
from bigchaindb.monitor import Monitor from bigchaindb.monitor import Monitor
monitor = Monitor() monitor = Monitor()
@ -150,7 +148,7 @@ class Bigchain(object):
If no transaction with that `txid` was found it returns `None` 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) .filter(lambda transaction: transaction['id'] == txid).run(self.conn)
# transaction ids should be unique # transaction ids should be unique
@ -181,8 +179,8 @@ class Bigchain(object):
returns `None` returns `None`
""" """
cursor = r.table('bigchain')\ cursor = r.table('bigchain') \
.get_all(payload_hash, index='payload_hash')\ .get_all(payload_hash, index='payload_hash') \
.run(self.conn) .run(self.conn)
transactions = list(cursor) transactions = list(cursor)
@ -202,7 +200,7 @@ class Bigchain(object):
""" """
# checks if an input was already spent # checks if an input was already spent
# checks if the bigchain has any transaction with input `transaction_id` # 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) .filter(lambda transaction: transaction['transaction']['inputs'].contains(txid)).run(self.conn)
# a transaction_id should have been spent at most one time # 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` list: list of `txids` currently owned by `owner`
""" """
response = r.table('bigchain')\ response = r.table('bigchain') \
.concat_map(lambda doc: doc['block']['transactions'])\ .concat_map(lambda doc: doc['block']['transactions']) \
.filter({'transaction': {'new_owner': owner}})\ .filter(lambda tx: tx['transaction']['conditions']
.pluck('id')['id']\ .contains(lambda c: c['new_owners']
.run(self.conn) .contains(owner))) \
.pluck('id')['id'] \
.run(self.conn)
owned = [] owned = []
# remove all inputs already spent # remove all inputs already spent
@ -439,37 +440,37 @@ class Bigchain(object):
if 'block_number' not in block: if 'block_number' not in block:
update['block_number'] = block_number update['block_number'] = block_number
r.table('bigchain')\ r.table('bigchain') \
.get(vote['vote']['voting_for_block'])\ .get(vote['vote']['voting_for_block']) \
.update(update)\ .update(update) \
.run(self.conn) .run(self.conn)
def get_last_voted_block(self): def get_last_voted_block(self):
"""Returns the last block that this node voted on.""" """Returns the last block that this node voted on."""
# query bigchain for all blocks this node is a voter but didn't voted on # query bigchain for all blocks this node is a voter but didn't voted on
last_voted = r.table('bigchain')\ last_voted = r.table('bigchain') \
.filter(r.row['block']['voters'].contains(self.me))\ .filter(r.row['block']['voters'].contains(self.me)) \
.filter(lambda doc: doc['votes'].contains(lambda vote: vote['node_pubkey'] == self.me))\ .filter(lambda doc: doc['votes'].contains(lambda vote: vote['node_pubkey'] == self.me)) \
.order_by(r.desc('block_number'))\ .order_by(r.desc('block_number')) \
.limit(1)\ .limit(1) \
.run(self.conn) .run(self.conn)
# return last vote if last vote exists else return Genesis block # return last vote if last vote exists else return Genesis block
last_voted = list(last_voted) last_voted = list(last_voted)
if not last_voted: if not last_voted:
return list(r.table('bigchain') return list(r.table('bigchain')
.filter(r.row['block_number'] == 0) .filter(r.row['block_number'] == 0)
.run(self.conn))[0] .run(self.conn))[0]
return last_voted[0] return last_voted[0]
def get_unvoted_blocks(self): def get_unvoted_blocks(self):
"""Return all the blocks that has not been voted by this node.""" """Return all the blocks that has not been voted by this node."""
unvoted = r.table('bigchain')\ unvoted = r.table('bigchain') \
.filter(lambda doc: doc['votes'].contains(lambda vote: vote['node_pubkey'] == self.me).not_())\ .filter(lambda doc: doc['votes'].contains(lambda vote: vote['node_pubkey'] == self.me).not_()) \
.order_by(r.asc((r.row['block']['timestamp'])))\ .order_by(r.asc((r.row['block']['timestamp']))) \
.run(self.conn) .run(self.conn)
if unvoted and unvoted[0].get('block_number') == 0: if unvoted and unvoted[0].get('block_number') == 0:

View File

@ -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 # handle payload
data = None data = None
@ -159,17 +162,14 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None):
# handle inputs # handle inputs
fulfillments = [] fulfillments = []
current_owners = current_owners if isinstance(current_owners, list) else [current_owners]
# transfer # transfer
if inputs: if inputs:
for fid, inp in enumerate(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({ fulfillments.append({
'current_owners': current_owners, 'current_owners': current_owners,
'input': inp, 'input': inp,
'fulfillment': fulfillment.serialize_json(), 'fulfillment': None,
'fid': fid 'fid': fid
}) })
# create # create
@ -184,9 +184,18 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None):
# handle outputs # handle outputs
conditions = [] conditions = []
for fulfillment in fulfillments: 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({ conditions.append({
'new_owners': new_owners, 'new_owners': new_owners,
'condition': None, 'condition': {
'details': json.loads(condition.serialize_json()),
'uri': condition.condition.serialize_uri()
},
'cid': fulfillment['fid'] 'cid': fulfillment['fid']
}) })
@ -220,7 +229,7 @@ def sign_tx(transaction, private_key):
Args: Args:
transaction (dict): transaction to sign. 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: Returns:
dict: transaction with the `fulfillment` fields populated. dict: transaction with the `fulfillment` fields populated.

View File

@ -71,7 +71,7 @@ setup(
'rethinkdb==2.2.0.post4', 'rethinkdb==2.2.0.post4',
'pysha3==0.3', 'pysha3==0.3',
'pytz==2015.7', 'pytz==2015.7',
'cryptoconditions==0.1.4', 'cryptoconditions==0.1.5',
'statsd==3.2.1', 'statsd==3.2.1',
'python-rapidjson==0.0.6', 'python-rapidjson==0.0.6',
'logstats==0.2.1', 'logstats==0.2.1',

View File

@ -23,17 +23,24 @@ def test_remove_unclosed_sockets():
class TestBigchainApi(object): 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') tx = b.create_transaction(b.me, user_sk, None, 'CREATE')
assert sorted(tx) == sorted(['id', 'transaction']) assert sorted(tx) == sorted(['id', 'transaction', 'version'])
assert sorted(tx['transaction']) == sorted(['current_owner', 'new_owner', 'input', 'operation', assert sorted(tx['transaction']) == sorted(['conditions', 'data', 'fulfillments', 'operation', 'timestamp'])
'timestamp', 'data'])
def test_create_transaction_with_unsupported_payload_raises(self, b): def test_create_transaction_with_unsupported_payload_raises(self, b):
with pytest.raises(TypeError): with pytest.raises(TypeError):
b.create_transaction('a', 'b', 'c', 'd', payload=[]) 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): def test_transaction_hash(self, b):
payload = {'cats': 'are awesome'} payload = {'cats': 'are awesome'}
tx = b.create_transaction('a', 'b', 'c', 'd', payload) tx = b.create_transaction('a', 'b', 'c', 'd', payload)