Merge branch 'master' into core/192/ignore-invalid-blocks

This commit is contained in:
ryan 2016-05-19 16:32:46 +02:00
commit 61bae88cdb
3 changed files with 230 additions and 27 deletions

View File

@ -276,7 +276,7 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None):
'new_owners': new_owners,
'condition': {
'details': json.loads(condition.serialize_json()),
'uri': condition.condition.serialize_uri()
'uri': condition.condition_uri
},
'cid': fulfillment['fid']
})
@ -330,7 +330,10 @@ def sign_tx(transaction, signing_keys):
for fulfillment in tx['transaction']['fulfillments']:
fulfillment_message = get_fulfillment_message(transaction, fulfillment)
parsed_fulfillment = cc.Fulfillment.from_json(fulfillment_message['condition']['condition']['details'])
# TODO: avoid instantiation, pass as argument!
bigchain = bigchaindb.Bigchain()
input_condition = get_input_condition(bigchain, fulfillment)
parsed_fulfillment = cc.Fulfillment.from_json(input_condition['condition']['details'])
# for the case in which the type of fulfillment is not covered by this method
parsed_fulfillment_signed = parsed_fulfillment
@ -436,19 +439,22 @@ def validate_fulfillments(signed_transaction):
Returns:
bool: True if the signature is correct, False otherwise.
"""
for fulfillment in signed_transaction['transaction']['fulfillments']:
fulfillment_message = get_fulfillment_message(signed_transaction, fulfillment)
try:
parsed_fulfillment = cc.Fulfillment.from_uri(fulfillment['fulfillment'])
except (TypeError, ValueError, ParsingError):
return False
# TODO: might already break on a False here
is_valid = parsed_fulfillment.validate(serialize(fulfillment_message))
# if transaction has an input (i.e. not a `CREATE` transaction)
if fulfillment['input']:
is_valid &= parsed_fulfillment.condition.serialize_uri() == \
fulfillment_message['condition']['condition']['uri']
# TODO: avoid instantiation, pass as argument!
bigchain = bigchaindb.Bigchain()
input_condition = get_input_condition(bigchain, fulfillment)
is_valid = is_valid and parsed_fulfillment.condition_uri == input_condition['condition']['uri']
if not is_valid:
return False
@ -466,8 +472,6 @@ def get_fulfillment_message(transaction, fulfillment, serialized=False):
Returns:
str|dict: fulfillment message
"""
b = bigchaindb.Bigchain()
# data to sign contains common transaction data
fulfillment_message = {
'operation': transaction['transaction']['operation'],
@ -479,27 +483,48 @@ def get_fulfillment_message(transaction, fulfillment, serialized=False):
# and the condition which needs to be retrieved from the output of a previous transaction
# or created on the fly it this is a `CREATE` transaction
fulfillment_message.update({
'input': fulfillment['input'],
'condition': None,
'fulfillment': copy.deepcopy(fulfillment),
'condition': transaction['transaction']['conditions'][fulfillment['fid']]
})
# if `TRANSFER` transaction
if fulfillment['input']:
# get previous condition
previous_tx = b.get_transaction(fulfillment['input']['txid'])
conditions = sorted(previous_tx['transaction']['conditions'], key=lambda d: d['cid'])
fulfillment_message['condition'] = conditions[fulfillment['input']['cid']]
# if `CREATE` transaction
# there is no previous transaction so we need to create one on the fly
else:
current_owner = transaction['transaction']['fulfillments'][0]['current_owners'][0]
condition = json.loads(cc.Ed25519Fulfillment(public_key=current_owner).serialize_json())
fulfillment_message['condition'] = {'condition': {'details': condition}}
# remove any fulfillment, as a fulfillment cannot sign itself
fulfillment_message['fulfillment']['fulfillment'] = None
if serialized:
return serialize(fulfillment_message)
return fulfillment_message
def get_input_condition(bigchain, fulfillment):
"""
Args:
bigchain:
fulfillment:
Returns:
"""
input_tx = fulfillment['input']
# if `TRANSFER` transaction
if input_tx:
# get previous condition
previous_tx = bigchain.get_transaction(input_tx['txid'])
conditions = sorted(previous_tx['transaction']['conditions'], key=lambda d: d['cid'])
return conditions[input_tx['cid']]
# if `CREATE` transaction
# there is no previous transaction so we need to create one on the fly
else:
current_owner = fulfillment['current_owners'][0]
condition = cc.Ed25519Fulfillment(public_key=current_owner)
return {
'condition': {
'details': json.loads(condition.serialize_json()),
'uri': condition.condition_uri
}
}
def get_hash_data(transaction):
""" Get the hashed data that (should) correspond to the `transaction['id']`

View File

@ -6,11 +6,28 @@ For full docs visit https://bigchaindb.readthedocs.org
"""
from setuptools import setup, find_packages
# get the version
version = {}
with open('bigchaindb/version.py') as fp:
exec(fp.read(), version)
# check if setuptools is up to date
def check_setuptools_features():
import pkg_resources
try:
list(pkg_resources.parse_requirements('foo~=1.0'))
except ValueError:
exit('Your Python distribution comes with an incompatible version '
'of `setuptools`. Please run:\n'
' $ pip3 install --upgrade setuptools\n'
'and then run this command again')
check_setuptools_features()
tests_require = [
'pytest',
'coverage',

View File

@ -1292,6 +1292,165 @@ class TestMultipleInputs(object):
assert b.get_spent(inp) is None
class TestFulfillmentMessage(object):
def test_fulfillment_message_create(self, b, user_vk):
tx = b.create_transaction(b.me, user_vk, None, 'CREATE', payload={'pay': 'load'})
original_fulfillment = tx['transaction']['fulfillments'][0]
fulfillment_message = util.get_fulfillment_message(tx, original_fulfillment)
assert sorted(fulfillment_message) == \
['condition', 'data', 'fulfillment', 'id', 'operation', 'timestamp', 'version']
assert fulfillment_message['data']['payload'] == tx['transaction']['data']['payload']
assert fulfillment_message['id'] == tx['id']
assert fulfillment_message['condition'] == tx['transaction']['conditions'][0]
assert fulfillment_message['fulfillment']['current_owners'] == original_fulfillment['current_owners']
assert fulfillment_message['fulfillment']['fid'] == original_fulfillment['fid']
assert fulfillment_message['fulfillment']['input'] == original_fulfillment['input']
assert fulfillment_message['operation'] == tx['transaction']['operation']
assert fulfillment_message['timestamp'] == tx['transaction']['timestamp']
assert fulfillment_message['version'] == tx['version']
@pytest.mark.usefixtures('inputs')
def test_fulfillment_message_transfer(self, b, user_vk):
input_tx = b.get_owned_ids(user_vk).pop()
assert b.validate_fulfillments(b.get_transaction(input_tx['txid'])) == True
tx = b.create_transaction(user_vk, b.me, input_tx, 'TRANSFER', payload={'pay': 'load'})
original_fulfillment = tx['transaction']['fulfillments'][0]
fulfillment_message = util.get_fulfillment_message(tx, original_fulfillment)
assert sorted(fulfillment_message) == \
['condition', 'data', 'fulfillment', 'id', 'operation', 'timestamp', 'version']
assert fulfillment_message['data']['payload'] == tx['transaction']['data']['payload']
assert fulfillment_message['id'] == tx['id']
assert fulfillment_message['condition'] == tx['transaction']['conditions'][0]
assert fulfillment_message['fulfillment']['current_owners'] == original_fulfillment['current_owners']
assert fulfillment_message['fulfillment']['fid'] == original_fulfillment['fid']
assert fulfillment_message['fulfillment']['input'] == original_fulfillment['input']
assert fulfillment_message['operation'] == tx['transaction']['operation']
assert fulfillment_message['timestamp'] == tx['transaction']['timestamp']
assert fulfillment_message['version'] == tx['version']
def test_fulfillment_message_multiple_current_owners_multiple_new_owners_multiple_inputs(self, b, user_vk):
# create a new users
user2_sk, user2_vk = crypto.generate_key_pair()
user3_sk, user3_vk = crypto.generate_key_pair()
user4_sk, user4_vk = crypto.generate_key_pair()
# create inputs to spend
transactions = []
for i in range(5):
tx = b.create_transaction(b.me, [user_vk, user2_vk], None, 'CREATE')
tx_signed = b.sign_transaction(tx, b.me_private)
transactions.append(tx_signed)
block = b.create_block(transactions)
b.write_block(block, durability='hard')
# get input
owned_inputs = b.get_owned_ids(user_vk)
inp = owned_inputs[:3]
# create a transaction
tx = b.create_transaction([user_vk, user2_vk], [user3_vk, user4_vk], inp, 'TRANSFER', payload={'pay': 'load'})
for original_fulfillment in tx['transaction']['fulfillments']:
fulfillment_message = util.get_fulfillment_message(tx, original_fulfillment)
assert sorted(fulfillment_message) == \
['condition', 'data', 'fulfillment', 'id', 'operation', 'timestamp', 'version']
assert fulfillment_message['data']['payload'] == tx['transaction']['data']['payload']
assert fulfillment_message['id'] == tx['id']
assert fulfillment_message['condition'] == tx['transaction']['conditions'][original_fulfillment['fid']]
assert fulfillment_message['fulfillment']['current_owners'] == original_fulfillment['current_owners']
assert fulfillment_message['fulfillment']['fid'] == original_fulfillment['fid']
assert fulfillment_message['fulfillment']['input'] == original_fulfillment['input']
assert fulfillment_message['operation'] == tx['transaction']['operation']
assert fulfillment_message['timestamp'] == tx['transaction']['timestamp']
assert fulfillment_message['version'] == tx['version']
class TestTransactionMalleability(object):
@pytest.mark.usefixtures('inputs')
def test_create_transaction_transfer(self, b, user_vk, user_sk):
input_tx = b.get_owned_ids(user_vk).pop()
assert b.validate_fulfillments(b.get_transaction(input_tx['txid'])) is True
tx = b.create_transaction(user_vk, b.me, input_tx, 'TRANSFER')
tx_signed = b.sign_transaction(tx, user_sk)
assert b.validate_fulfillments(tx_signed) is True
assert b.is_valid_transaction(tx_signed) == tx_signed
tx_changed = copy.deepcopy(tx_signed)
tx_changed['id'] = 'dsdasd'
assert b.validate_fulfillments(tx_changed) is False
assert b.is_valid_transaction(tx_changed) is False
tx_changed = copy.deepcopy(tx_signed)
tx_changed['version'] = '0'
assert b.validate_fulfillments(tx_changed) is False
assert b.is_valid_transaction(tx_changed) is False
tx_changed = copy.deepcopy(tx_signed)
tx_changed['transaction']['operation'] = 'CREATE'
assert b.validate_fulfillments(tx_changed) is False
assert b.is_valid_transaction(tx_changed) is False
tx_changed = copy.deepcopy(tx_signed)
tx_changed['transaction']['timestamp'] = '1463033192.123456'
assert b.validate_fulfillments(tx_changed) is False
assert b.is_valid_transaction(tx_changed) is False
tx_changed = copy.deepcopy(tx_signed)
tx_changed['transaction']['data'] = {
"hash": "872fa6e6f46246cd44afdb2ee9cfae0e72885fb0910e2bcf9a5a2a4eadb417b8",
"payload": {
"msg": "Hello BigchainDB!"
}
}
assert b.validate_fulfillments(tx_changed) == False
tx_changed = copy.deepcopy(tx_signed)
tx_changed['transaction']['fulfillments'] = [
{
"current_owners": [
"AFbofwJYEB7Cx2fgrPrCJzbdDVRzRKysoGXt4DsvuTGN"
],
"fid": 0,
"fulfillment": "cf:4:iXaq3UbandDj4DgBhFDcfHjkm2639RwgLmwAHUmuDFMfMEKMZ71eQw2qCMK951kBaNNJel_FCDuYnacn_MsWzYXOUJs6DGW3lYfXI_d55xuqpH2BenvRWKNp98tRRr4B",
"input": None
}
]
assert b.validate_fulfillments(tx_changed) is False
assert b.is_valid_transaction(tx_changed) is False
tx_changed = copy.deepcopy(tx_signed)
tx_changed['transaction']['fulfillments'][0]['fid'] = 1
with pytest.raises(IndexError):
assert b.validate_fulfillments(tx_changed) is False
assert b.is_valid_transaction(tx_changed) is False
tx_changed = copy.deepcopy(tx_signed)
tx_changed['transaction']['fulfillments'][0]['current_owners'] = [
"AFbofwJYEB7Cx2fgrPrCJzbdDVRzRKysoGXt4DsvuTGN"]
assert b.validate_fulfillments(tx_changed) is False
assert b.is_valid_transaction(tx_changed) is False
tx_changed = copy.deepcopy(tx_signed)
tx_changed['transaction']['fulfillments'][0]['input'] = {
"cid": 0,
"txid": "3055348675fc6f23b75f13c55db6d112b66eee068e99d30a802883d3b1784203"
}
with pytest.raises(TypeError):
assert b.validate_fulfillments(tx_changed) is False
assert b.is_valid_transaction(tx_changed) is False
class TestCryptoconditions(object):
def test_fulfillment_transaction_create(self, b, user_vk):
tx = b.create_transaction(b.me, user_vk, None, 'CREATE')
@ -1396,7 +1555,7 @@ class TestCryptoconditions(object):
assert b.is_valid_transaction(tx) == tx
@pytest.mark.usefixtures('inputs')
def test_override_fulfillment_transfer(self, b, user_vk, user_sk):
def test_override_fulfillment_transfer(self, b, user_vk, user_sk):
# create valid transaction
other_sk, other_vk = crypto.generate_key_pair()
prev_tx_id = b.get_owned_ids(user_vk).pop()
@ -1413,7 +1572,7 @@ class TestCryptoconditions(object):
assert b.is_valid_transaction(tx) == tx
@pytest.mark.usefixtures('inputs')
def test_override_condition_and_fulfillment_transfer(self, b, user_vk, user_sk):
def test_override_condition_and_fulfillment_transfer(self, b, user_vk, user_sk):
other_sk, other_vk = crypto.generate_key_pair()
first_input_tx = b.get_owned_ids(user_vk).pop()
first_tx = b.create_transaction(user_vk, other_vk, first_input_tx, 'TRANSFER')
@ -1650,7 +1809,8 @@ class TestCryptoconditions(object):
tx_transfer_signed = b.sign_transaction(tx_transfer, [user_sk, user2_sk])
# expected fulfillment
expected_fulfillment = cc.Fulfillment.from_json(tx_create['transaction']['conditions'][0]['condition']['details'])
expected_fulfillment = cc.Fulfillment.from_json(
tx_create['transaction']['conditions'][0]['condition']['details'])
subfulfillment1 = expected_fulfillment.subconditions[0]['body']
subfulfillment2 = expected_fulfillment.subconditions[1]['body']
@ -1661,7 +1821,7 @@ class TestCryptoconditions(object):
subfulfillment2.sign(util.serialize(expected_fulfillment_message), crypto.SigningKey(user2_sk))
assert tx_transfer_signed['transaction']['fulfillments'][0]['fulfillment'] \
== expected_fulfillment.serialize_uri()
== expected_fulfillment.serialize_uri()
assert b.validate_fulfillments(tx_transfer_signed) is True
@ -1786,7 +1946,8 @@ class TestCryptoconditions(object):
# try doublespending
user2_sk, user2_vk = crypto.generate_key_pair()
hashlock_doublespend_tx = b.create_transaction(None, user2_vk, {'txid': hashlock_tx['id'], 'cid': 0}, 'TRANSFER')
hashlock_doublespend_tx = b.create_transaction(None, user2_vk, {'txid': hashlock_tx['id'], 'cid': 0},
'TRANSFER')
hashlock_doublespend_tx_fulfillment = cc.PreimageSha256Fulfillment(preimage=secret)
hashlock_doublespend_tx['transaction']['fulfillments'][0]['fulfillment'] = \