Merge remote-tracking branch 'remotes/origin/feat/128/multiple-input-output' into feat/127/crypto-conditions-ilp-bigchain-integration

Conflicts:
	bigchaindb/util.py
This commit is contained in:
diminator 2016-04-06 12:41:56 +02:00
commit dd20737bb9
4 changed files with 143 additions and 39 deletions

View File

@ -119,7 +119,7 @@ class BaseConsensusRules(AbstractConsensusRules):
# If the operation is CREATE the transaction should have no inputs and # If the operation is CREATE the transaction should have no inputs and
# should be signed by a federation node # should be signed by a federation node
if transaction['transaction']['operation'] == 'CREATE': if transaction['transaction']['operation'] == 'CREATE':
if transaction['transaction']['input']: if transaction['transaction']['inputs']:
raise ValueError('A CREATE operation has no inputs') raise ValueError('A CREATE operation has no inputs')
if transaction['transaction']['current_owner'] not in ( if transaction['transaction']['current_owner'] not in (
bigchain.federation_nodes + [bigchain.me]): bigchain.federation_nodes + [bigchain.me]):
@ -128,12 +128,13 @@ class BaseConsensusRules(AbstractConsensusRules):
else: else:
# check if the input exists, is owned by the current_owner # check if the input exists, is owned by the current_owner
if not transaction['transaction']['input']: if not transaction['transaction']['inputs']:
raise ValueError( raise ValueError(
'Only `CREATE` transactions can have null inputs') 'Only `CREATE` transactions can have null inputs')
tx_input = bigchain.get_transaction( # check inputs
transaction['transaction']['input']) for inp in transaction['transaction']['inputs']:
tx_input = bigchain.get_transaction(inp)
if not tx_input: if not tx_input:
raise exceptions.TransactionDoesNotExist( raise exceptions.TransactionDoesNotExist(
@ -152,8 +153,7 @@ class BaseConsensusRules(AbstractConsensusRules):
spent = bigchain.get_spent(tx_input['id']) spent = bigchain.get_spent(tx_input['id'])
if spent and spent['id'] != transaction['id']: if spent and spent['id'] != transaction['id']:
raise exceptions.DoubleSpend( raise exceptions.DoubleSpend(
'input `{}` was already spent'.format( 'input `{}` was already spent'.format(inp))
transaction['transaction']['input']))
# Check hash of the transaction # Check hash of the transaction
calculated_hash = crypto.hash_data(util.serialize( calculated_hash = crypto.hash_data(util.serialize(

View File

@ -203,7 +203,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']['input'] == 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
transactions = list(response) transactions = list(response)

View File

@ -76,7 +76,8 @@ def timestamp():
return "{0:.6f}".format(time.mktime(dt.timetuple()) + dt.microsecond / 1e6) return "{0:.6f}".format(time.mktime(dt.timetuple()) + dt.microsecond / 1e6)
def create_tx(current_owner, new_owner, tx_input, operation, payload=None): # TODO: Consider remove the operation (if there are no inputs CREATE else TRANSFER)
def create_tx(current_owners, new_owners, inputs, operation, payload=None):
"""Create a new transaction """Create a new transaction
A transaction in the bigchain is a transfer of a digital asset between two entities represented A transaction in the bigchain is a transfer of a digital asset between two entities represented
@ -92,9 +93,9 @@ def create_tx(current_owner, new_owner, tx_input, operation, payload=None):
`TRANSFER` - A transfer operation allows for a transfer of the digital assets between entities. `TRANSFER` - A transfer operation allows for a transfer of the digital assets between entities.
Args: Args:
current_owner (str): base58 encoded public key of the current owner of the asset. current_owners (list): base58 encoded public key of the current owners of the asset.
new_owner (str): base58 encoded public key of the new owner of the digital asset. new_owners (list): base58 encoded public key of the new owners of the digital asset.
tx_input (str): id of the transaction to use as input. inputs (list): id of the transaction to use as input.
operation (str): Either `CREATE` or `TRANSFER` operation. operation (str): Either `CREATE` or `TRANSFER` operation.
payload (Optional[dict]): dictionary with information about asset. payload (Optional[dict]): dictionary with information about asset.
@ -104,8 +105,45 @@ def create_tx(current_owner, new_owner, tx_input, operation, payload=None):
Raises: Raises:
TypeError: if the optional ``payload`` argument is not a ``dict``. TypeError: if the optional ``payload`` argument is not a ``dict``.
Reference:
{
"id": "<sha3 hash>",
"version": "transaction version number",
"transaction": {
"fulfillments": [
{
"current_owners": ["list of <pub-keys>"],
"input": {
"txid": "<sha3 hash>",
"cid": "condition index"
},
"fulfillment": "fulfillement of condition cid",
"fid": "fulfillment index"
}
],
"conditions": [
{
"new_owners": ["list of <pub-keys>"],
"condition": "condition to be met",
"cid": "condition index (1-to-1 mapping with fid)"
}
],
"operation": "<string>",
"timestamp": "<timestamp from client>",
"data": {
"hash": "<SHA3-256 hash hexdigest of payload>",
"payload": {
"title": "The Winds of Plast",
"creator": "Johnathan Plunkett",
"IPFS_key": "QmfQ5QAjvg4GtA3wg3adpnDJug8ktA1BxurVqBD8rtgVjP"
}
}
},
}
""" """
# handle payload
data = None data = None
if payload is not None: if payload is not None:
if isinstance(payload, dict): if isinstance(payload, dict):
@ -117,16 +155,38 @@ def create_tx(current_owner, new_owner, tx_input, operation, payload=None):
else: else:
raise TypeError('`payload` must be an dict instance') raise TypeError('`payload` must be an dict instance')
hash_payload = crypto.hash_data(serialize(payload)) # handle inputs
data = { fulfillments = []
'hash': hash_payload, # transfer
'payload': payload if inputs:
} for fid, inp in enumerate(inputs):
fulfillments.append({
'current_owners': current_owners,
'input': inp,
'fulfillment': None,
'fid': fid
})
# create
else:
fulfillments.append({
'current_owners': current_owners,
'input': None,
'fulfillment': None,
'fid': 0
})
# handle outputs
conditions = []
for fulfillment in fulfillments:
conditions.append({
'new_owners': new_owners,
'condition': None,
'cid': fulfillment['fid']
})
tx = { tx = {
'current_owner': current_owner, 'fulfillments': fulfillments,
'new_owner': new_owner, 'conditions': conditions,
'input': tx_input,
'operation': operation, 'operation': operation,
'timestamp': timestamp(), 'timestamp': timestamp(),
'data': data 'data': data
@ -139,12 +199,14 @@ def create_tx(current_owner, new_owner, tx_input, operation, payload=None):
# create the transaction # create the transaction
transaction = { transaction = {
'id': tx_hash, 'id': tx_hash,
'version': 1,
'transaction': tx 'transaction': tx
} }
return transaction return transaction
#TODO: Change sign_tx to populate the fulfillments
def sign_tx(transaction, private_key): def sign_tx(transaction, private_key):
"""Sign a transaction """Sign a transaction
@ -155,14 +217,43 @@ def sign_tx(transaction, private_key):
private_key (str): base58 encoded private key to create a signature of the transaction. private_key (str): base58 encoded private key to create a signature of the transaction.
Returns: Returns:
dict: transaction with the `signature` field included. dict: transaction with the `fulfillment` fields populated.
""" """
b = bigchaindb.Bigchain()
private_key = crypto.SigningKey(private_key) private_key = crypto.SigningKey(private_key)
signature = private_key.sign(serialize(transaction))
signed_transaction = transaction.copy() common_data = {
signed_transaction.update({'signature': signature.decode()}) 'operation': transaction['transaction']['operation'],
return signed_transaction 'timestamp': transaction['transaction']['timestamp'],
'data': transaction['transaction']['data'],
'version': transaction['version'],
'id': transaction['id']
}
for fulfillment in transaction['transaction']['fulfillments']:
fulfillment_message = common_data.copy()
if transaction['transaction']['operation'] == 'CREATE':
fulfillment_message.update({
'input': None,
'condition': None
})
else:
# get previous condition
previous_tx = b.get_transaction(fulfillment['input']['txid'])
conditions = sorted(previous_tx['transaction']['conditions'], key=lambda d: d['cid'])
# update the fulfillment message
fulfillment_message.update({
'input': fulfillment['input'],
'condition': conditions[fulfillment['cid']]
})
# sign the fulfillment message
fulfillment_message_signature = private_key.sign(serialize(fulfillment_message))
fulfillment.update({'fulfillment': fulfillment_message_signature.decode()})
return transaction
def create_and_sign_tx(private_key, current_owner, new_owner, tx_input, operation='TRANSFER', payload=None): def create_and_sign_tx(private_key, current_owner, new_owner, tx_input, operation='TRANSFER', payload=None):

View File

@ -717,3 +717,16 @@ class TestBigchainBlock(object):
def test_duplicated_transactions(self): def test_duplicated_transactions(self):
pytest.skip('We may have duplicates in the initial_results and changefeed') pytest.skip('We may have duplicates in the initial_results and changefeed')
class TestMultipleInputs(object):
def test_transfer_transaction_multiple(self, b):
pass
def test_transfer_single_input_from_multi_input(self, b):
pass
def test_get_spent(self, b):
pass