mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
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:
commit
dd20737bb9
@ -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,32 +128,32 @@ 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(
|
||||||
'input `{}` does not exist in the bigchain'.format(
|
'input `{}` does not exist in the bigchain'.format(
|
||||||
transaction['transaction']['input']))
|
transaction['transaction']['input']))
|
||||||
|
|
||||||
if (tx_input['transaction']['new_owner'] !=
|
if (tx_input['transaction']['new_owner'] !=
|
||||||
transaction['transaction']['current_owner']):
|
transaction['transaction']['current_owner']):
|
||||||
raise exceptions.TransactionOwnerError(
|
raise exceptions.TransactionOwnerError(
|
||||||
'current_owner `{}` does not own the input `{}`'.format(
|
'current_owner `{}` does not own the input `{}`'.format(
|
||||||
transaction['transaction']['current_owner'],
|
transaction['transaction']['current_owner'],
|
||||||
transaction['transaction']['input']))
|
transaction['transaction']['input']))
|
||||||
|
|
||||||
# check if the input was already spent by a transaction other than
|
# check if the input was already spent by a transaction other than
|
||||||
# this one.
|
# this one.
|
||||||
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(
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user