WIP: docs and tutorial

This commit is contained in:
diminator 2016-05-25 16:40:47 +02:00
parent 9875cab3b3
commit 37c698529f
No known key found for this signature in database
GPG Key ID: C3D8590E6D0D439A
8 changed files with 422 additions and 27 deletions

View File

@ -238,23 +238,6 @@ class Bigchain(object):
.run(self.conn)
owned = []
def owner_in_subfulfillments(condition_details, _owner):
if 'subfulfillments' in condition_details:
result = owner_in_subfulfillments(condition_details['subfulfillments'], _owner)
if result:
return True
elif isinstance(condition_details, list):
for _subfulfillment in condition_details:
result = owner_in_subfulfillments(_subfulfillment, _owner)
if result:
return True
else:
if 'public_key' in condition_details \
and _owner == condition_details['public_key']:
return True
return False
for tx in response:
# a transaction can contain multiple outputs (conditions) so we need to iterate over all of them
# to get a list of outputs available to spend
@ -268,7 +251,7 @@ class Bigchain(object):
# for transactions with multiple `new_owners` there will be several subfulfillments nested
# in the condition. We need to iterate the subfulfillments to make sure there is a
# subfulfillment for `owner`
if owner_in_subfulfillments(condition['condition']['details'], owner):
if util.condition_details_has_owner(condition['condition']['details'], owner):
tx_input = {'txid': tx['id'], 'cid': condition['cid']}
# check if input was already spent
if not self.get_spent(tx_input):

View File

@ -526,6 +526,37 @@ def get_input_condition(bigchain, fulfillment):
}
def condition_details_has_owner(condition_details, owner):
"""
Check if the public_key of owner is in the condition details
as an Ed25519Fulfillment.public_key
Args:
condition_details (dict): dict with condition details
owner (str): base58 public key of owner
Returns:
bool: True if the public key is found in the condition details, False otherwise
"""
if 'subfulfillments' in condition_details:
result = condition_details_has_owner(condition_details['subfulfillments'], owner)
if result:
return True
elif isinstance(condition_details, list):
for subcondition in condition_details:
result = condition_details_has_owner(subcondition, owner)
if result:
return True
else:
if 'public_key' in condition_details \
and owner == condition_details['public_key']:
return True
return False
def get_hash_data(transaction):
""" Get the hashed data that (should) correspond to the `transaction['id']`

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -885,4 +885,204 @@ hashlock_fulfill_tx
},
"version":1
}
```
### Timeout Conditions
Timeout conditions allow assets to expire after a certain time.
The primary use case of timeout conditions is to enable Escrow (see further).
The condition can only be fulfilled before the expiry time.
Once expired, the asset is lost and cannot be fulfilled by anyone.
__Note__: The timeout conditions are BigchainDB-specific and not (yet) supported by the ILP standard.
```python
# Create a timeout asset without any new_owners
timeout_tx = b.create_transaction(b.me, None, None, 'CREATE')
# Set expiry time - the asset needs to be transfered before expiration
time_sleep = 12
time_expire = str(float(util.timestamp()) + time_sleep) # 12 secs from now
condition_timeout = cc.TimeoutFulfillment(expire_time=time_expire)
# The conditions list is empty, so we need to append a new condition
tx_timeout['transaction']['conditions'].append({
'condition': {
'details': json.loads(condition_timeout.serialize_json()),
'uri': condition_timeout.condition.serialize_uri()
},
'cid': 0,
'new_owners': None
})
# Conditions have been updated, so the hash needs updating
tx_timeout['id'] = util.get_hash_data(tx_timeout)
# The asset needs to be signed by the current_owner
tx_timeout_signed = b.sign_transaction(tx_timeout, b.me_private)
# Some validations
assert b.validate_transaction(tx_timeout_signed) == tx_timeout_signed
assert b.is_valid_transaction(tx_timeout_signed) == tx_timeout_signed
b.write_transaction(tx_timeout_signed)
tx_timeout_signed
```
```python
{
"id":"78145396cd368f7168fb01c97aaf1df6f85244d7b544073dfcb42397dae38f90",
"transaction":{
"conditions":[
{
"cid":0,
"condition":{
"details":{
"bitmask":9,
"expire_time":"1464167910.643431",
"type":"fulfillment",
"type_id":99
},
"uri":"cc:63:9:sceU_NZc3cAjAvaR1TVmgj7am5y8hJEBoqLm-tbqGbQ:17"
},
"new_owners":null
}
],
"data":null,
"fulfillments":[
{
"current_owners":[
"FmLm6MxCABc8TsiZKdeYaZKo5yZWMM6Vty7Q1B6EgcP2"
],
"fid":0,
"fulfillment":null,
"input":null
}
],
"operation":"CREATE",
"timestamp":"1464167898.643353"
},
"version":1
}
```
The following demonstrates that the transaction invalidates once the timeout occurs:
```python
# Create a timeout fulfillment tx
tx_timeout_transfer = b.create_transaction(None, testuser1_pub, {'txid': tx_timeout['id'], 'cid': 0}, 'TRANSFER')
# Parse the timeout condition and create the corresponding fulfillment
timeout_fulfillment = cc.Fulfillment.from_json(
tx_timeout['transaction']['conditions'][0]['condition']['details'])
tx_timeout_transfer['transaction']['fulfillments'][0]['fulfillment'] = timeout_fulfillment.serialize_uri()
# No need to sign transaction, like with hashlocks
# Small test to see the state change
for i in range(time_sleep - 4):
tx_timeout_valid = b.is_valid_transaction(tx_timeout_transfer) == tx_timeout_transfer
seconds_to_timeout = int(float(time_expire) - float(util.timestamp()))
print('tx_timeout valid: {} ({}s to timeout)'.format(tx_timeout_valid, seconds_to_timeout))
sleep(1)
```
If you were fast enough, you should see the following output:
```python
tx_timeout valid: True (3s to timeout)
tx_timeout valid: True (2s to timeout)
tx_timeout valid: True (1s to timeout)
tx_timeout valid: True (0s to timeout)
tx_timeout valid: False (0s to timeout)
tx_timeout valid: False (-1s to timeout)
tx_timeout valid: False (-2s to timeout)
tx_timeout valid: False (-3s to timeout)
```
## Escrow
Escrow is a mechanism for conditional release of assets.
This means that a the assets are locked up by a trusted party until an `execute` condition is presented. In order not to tie up the assets forever, the escrow foresees an `abort` condition, which is typically an expiry time.
BigchainDB and cryptoconditions provides escrow out-of-the-box, without the need of a trusted party.
A threshold condition is used to represent the escrow, since BigchainDB transactions cannot have a _pending_ state.
![BigchainDB escrow conditions and fulfillments](./_static/tx_escrow_execute_abort.png)
The logic for switching between `execute` and `abort` conditions is conceptually simple:
```python
if timeout_condition.validate(utcnow()):
execute_fulfillment.validate(msg) == True
abort_fulfillment.validate(msg) == False
else:
execute_fulfillment.validate(msg) == False
abort_fulfillment.validate(msg) == True
```
The above switch can be implemented as follows using threshold cryptoconditions:
![Cryptoconditions escrow conditions and fulfillments](./_static/cc_escrow_execute_abort.png)
The small circle on the threshold conditions denotes an inversion of the fulfillment:
```python
inverted_fulfillment.validate(msg) == not fulfillment.validate(msg)
```
An inverted input to a threshold condition is simply obtained by using negative weights.
__Note__: negative weights are BigchainDB-specific and not (yet) supported by the ILP standard.
The following code snippet shows how to create an escrow condition
```python
# Retrieve the last transaction of testuser2_pub
tx_retrieved_id = b.get_owned_ids(testuser2_pub).pop()
# Create a base template with the execute and abort address
tx_escrow = b.create_transaction(testuser2_pub, [testuser2_pub, testuser1_pub], tx_retrieved_id, 'TRANSFER')
# Set expiry time - the execute address needs to fulfill before expiration
time_sleep = 12
time_expire = str(float(util.timestamp()) + time_sleep) # 12 secs from now
# Create the escrow and timeout condition
condition_escrow = cc.ThresholdSha256Fulfillment(threshold=1) # OR Gate
condition_timeout = cc.TimeoutFulfillment(expire_time=time_expire) # only valid if now() <= time_expire
# Create the execute branch
condition_execute = cc.ThresholdSha256Fulfillment(threshold=2) # AND gate
condition_execute.add_subfulfillment(cc.Ed25519Fulfillment(public_key=testuser1_pub)) # execute address
condition_execute.add_subfulfillment(condition_timeout) # federation checks on expiry
condition_escrow.add_subfulfillment(condition_execute)
# Create the abort branch
condition_abort = cc.ThresholdSha256Fulfillment(threshold=2) # AND gate
condition_abort.add_subfulfillment(cc.Ed25519Fulfillment(public_key=testuser2_pub)) # abort address
condition_abort.add_subfulfillment(condition_timeout, weight=-1) # the negative weight inverts the condition
condition_escrow.add_subfulfillment(condition_abort)
# Update the condition in the newly created transaction
tx_escrow['transaction']['conditions'][0]['condition'] = {
'details': json.loads(condition_escrow.serialize_json()),
'uri': condition_escrow.condition.serialize_uri()
}
# Conditions have been updated, so the hash needs updating
tx_escrow['id'] = util.get_hash_data(tx_escrow)
# The asset needs to be signed by the current_owner
tx_escrow_signed = b.sign_transaction(tx_escrow, testuser2_priv)
# Some validations
assert b.validate_transaction(tx_escrow_signed) == tx_escrow_signed
assert b.is_valid_transaction(tx_escrow_signed) == tx_escrow_signed
b.write_transaction(tx_escrow_signed)
tx_escrow_signed
```

View File

@ -1884,6 +1884,7 @@ class TestCryptoconditions(object):
first_input_tx = b.get_owned_ids(user_vk).pop()
user2_sk, user2_vk = crypto.generate_key_pair()
# ESCROW
escrow_tx = b.create_transaction(user_vk, [user_vk, user2_vk], first_input_tx, 'TRANSFER')
time_sleep = 3
@ -1926,12 +1927,10 @@ class TestCryptoconditions(object):
block = b.create_block([escrow_tx_signed])
b.write_block(block, durability='hard')
# create hashlock fulfillment tx
# Retrieve the last transaction of thresholduser1_pub
tx_retrieved_id = b.get_owned_ids(user2_vk).pop()
# EXECUTE
# Create a base template for output transaction
escrow_tx_transfer = b.create_transaction([user_vk, user2_vk], user2_vk, tx_retrieved_id, 'TRANSFER')
@ -1973,7 +1972,6 @@ class TestCryptoconditions(object):
assert b.validate_transaction(escrow_tx_transfer) == escrow_tx_transfer
# ABORT
# Create a base template for output transaction
escrow_tx_abort = b.create_transaction([user_vk, user2_vk], user_vk, tx_retrieved_id, 'TRANSFER')
@ -2013,6 +2011,7 @@ class TestCryptoconditions(object):
first_input_tx = b.get_owned_ids(user_vk).pop()
user2_sk, user2_vk = crypto.generate_key_pair()
# ESCROW
escrow_tx = b.create_transaction(user_vk, [user_vk, user2_vk], first_input_tx, 'TRANSFER')
time_sleep = 3
@ -2055,12 +2054,10 @@ class TestCryptoconditions(object):
block = b.create_block([escrow_tx_signed])
b.write_block(block, durability='hard')
# create hashlock fulfillment tx
# Retrieve the last transaction of thresholduser1_pub
tx_retrieved_id = b.get_owned_ids(user2_vk).pop()
# EXECUTE
# Create a base template for output transaction
escrow_tx_transfer = b.create_transaction([user_vk, user2_vk], user2_vk, tx_retrieved_id, 'TRANSFER')
@ -2108,7 +2105,6 @@ class TestCryptoconditions(object):
assert b.validate_transaction(escrow_tx_transfer) == escrow_tx_transfer
# ABORT
# Create a base template for output transaction
escrow_tx_abort = b.create_transaction([user_vk, user2_vk], user_vk, tx_retrieved_id, 'TRANSFER')
@ -2124,13 +2120,13 @@ class TestCryptoconditions(object):
escrow_tx_abort['transaction']['fulfillments'][0],
serialized=True)
escrow_fulfillment.subconditions = []
# fulfill execute branch
# do not fulfill execute branch
fulfillment_and_execute = cc.ThresholdSha256Fulfillment(threshold=2)
fulfillment_and_execute.add_subfulfillment(subfulfillment_user2)
fulfillment_and_execute.add_subfulfillment(fulfillment_timeout)
escrow_fulfillment.add_subcondition(fulfillment_and_execute.condition)
# do not fulfill abort branch
# fulfill abort branch
fulfillment_and_abort = cc.ThresholdSha256Fulfillment(threshold=2)
subfulfillment_user.sign(escrow_tx_fulfillment_message, crypto.SigningKey(user_sk))
fulfillment_and_abort.add_subfulfillment(subfulfillment_user)

View File

@ -106,6 +106,7 @@ sleep(8)
# retrieve the transaction
tx_multisig_retrieved = b.get_transaction(tx_multisig_signed['id'])
assert tx_multisig_retrieved is not None
print(json.dumps(tx_multisig_retrieved, sort_keys=True, indent=4, separators=(',', ':')))
@ -115,7 +116,11 @@ tx_multisig_retrieved_id = b.get_owned_ids(testuser2_pub).pop()
tx_multisig_transfer = b.create_transaction([testuser1_pub, testuser2_pub], testuser3_pub, tx_multisig_retrieved_id, 'TRANSFER')
tx_multisig_transfer_signed = b.sign_transaction(tx_multisig_transfer, [testuser1_priv, testuser2_priv])
b.validate_transaction(tx_multisig_transfer_signed)
try:
b.validate_transaction(tx_multisig_transfer_signed)
except exceptions.InvalidSignature:
import ipdb; ipdb.set_trace()
b.validate_transaction(tx_multisig_transfer_signed)
b.write_transaction(tx_multisig_transfer_signed)
# wait a few seconds for the asset to appear on the blockchain
@ -151,6 +156,8 @@ b.write_transaction(tx_mimo_signed)
print(json.dumps(tx_mimo_signed, sort_keys=True, indent=4, separators=(',', ':')))
sleep(8)
"""
Threshold Conditions
"""
@ -298,3 +305,181 @@ assert b.is_valid_transaction(hashlock_fulfill_tx) == hashlock_fulfill_tx
b.write_transaction(hashlock_fulfill_tx)
print(json.dumps(hashlock_fulfill_tx, sort_keys=True, indent=4, separators=(',', ':')))
"""
Timeout Conditions
"""
# Create transaction template
tx_timeout = b.create_transaction(b.me, None, None, 'CREATE')
# Set expiry time (12 secs from now)
time_sleep = 12
time_expire = str(float(util.timestamp()) + time_sleep)
# only valid if the server time <= time_expire
condition_timeout = cc.TimeoutFulfillment(expire_time=time_expire)
# The conditions list is empty, so we need to append a new condition
tx_timeout['transaction']['conditions'].append({
'condition': {
'details': json.loads(condition_timeout.serialize_json()),
'uri': condition_timeout.condition.serialize_uri()
},
'cid': 0,
'new_owners': None
})
# conditions have been updated, so hash needs updating
tx_timeout['id'] = util.get_hash_data(tx_timeout)
# sign transaction
tx_timeout_signed = b.sign_transaction(tx_timeout, b.me_private)
b.write_transaction(tx_timeout_signed)
print(json.dumps(tx_timeout, sort_keys=True, indent=4, separators=(',', ':')))
sleep(8)
# Retrieve the transaction id of tx_timeout
tx_timeout_id = {'txid': tx_timeout['id'], 'cid': 0}
# Create a template to transfer the tx_timeout
tx_timeout_transfer = b.create_transaction(None, testuser1_pub, tx_timeout_id, 'TRANSFER')
# Parse the threshold cryptocondition
timeout_fulfillment = cc.Fulfillment.from_json(
tx_timeout['transaction']['conditions'][0]['condition']['details'])
tx_timeout_transfer['transaction']['fulfillments'][0]['fulfillment'] = timeout_fulfillment.serialize_uri()
# no need to sign transaction, like with hashlocks
for i in range(time_sleep - 4):
tx_timeout_valid = b.is_valid_transaction(tx_timeout_transfer) == tx_timeout_transfer
seconds_to_timeout = int(float(time_expire) - float(util.timestamp()))
print('tx_timeout valid: {} ({}s to timeout)'.format(tx_timeout_valid, seconds_to_timeout))
sleep(1)
"""
Escrow Conditions
"""
# retrieve the last transaction of testuser2
tx_retrieved_id = b.get_owned_ids(testuser2_pub).pop()
# Create escrow template with the execute and abort address
tx_escrow = b.create_transaction(testuser2_pub, [testuser2_pub, testuser1_pub], tx_retrieved_id, 'TRANSFER')
# Set expiry time (12 secs from now)
time_sleep = 12
time_expire = str(float(util.timestamp()) + time_sleep)
# Create escrow and timeout condition
condition_escrow = cc.ThresholdSha256Fulfillment(threshold=1) # OR Gate
condition_timeout = cc.TimeoutFulfillment(expire_time=time_expire) # only valid if now() <= time_expire
# Create execute branch
condition_execute = cc.ThresholdSha256Fulfillment(threshold=2) # AND gate
condition_execute.add_subfulfillment(cc.Ed25519Fulfillment(public_key=testuser1_pub)) # execute address
condition_execute.add_subfulfillment(condition_timeout) # federation checks on expiry
condition_escrow.add_subfulfillment(condition_execute)
# Create abort branch
condition_abort = cc.ThresholdSha256Fulfillment(threshold=2) # AND gate
condition_abort.add_subfulfillment(cc.Ed25519Fulfillment(public_key=testuser2_pub)) # abort address
condition_abort.add_subfulfillment(condition_timeout, weight=-1) # the negative weight inverts the condition
condition_escrow.add_subfulfillment(condition_abort)
# Update the condition in the newly created transaction
tx_escrow['transaction']['conditions'][0]['condition'] = {
'details': json.loads(condition_escrow.serialize_json()),
'uri': condition_escrow.condition.serialize_uri()
}
# conditions have been updated, so hash needs updating
tx_escrow['id'] = util.get_hash_data(tx_escrow)
# sign transaction
tx_escrow_signed = b.sign_transaction(tx_escrow, testuser2_priv)
# some checks
assert b.validate_transaction(tx_escrow_signed) == tx_escrow_signed
assert b.is_valid_transaction(tx_escrow_signed) == tx_escrow_signed
print(json.dumps(tx_escrow_signed, sort_keys=True, indent=4, separators=(',', ':')))
b.write_transaction(tx_escrow_signed)
sleep(8)
# Retrieve the last transaction of thresholduser1_pub
tx_escrow_id = {'txid': tx_escrow_signed['id'], 'cid': 0}
# Create a base template for output transaction
tx_escrow_execute = b.create_transaction([testuser2_pub, testuser1_pub], testuser1_pub, tx_escrow_id, 'TRANSFER')
# Parse the threshold cryptocondition
escrow_fulfillment = cc.Fulfillment.from_json(
tx_escrow['transaction']['conditions'][0]['condition']['details'])
subfulfillment_testuser1 = escrow_fulfillment.get_subcondition_from_vk(testuser1_pub)[0]
subfulfillment_testuser2 = escrow_fulfillment.get_subcondition_from_vk(testuser2_pub)[0]
subfulfillment_timeout = escrow_fulfillment.subconditions[0]['body'].subconditions[1]['body']
# Get the fulfillment message to sign
tx_escrow_execute_fulfillment_message = \
util.get_fulfillment_message(tx_escrow_execute,
tx_escrow_execute['transaction']['fulfillments'][0],
serialized=True)
escrow_fulfillment.subconditions = []
# fulfill execute branch
fulfillment_execute = cc.ThresholdSha256Fulfillment(threshold=2)
subfulfillment_testuser1.sign(tx_escrow_execute_fulfillment_message, crypto.SigningKey(testuser1_priv))
fulfillment_execute.add_subfulfillment(subfulfillment_testuser1)
fulfillment_execute.add_subfulfillment(subfulfillment_timeout)
escrow_fulfillment.add_subfulfillment(fulfillment_execute)
# do not fulfill abort branch
condition_abort = cc.ThresholdSha256Fulfillment(threshold=2)
condition_abort.add_subfulfillment(subfulfillment_testuser2)
condition_abort.add_subfulfillment(subfulfillment_timeout, weight=-1)
escrow_fulfillment.add_subcondition(condition_abort.condition)
# create fulfillment and append to transaction
tx_escrow_execute['transaction']['fulfillments'][0]['fulfillment'] = escrow_fulfillment.serialize_uri()
# Time has expired, hence the abort branch can redeem
tx_escrow_abort = b.create_transaction([testuser2_pub, testuser1_pub], testuser2_pub, tx_escrow_id, 'TRANSFER')
# Parse the threshold cryptocondition
escrow_fulfillment = cc.Fulfillment.from_json(
tx_escrow['transaction']['conditions'][0]['condition']['details'])
subfulfillment_testuser1 = escrow_fulfillment.get_subcondition_from_vk(testuser1_pub)[0]
subfulfillment_testuser2 = escrow_fulfillment.get_subcondition_from_vk(testuser2_pub)[0]
subfulfillment_timeout = escrow_fulfillment.subconditions[0]['body'].subconditions[1]['body']
tx_escrow_abort_fulfillment_message = \
util.get_fulfillment_message(tx_escrow_abort,
tx_escrow_abort['transaction']['fulfillments'][0],
serialized=True)
escrow_fulfillment.subconditions = []
# Do not fulfill execute branch
condition_execute = cc.ThresholdSha256Fulfillment(threshold=2)
condition_execute.add_subfulfillment(subfulfillment_testuser1)
condition_execute.add_subfulfillment(subfulfillment_timeout)
escrow_fulfillment.add_subcondition(condition_execute.condition)
# Fulfill abort branch
fulfillment_abort = cc.ThresholdSha256Fulfillment(threshold=2)
subfulfillment_testuser2.sign(tx_escrow_abort_fulfillment_message, crypto.SigningKey(testuser2_priv))
fulfillment_abort.add_subfulfillment(subfulfillment_testuser2)
fulfillment_abort.add_subfulfillment(subfulfillment_timeout, weight=-1)
escrow_fulfillment.add_subfulfillment(fulfillment_abort)
tx_escrow_abort['transaction']['fulfillments'][0]['fulfillment'] = escrow_fulfillment.serialize_uri()
for i in range(time_sleep - 4):
valid_execute = b.is_valid_transaction(tx_escrow_execute) == tx_escrow_execute
valid_abort = b.is_valid_transaction(tx_escrow_abort) == tx_escrow_abort
print('execute: {} - abort {}'.format(valid_execute, valid_abort))
sleep(1)