Advanced Usage Examples¶
This page has examples of using the Python Driver for more advanced use cases such as escrow.
Todo
This is work in progress. More examples will gradually appear as issues like
are taken care of.
For the examples on this page, we assume you’re using a Python 3 version of IPython (or similar), you’ve installed the bigchaindb_driver Python package, and you have determined the BigchainDB Root URL of the node or cluster you want to connect to.
Connect to Multiple Nodes¶
You can optionally configure multiple nodes to connect to.
In [1]: from bigchaindb_driver import BigchainDB
In [2]: first_node = 'https://first.example.com:9984'
In [3]: second_node = 'https://second.example.com:9984'
In [4]: headers = {'app_id': 'your_app_id', 'app_key': 'your_app_key'}
In [5]: bdb = BigchainDB(first_node, second_node, headers=headers)
Each node can have its specific headers in addition to headers specified for all nodes, if any.
In [6]: first_node = 'https://first.example.com:9984'
In [7]: second_node = 'https://second.example.com:9984'
In [8]: common_headers = {'app_id': 'your_app_id', 'app_key': 'your_app_key'}
In [9]: second_node_headers = {'node_header': 'node_header_value'}
In [10]: bdb = BigchainDB(first_node,
....: {'endpoint': second_node, 'headers': second_node_headers},
....: headers=common_headers)
....:
The driver switches nodes on connection failures in a round robin fashion. Connection failures are intermittent network issues like DNS failures or “refused connection” errors.
The driver switches nodes and repeats requests until the specified timeout is expired. The default timeout is 20
seconds. When timeout expires, an instance of bigchaindb_driver.exceptions.TimeoutError
is raised. Specify
timeout=None
to repeat connection errors indefinitely.
In [11]: bdb = BigchainDB(first_node, second_node, headers=headers, timeout=None)
Also, the driver takes care of the exponential backoff. If a connection error occurs, the driver ensures at least half of a second is passed before the request to the same node is repeated. The intervals increase exponentially when multiple connection errors occur in a row. The user-specified timeout is always respected.
Warning
Every node the driver communicates with is supposed to be trusted. The driver does not currently implement any light client protocols.
Create a Digital Asset¶
At a high level, a “digital asset” is something which can be represented digitally and can be assigned to a user. In BigchainDB, users are identified by their public key, and the data payload in a digital asset is represented using a generic Python dict.
In BigchainDB, digital assets can be created by doing a special kind of
transaction: a CREATE
transaction.
In [12]: from bigchaindb_driver.crypto import generate_keypair
Create a test user: alice
In [13]: alice = generate_keypair()
Define a digital asset data payload
In [14]: digital_asset_payload = {'data': {'msg': 'Hello BigchainDB!'}}
In [15]: tx = bdb.transactions.prepare(operation='CREATE',
....: signers=alice.public_key,
....: asset=digital_asset_payload)
....:
All transactions need to be signed by the user creating the transaction.
In [16]: signed_tx = bdb.transactions.fulfill(tx, private_keys=alice.private_key)
In [17]: signed_tx
Out[17]:
{'asset': {'data': {'msg': 'Hello BigchainDB!'}},
'id': 'b65bad7bd737d0a2b7a76d42d86e9216d14ef4df72293ad290714e35de5c5030',
'inputs': [{'fulfillment': 'pGSAINtydsVkq5RlnTmBbZLjZgbTiDmORTOXyiUvLui1qqSogUAniIhBfK6wHZxxYWZGrHs9fsoWm70csT5vSY2-EQMjiZPekyIz2_RJA1ff33eeymqGV8gNm8aeczqUaPYiKPIB',
'fulfills': None,
'owners_before': ['FmdX4atvL1K6cAVTwm4NQHuQSQtn4bGQjiKwxD3YXTH1']}],
'metadata': None,
'operation': 'CREATE',
'outputs': [{'amount': '1',
'condition': {'details': {'public_key': 'FmdX4atvL1K6cAVTwm4NQHuQSQtn4bGQjiKwxD3YXTH1',
'type': 'ed25519-sha-256'},
'uri': 'ni:///sha-256;4WvzJrfqw7H8O-56Zr4hnei3aDKFaErgEgGAAEY-yCE?fpt=ed25519-sha-256&cost=131072'},
'public_keys': ['FmdX4atvL1K6cAVTwm4NQHuQSQtn4bGQjiKwxD3YXTH1']}],
'version': '2.0'}
Write the transaction to BigchainDB. The transaction will be stored in a backlog where it will be validated before being included in a block.
>>> sent_tx = bdb.transactions.send_commit(signed_tx)
Warning
The method .send will be deprecated in the next release of the driver, please use .send_commit
, .send_sync
, or .send_async
instead. More info
Note that the transaction payload returned by the BigchainDB node is equivalent to the signed transaction payload.
>>> sent_tx == signed_tx
True
Recap: Asset Creation¶
from bigchaindb_driver import BigchainDB
from bigchaindb_driver.crypto import generate_keypair
bdb_root_url = 'https://example.com:9984' # Use YOUR BigchainDB Root URL here
bdb = BigchainDB(bdb_root_url)
alice = generate_keypair()
digital_asset_payload = {'data': {'msg': 'Hello BigchainDB!'}}
tx = bdb.transactions.prepare(operation='CREATE',
signers=alice.public_key,
asset=digital_asset_payload)
signed_tx = bdb.transactions.fulfill(tx, private_keys=alice.private_key)
sent_tx = bdb.transactions.send_commit(signed_tx)
sent_tx == signed_tx
Check if the Transaction was sent successfully¶
After a couple of seconds, we can check if the transaction was included in a block.
# Retrieve the block height
>>> block_height = bdb.blocks.get(txid=signed_tx['id'])
This will return the block height containing the transaction. If the transaction is not in any block then None
is
returned. If it is None
it can have different reasons for example the transaction was invalid or is still in the
queue and you can try again later. If the transaction was invalid or could not be sent an exception is raised.
If we want to see the whole block we can use the block height to retrieve the block itself.
# Retrieve the block that contains the transaction
>>> block = bdb.blocks.retrieve(str(block_height))
The new owner of the digital asset is now Alice (or more correctly, her public key):
In [18]: alice.public_key
Out[18]: 'FmdX4atvL1K6cAVTwm4NQHuQSQtn4bGQjiKwxD3YXTH1'
Transfer the Digital Asset¶
Now that alice
has a digital asset assigned to her, she can transfer it to
another person. Transfer transactions require an input. The input will be the
transaction id of a digital asset that was assigned to alice
, which in our
case is
In [19]: signed_tx['id']
Out[19]: 'b65bad7bd737d0a2b7a76d42d86e9216d14ef4df72293ad290714e35de5c5030'
BigchainDB makes use of the crypto-conditions library
to cryptographically lock and unlock transactions. The locking script is
referred to as a condition
(put inside an “output”) and a corresponding
fulfillment
(put inside an “input”) unlocks the output condition of an
input_tx
.
Since a transaction can have multiple outputs each with their own
(crypto)condition, each transaction input is required to refer to the output
condition that they fulfill via fulfills['output_index']
.
In order to prepare a transfer transaction, Alice needs to provide at least three things:
inputs
– one or more fulfillments that fulfill a prior transaction’s output conditions.asset['id']
– the id of the asset being transferred.- Recipient
public_keys
– one or more public keys representing the new recipients(s).
To construct the input:
In [20]: output_index = 0
In [21]: output = signed_tx['outputs'][output_index]
In [22]: input_ = {
....: 'fulfillment': output['condition']['details'],
....: 'fulfills': {
....: 'output_index': output_index,
....: 'transaction_id': signed_tx['id'],
....: },
....: 'owners_before': output['public_keys'],
....: }
....:
The asset in a TRANSFER
transaction must be a dictionary with an id
key
denoting the asset to transfer. This asset id is either the id of the
CREATE
transaction of the asset (as it is in this case), or is the
asset['id']
property in a TRANSFER
transaction (note that this value
simply points to the id of the asset’s CREATE
transaction):
In [23]: transfer_asset_id = signed_tx['id']
In [24]: transfer_asset = {
....: 'id': transfer_asset_id,
....: }
....:
Create a second test user, bob
:
In [25]: bob = generate_keypair()
In [26]: bob.public_key
Out[26]: 'GTT7caxUSjcjWuDPfVmGRA8U6hgaurxkQzLRhqpsy65z'
And prepare the transfer transaction:
In [27]: tx_transfer = bdb.transactions.prepare(
....: operation='TRANSFER',
....: inputs=input_,
....: asset=transfer_asset,
....: recipients=bob.public_key,
....: )
....:
The tx_transfer
dictionary should look something like:
In [28]: tx_transfer
Out[28]:
{'asset': {'id': 'b65bad7bd737d0a2b7a76d42d86e9216d14ef4df72293ad290714e35de5c5030'},
'id': None,
'inputs': [{'fulfillment': {'public_key': 'FmdX4atvL1K6cAVTwm4NQHuQSQtn4bGQjiKwxD3YXTH1',
'type': 'ed25519-sha-256'},
'fulfills': {'output_index': 0,
'transaction_id': 'b65bad7bd737d0a2b7a76d42d86e9216d14ef4df72293ad290714e35de5c5030'},
'owners_before': ['FmdX4atvL1K6cAVTwm4NQHuQSQtn4bGQjiKwxD3YXTH1']}],
'metadata': None,
'operation': 'TRANSFER',
'outputs': [{'amount': '1',
'condition': {'details': {'public_key': 'GTT7caxUSjcjWuDPfVmGRA8U6hgaurxkQzLRhqpsy65z',
'type': 'ed25519-sha-256'},
'uri': 'ni:///sha-256;b9RUoiqwS-9lmASG8ULmy9aBCnbKbHZ0ssvKbMRbTV8?fpt=ed25519-sha-256&cost=131072'},
'public_keys': ['GTT7caxUSjcjWuDPfVmGRA8U6hgaurxkQzLRhqpsy65z']}],
'version': '2.0'}
Notice, bob
’s public key, appearing in the above dict
.
In [29]: tx_transfer['outputs'][0]['public_keys'][0]
Out[29]: 'GTT7caxUSjcjWuDPfVmGRA8U6hgaurxkQzLRhqpsy65z'
In [30]: bob.public_key