Basic Usage Examples¶
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.
Getting Started¶
We begin by creating an object of class BigchainDB:
In [1]: from bigchaindb_driver import BigchainDB
In [2]: bdb_root_url = 'https://example.com:9984' # Use YOUR BigchainDB Root URL here
If the BigchainDB node or cluster doesn’t require authentication tokens, you can do:
In [3]: bdb = BigchainDB(bdb_root_url)
If it does require authentication tokens, you can do put them in a dict like so:
In [4]: tokens = {'app_id': 'your_app_id', 'app_key': 'your_app_key'}
In [5]: bdb = BigchainDB(bdb_root_url, headers=tokens)
Digital Asset Definition¶
As an example, let’s consider the creation and transfer of a digital asset that represents a bicycle:
In [6]: bicycle = {
...: 'data': {
...: 'bicycle': {
...: 'serial_number': 'abcd1234',
...: 'manufacturer': 'bkfab',
...: },
...: },
...: }
...:
We’ll suppose that the bike belongs to Alice, and that it will be transferred to Bob.
In general, you may use any dictionary for the 'data'
property.
Metadata Definition (optional)¶
You can optionally add metadata to a transaction. Any dictionary is accepted.
For example:
In [7]: metadata = {'planet': 'earth'}
Cryptographic Identities Generation¶
Alice and Bob are represented by public/private key pairs. The private key is used to sign transactions, meanwhile the public key is used to verify that a signed transaction was indeed signed by the one who claims to be the signee.
In [8]: from bigchaindb_driver.crypto import generate_keypair
In [9]: alice, bob = generate_keypair(), generate_keypair()
Asset Creation¶
We’re now ready to create the digital asset. First, let’s prepare the transaction:
In [10]: prepared_creation_tx = bdb.transactions.prepare(
....: operation='CREATE',
....: signers=alice.public_key,
....: asset=bicycle,
....: metadata=metadata,
....: )
....:
The prepared_creation_tx
dictionary should be similar to:
In [11]: prepared_creation_tx
Out[11]:
{'asset': {'data': {'bicycle': {'manufacturer': 'bkfab',
'serial_number': 'abcd1234'}}},
'id': '6fd9b07bc947278bff11829291f39f5924d190b462b3ba5cd10fa0c28ef5f9d2',
'inputs': [{'fulfillment': {'public_key': '6ZYtMrVBvUmBkkRgtuhGFdhBYQ5utoKk3hu2M43C7W9C',
'signature': None,
'type': 'ed25519-sha-256'},
'fulfills': None,
'owners_before': ['6ZYtMrVBvUmBkkRgtuhGFdhBYQ5utoKk3hu2M43C7W9C']}],
'metadata': {'planet': 'earth'},
'operation': 'CREATE',
'outputs': [{'amount': '1',
'condition': {'details': {'public_key': '6ZYtMrVBvUmBkkRgtuhGFdhBYQ5utoKk3hu2M43C7W9C',
'signature': None,
'type': 'ed25519-sha-256'},
'uri': 'ni:///sha-256;GFf944YUe5GvnxETQ6twe38L1mGYo-5LAmobrsXen78?fpt=ed25519-sha-256&cost=131072'},
'public_keys': ['6ZYtMrVBvUmBkkRgtuhGFdhBYQ5utoKk3hu2M43C7W9C']}],
'version': '1.0'}
The transaction now needs to be fulfilled by signing it with Alice’s private key:
In [12]: fulfilled_creation_tx = bdb.transactions.fulfill(
....: prepared_creation_tx, private_keys=alice.private_key)
....:
In [13]: fulfilled_creation_tx
Out[13]:
{'asset': {'data': {'bicycle': {'manufacturer': 'bkfab',
'serial_number': 'abcd1234'}}},
'id': '6fd9b07bc947278bff11829291f39f5924d190b462b3ba5cd10fa0c28ef5f9d2',
'inputs': [{'fulfillment': 'pGSAIFKhEtZMg5YNfzmU2dAYd4bp5gJ39qid4Ytn-VOsNESPgUA0vgGEhWGfVRT68o_qtQ1n7tauJfZ_m4Wle8zUp9ormKZatsuhk-iAGC74BlY1WOB_y2OxjbCqNswCjoc7pPYK',
'fulfills': None,
'owners_before': ['6ZYtMrVBvUmBkkRgtuhGFdhBYQ5utoKk3hu2M43C7W9C']}],
'metadata': {'planet': 'earth'},
'operation': 'CREATE',
'outputs': [{'amount': '1',
'condition': {'details': {'public_key': '6ZYtMrVBvUmBkkRgtuhGFdhBYQ5utoKk3hu2M43C7W9C',
'signature': None,
'type': 'ed25519-sha-256'},
'uri': 'ni:///sha-256;GFf944YUe5GvnxETQ6twe38L1mGYo-5LAmobrsXen78?fpt=ed25519-sha-256&cost=131072'},
'public_keys': ['6ZYtMrVBvUmBkkRgtuhGFdhBYQ5utoKk3hu2M43C7W9C']}],
'version': '1.0'}
And sent over to a BigchainDB node:
>>> sent_creation_tx = bdb.transactions.send(fulfilled_creation_tx)
Note that the response from the node should be the same as that which was sent:
>>> sent_creation_tx == fulfilled_creation_tx
True
Notice the transaction id
:
In [14]: txid = fulfilled_creation_tx['id']
In [15]: txid
Out[15]: '6fd9b07bc947278bff11829291f39f5924d190b462b3ba5cd10fa0c28ef5f9d2'
To check the status of the transaction:
>>> bdb.transactions.status(txid)
Note
It may take a small amount of time before a BigchainDB cluster confirms a transaction as being valid.
Here’s some code that keeps checking the status of the transaction until it is valid:
>>> trials = 0
>>> while trials < 100:
... try:
... if bdb.transactions.status(txid).get('status') == 'valid':
... break
... except bigchaindb_driver.exceptions.NotFoundError:
... trials += 1
>>> bdb.transactions.status(txid)
{'status': 'valid'}
Asset Transfer¶
Imagine some time goes by, during which Alice is happy with her bicycle, and one day, she meets Bob, who is interested in acquiring her bicycle. The timing is good for Alice as she had been wanting to get a new bicycle.
To transfer the bicycle (asset) to Bob, Alice must consume the transaction in which the Bicycle asset was created.
Alice could retrieve the transaction:
>>> creation_tx = bdb.transactions.retrieve(txid)
or simply use fulfilled_creation_tx
:
In [16]: creation_tx = fulfilled_creation_tx
In order to prepare the transfer transaction, we first need to know the id of
the asset we’ll be transferring. Here, because Alice is consuming a CREATE
transaction, we have a special case in that the asset id is NOT found on the
asset
itself, but is simply the CREATE
transaction’s id:
In [17]: asset_id = creation_tx['id']
In [18]: transfer_asset = {
....: 'id': asset_id,
....: }
....:
Let’s now prepare the transfer transaction:
In [19]: output_index = 0
In [20]: output = creation_tx['outputs'][output_index]
In [21]: transfer_input = {
....: 'fulfillment': output['condition']['details'],
....: 'fulfills': {
....: 'output': output_index,
....: 'transaction_id': creation_tx['id'],
....: },
....: 'owners_before': output['public_keys'],
....: }
....:
In [22]: prepared_transfer_tx = bdb.transactions.prepare(
....: operation='TRANSFER',
....: asset=transfer_asset,
....: inputs=transfer_input,
....: recipients=bob.public_key,
....: )
....:
fulfill it:
In [23]: fulfilled_transfer_tx = bdb.transactions.fulfill(
....: prepared_transfer_tx,
....: private_keys=alice.private_key,
....: )
....:
and finally send it to the connected BigchainDB node:
>>> sent_transfer_tx = bdb.transactions.send(fulfilled_transfer_tx)
>>> sent_transfer_tx == fulfilled_transfer_tx
True
The fulfilled_transfer_tx
dictionary should look something like:
In [24]: fulfilled_transfer_tx
Out[24]:
{'asset': {'id': '6fd9b07bc947278bff11829291f39f5924d190b462b3ba5cd10fa0c28ef5f9d2'},
'id': '4fe5c06e339c851f98cfc611cfc4cc8afd01191d6bfa5930d7a729374dd47ddb',
'inputs': [{'fulfillment': 'pGSAIFKhEtZMg5YNfzmU2dAYd4bp5gJ39qid4Ytn-VOsNESPgUBlxkZM7LQig0zQY5p2eTMt2bTrjssN34zlHJEHtzc6OJepp6IMY9J4KsGJncsIfTyLN-jWqU_ERUR_6RHzO1YI',
'fulfills': {'output': 0,
'transaction_id': '6fd9b07bc947278bff11829291f39f5924d190b462b3ba5cd10fa0c28ef5f9d2'},
'owners_before': ['6ZYtMrVBvUmBkkRgtuhGFdhBYQ5utoKk3hu2M43C7W9C']}],
'metadata': None,
'operation': 'TRANSFER',
'outputs': [{'amount': '1',
'condition': {'details': {'public_key': '9Gyu2z1WydYcoEeKQBtSrS4sTwhg566wRvcn8C9Wt4fW',
'signature': None,
'type': 'ed25519-sha-256'},
'uri': 'ni:///sha-256;ri17fzUY4XMZRS_VwJNDfLkYg-c1bAOyl2DoHuqAbJs?fpt=ed25519-sha-256&cost=131072'},
'public_keys': ['9Gyu2z1WydYcoEeKQBtSrS4sTwhg566wRvcn8C9Wt4fW']}],
'version': '1.0'}
Bob is the new owner:
In [25]: fulfilled_transfer_tx['outputs'][0]['public_keys'][0] == bob.public_key
Out[25]: True
Alice is the former owner:
In [26]: fulfilled_transfer_tx['inputs'][0]['owners_before'][0] == alice.public_key
Out[26]: True
Note
Obtaining asset ids:
You might have noticed that we considered Alice’s case of consuming a
CREATE
transaction as a special case. In order to obtain the asset id
of a CREATE
transaction, we had to use the CREATE
transaction’s
id:
transfer_asset_id = create_tx['id']
If you instead wanted to consume TRANSFER
transactions (for example,
fulfilled_transfer_tx
), you could obtain the asset id to transfer from
the asset['id']
property:
transfer_asset_id = transfer_tx['asset']['id']
Recap: Asset Creation & Transfer¶
from bigchaindb_driver import BigchainDB
from bigchaindb_driver.crypto import generate_keypair
from time import sleep
from sys import exit
alice, bob = generate_keypair(), generate_keypair()
bdb_root_url = 'https://example.com:9984' # Use YOUR BigchainDB Root URL here
bdb = BigchainDB(bdb_root_url)
bicycle_asset = {
'data': {
'bicycle': {
'serial_number': 'abcd1234',
'manufacturer': 'bkfab'
},
},
}
bicycle_asset_metadata = {
'planet': 'earth'
}
prepared_creation_tx = bdb.transactions.prepare(
operation='CREATE',
signers=alice.public_key,
asset=bicycle_asset,
metadata=bicycle_asset_metadata
)
fulfilled_creation_tx = bdb.transactions.fulfill(
prepared_creation_tx,
private_keys=alice.private_key
)
sent_creation_tx = bdb.transactions.send(fulfilled_creation_tx)
txid = fulfilled_creation_tx['id']
trials = 0
while trials < 60:
try:
if bdb.transactions.status(txid).get('status') == 'valid':
print('Tx valid in:', trials, 'secs')
break
except bigchaindb_driver.exceptions.NotFoundError:
trials += 1
sleep(1)
if trials == 60:
print('Tx is still being processed... Bye!')
exit(0)
asset_id = txid
transfer_asset = {
'id': asset_id
}
output_index = 0
output = fulfilled_creation_tx['outputs'][output_index]
transfer_input = {
'fulfillment': output['condition']['details'],
'fulfills': {
'output': output_index,
'transaction_id': fulfilled_creation_tx['id']
},
'owners_before': output['public_keys']
}
prepared_transfer_tx = bdb.transactions.prepare(
operation='TRANSFER',
asset=transfer_asset,
inputs=transfer_input,
recipients=bob.public_key,
)
fulfilled_transfer_tx = bdb.transactions.fulfill(
prepared_transfer_tx,
private_keys=alice.private_key,
)
sent_transfer_tx = bdb.transactions.send(fulfilled_transfer_tx)
print("Is Bob the owner?",
sent_transfer_tx['outputs'][0]['public_keys'][0] == bob.public_key)
print("Was Alice the previous owner?",
fulfilled_transfer_tx['inputs'][0]['owners_before'][0] == alice.public_key)
Transaction Status¶
Using the id
of a transaction, its status can be obtained:
>>> bdb.transactions.status(creation_tx['id'])
{'status': 'valid'}
Handling cases for which the transaction id
may not be found:
import logging
from bigchaindb_driver import BigchainDB
from bigchaindb_driver.exceptions import NotFoundError
logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)-15s %(status)-3s %(message)s')
bdb_root_url = 'https://example.com:9984' # Use YOUR BigchainDB Root URL here
bdb = BigchainDB(bdb_root_url)
txid = '12345'
try:
status = bdb.transactions.status(txid)
except NotFoundError as e:
logger.error('Transaction "%s" was not found.',
txid,
extra={'status': e.status_code})
Running the above code should give something similar to:
2016-09-29 15:06:30,606 404 Transaction "12345" was not found.
Divisible Assets¶
All assets in BigchainDB become implicitly divisible if a transaction contains more than one of that asset (we’ll see how this happens shortly).
Let’s continue with the bicycle example. Bob is now the proud owner of the bicycle and he decides he wants to rent the bicycle. Bob starts by creating a time sharing token in which one token corresponds to one hour of riding time:
In [27]: bicycle_token = {
....: 'data': {
....: 'token_for': {
....: 'bicycle': {
....: 'serial_number': 'abcd1234',
....: 'manufacturer': 'bkfab'
....: }
....: },
....: 'description': 'Time share token. Each token equals one hour of riding.',
....: },
....: }
....:
Bob has now decided to issue 10 tokens and assigns them to Carly. Notice how we
denote Carly as receiving 10 tokens by using a tuple:
([carly.public_key], 10)
.
In [28]: bob, carly = generate_keypair(), generate_keypair()
In [29]: prepared_token_tx = bdb.transactions.prepare(
....: operation='CREATE',
....: signers=bob.public_key,
....: recipients=[([carly.public_key], 10)],
....: asset=bicycle_token,
....: )
....:
In [30]: fulfilled_token_tx = bdb.transactions.fulfill(
....: prepared_token_tx, private_keys=bob.private_key)
....:
Sending the transaction:
>>> sent_token_tx = bdb.transactions.send(fulfilled_token_tx)
>>> sent_token_tx == fulfilled_token_tx
True
Note
Defining recipients
:
To create divisible assets, we need to specify an amount >1
together
with the public keys. The way we do this is by passing a list
of
tuples
in recipients
where each tuple
corresponds to an output.
For instance, instead of creating a transaction with one output containing
amount=10
we could have created a transaction with two outputs each
holding amount=5
:
recipients=[([carly.public_key], 5), ([carly.public_key], 5)]
The reason why the addresses are contained in lists
is because each
output can have multiple recipients. For instance, we can create an
output with amount=10
in which both Carly and Alice are recipients
(of the same asset):
recipients=[([carly.public_key, alice.public_key], 10)]
The fulfilled_token_tx
dictionary should look something like:
In [31]: fulfilled_token_tx
Out[31]:
{'asset': {'data': {'description': 'Time share token. Each token equals one hour of riding.',
'token_for': {'bicycle': {'manufacturer': 'bkfab',
'serial_number': 'abcd1234'}}}},
'id': '7b9acc0844fe098b76e456bbe0ee93b3625a72f669a0bbc64a9b4c359dcb8c7d',
'inputs': [{'fulfillment': 'pGSAIFJxdXM-ksoynXtLg1F-BYa9JGpwdZgnI9KAXNTCHpxagUC6hu6A3k-oj6sFWqTdIOFmepS2uwPzw1Aor44R4s4-L_qQQ1_SeNvJgIOxdKgH6ip-u5RdBaWYl-ZBReSTIW4P',
'fulfills': None,
'owners_before': ['6YpmwF6M8EG8Kb1paH9QBxeXuxY9b9yPkWp5mAZojWt9']}],
'metadata': None,
'operation': 'CREATE',
'outputs': [{'amount': '10',
'condition': {'details': {'public_key': '4nUp4PYEpQtsFhVvKU29b23fEPjy8D8dRWzT8dpFxzL4',
'signature': None,
'type': 'ed25519-sha-256'},
'uri': 'ni:///sha-256;56DPEzMhezoPl62nrLPPy674o_3GVtOCrhtW6cEHOgg?fpt=ed25519-sha-256&cost=131072'},
'public_keys': ['4nUp4PYEpQtsFhVvKU29b23fEPjy8D8dRWzT8dpFxzL4']}],
'version': '1.0'}
Bob is the issuer:
In [32]: fulfilled_token_tx['inputs'][0]['owners_before'][0] == bob.public_key
Out[32]: True
Carly is the owner of 10 tokens:
In [33]: fulfilled_token_tx['outputs'][0]['public_keys'][0] == carly.public_key
Out[33]: True
In [34]: fulfilled_token_tx['outputs'][0]['amount'] == '10'