Basic Usage Examples

Note

You must install the bigchaindb_driver Python package first.

You should use Python 3 for these examples.

The BigchainDB driver’s main purpose is to connect to one or more BigchainDB server nodes, in order to perform supported API calls documented under drivers-clients/http-client-server-api.

Connecting to a BigchainDB node, is done via the BigchainDB class:

from bigchaindb_driver import BigchainDB

bdb = BigchainDB('<api_endpoint>')

where <api_endpoint> is the root URL of the BigchainDB server API you wish to connect to.

For the simplest case in which a BigchainDB node would be running locally, (and the BIGCHAINDB_SERVER_BIND setting wouldn’t have been changed), you would connect to the local BigchainDB server this way:

bdb = BigchainDB('http://localhost:9984/api/v1')

If you are running the docker-based dev setup that comes along with the bigchaindb_driver repository (see Development Environment with Docker for more information), and wish to connect to it from the bdb-driver linked (container) service:

>>> bdb = BigchainDB('http://bdb-server:9984/api/v1')

Alternatively, you may connect to the containerized BigchainDB node from “outside”, in which case you need to know the port binding:

$ docker-compose port bdb-server 9984
0.0.0.0:32780
>>> bdb = BigchainDB('http://0.0.0.0:32780/api/v1')

For the sake of this example:

In [1]: from bigchaindb_driver import BigchainDB

In [2]: bdb = BigchainDB('http://bdb-server:9984/api/v1')

Digital Asset Definition

As an example, let’s consider the creation and transfer of a digital asset that represents a bicycle:

In [3]: 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.

Metadata Definition (optional)

You can optionally add metadata to a transaction. Any dictionary is accepted.

For example:

In [4]: metadata = {'planet': 'earth'}

Cryptographic Identities Generation

Alice, and Bob are represented by signing/verifying key pairs. The signing (private) key is used to sign transactions, meanwhile the verifying (public) key is used to verify that a signed transaction was indeed signed by the one who claims to be the signee.

In [5]: from bigchaindb_driver.crypto import generate_keypair

In [6]: alice, bob = generate_keypair(), generate_keypair()

Asset Creation

We’re now ready to create the digital asset. First we prepare the transaction:

In [7]: prepared_creation_tx = bdb.transactions.prepare(
   ...:     operation='CREATE',
   ...:     owners_before=alice.verifying_key,
   ...:     asset=bicycle,
   ...:     metadata=metadata,
   ...: )
   ...: 

The prepared_creation_tx dictionary should be similar to:

In [8]: prepared_creation_tx
Out[8]: 
{'id': '11db7b4fe658192b6aa83b0b1571c984ad0880d02ede3846e6fdcf60ca41a019',
 'transaction': {'asset': {'data': {'bicycle': {'manufacturer': 'bkfab',
     'serial_number': 'abcd1234'}},
   'divisible': False,
   'id': 'd65368d1-0c3d-4ca8-9388-31776466a215',
   'refillable': False,
   'updatable': False},
  'conditions': [{'amount': 1,
    'cid': 0,
    'condition': {'details': {'bitmask': 32,
      'public_key': 'C1WwQEr45YWihH8jCyq8eQzkuG5D7twTztHno4YLmwCV',
      'signature': None,
      'type': 'fulfillment',
      'type_id': 4},
     'uri': 'cc:4:20:o5JoOaJiE0jXY4tpCt4G0nU-c0b0Fr0aMYAaCAdP1GI:96'},
    'owners_after': ['C1WwQEr45YWihH8jCyq8eQzkuG5D7twTztHno4YLmwCV']}],
  'fulfillments': [{'fid': 0,
    'fulfillment': {'bitmask': 32,
     'public_key': 'C1WwQEr45YWihH8jCyq8eQzkuG5D7twTztHno4YLmwCV',
     'signature': None,
     'type': 'fulfillment',
     'type_id': 4},
    'input': None,
    'owners_before': ['C1WwQEr45YWihH8jCyq8eQzkuG5D7twTztHno4YLmwCV']}],
  'metadata': {'data': {'planet': 'earth'},
   'id': '90ff637a-5256-4f09-bf72-6f87bb547ebc'},
  'operation': 'CREATE'},
 'version': 1}

The transaction needs to be fulfilled:

In [9]: fulfilled_creation_tx = bdb.transactions.fulfill(
   ...:     prepared_creation_tx, private_keys=alice.signing_key)
   ...: 
In [10]: fulfilled_creation_tx
Out[10]: 
{'id': '11db7b4fe658192b6aa83b0b1571c984ad0880d02ede3846e6fdcf60ca41a019',
 'transaction': {'asset': {'data': {'bicycle': {'manufacturer': 'bkfab',
     'serial_number': 'abcd1234'}},
   'divisible': False,
   'id': 'd65368d1-0c3d-4ca8-9388-31776466a215',
   'refillable': False,
   'updatable': False},
  'conditions': [{'amount': 1,
    'cid': 0,
    'condition': {'details': {'bitmask': 32,
      'public_key': 'C1WwQEr45YWihH8jCyq8eQzkuG5D7twTztHno4YLmwCV',
      'signature': None,
      'type': 'fulfillment',
      'type_id': 4},
     'uri': 'cc:4:20:o5JoOaJiE0jXY4tpCt4G0nU-c0b0Fr0aMYAaCAdP1GI:96'},
    'owners_after': ['C1WwQEr45YWihH8jCyq8eQzkuG5D7twTztHno4YLmwCV']}],
  'fulfillments': [{'fid': 0,
    'fulfillment': 'cf:4:o5JoOaJiE0jXY4tpCt4G0nU-c0b0Fr0aMYAaCAdP1GJCR0kjjXZ64Fg2aVypK3j-SD_Rfn0RZa4Rc_iHovvpKIbb1Z2RiJgE5TIgZK9vq5u8Gz-ed1QesoanpwGSVlgK',
    'input': None,
    'owners_before': ['C1WwQEr45YWihH8jCyq8eQzkuG5D7twTztHno4YLmwCV']}],
  'metadata': {'data': {'planet': 'earth'},
   'id': '90ff637a-5256-4f09-bf72-6f87bb547ebc'},
  'operation': 'CREATE'},
 'version': 1}

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 [11]: txid = fulfilled_creation_tx['id']

In [12]: txid
Out[12]: '11db7b4fe658192b6aa83b0b1571c984ad0880d02ede3846e6fdcf60ca41a019'

To check the status of the transaction:

>>> trials = 0

>>> while bdb.transactions.status(txid).get('status') != 'valid' and trials < 100:
...     trials += 1

>>> bdb.transactions.status(txid)
{'status': 'valid'}

Note

It may take a small amount of time before a BigchainDB cluster confirms a transaction as being 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 [13]: creation_tx = fulfilled_creation_tx

Preparing the transfer transaction:

In [14]: cid = 0

In [15]: condition = creation_tx['transaction']['conditions'][cid]

In [16]: transfer_input = {
   ....:     'fulfillment': condition['condition']['details'],
   ....:     'input': {
   ....:          'cid': cid,
   ....:          'txid': creation_tx['id'],
   ....:      },
   ....:      'owners_before': condition['owners_after'],
   ....: }
   ....: 

In [17]: prepared_transfer_tx = bdb.transactions.prepare(
   ....:     operation='TRANSFER',
   ....:     asset=creation_tx['transaction']['asset'],
   ....:     inputs=transfer_input,
   ....:     owners_after=bob.verifying_key,
   ....: )
   ....: 

and then fulfills the prepared transfer:

In [18]: fulfilled_transfer_tx = bdb.transactions.fulfill(
   ....:     prepared_transfer_tx,
   ....:     private_keys=alice.signing_key,
   ....: )
   ....: 

and finally sends the fulfilled transaction 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 [19]: fulfilled_transfer_tx
Out[19]: 
{'id': '7c7e6dad9598d60f7ec9ea6c491c7903df135a5733b5ed0f7715023d1b9bdbb6',
 'transaction': {'asset': {'id': 'd65368d1-0c3d-4ca8-9388-31776466a215'},
  'conditions': [{'amount': 1,
    'cid': 0,
    'condition': {'details': {'bitmask': 32,
      'public_key': 'BMvsFomgmGaJVhWH9s7J2kG4C9jHJVkHB6U7dwxw1JQq',
      'signature': None,
      'type': 'fulfillment',
      'type_id': 4},
     'uri': 'cc:4:20:mfFkeBRZXhAPNtUco_onGWcBPRthJXJRnViTIzcovwo:96'},
    'owners_after': ['BMvsFomgmGaJVhWH9s7J2kG4C9jHJVkHB6U7dwxw1JQq']}],
  'fulfillments': [{'fid': 0,
    'fulfillment': 'cf:4:o5JoOaJiE0jXY4tpCt4G0nU-c0b0Fr0aMYAaCAdP1GJOFhJfwocon11CwnNwnaXwwQNrQE7ppTIGaVfdyo1KYR_HV8GjRL_G9yMCbLjBawn7d6qF1vqfljVSXSHmbeYE',
    'input': {'cid': 0,
     'txid': '11db7b4fe658192b6aa83b0b1571c984ad0880d02ede3846e6fdcf60ca41a019'},
    'owners_before': ['C1WwQEr45YWihH8jCyq8eQzkuG5D7twTztHno4YLmwCV']}],
  'metadata': None,
  'operation': 'TRANSFER'},
 'version': 1}

Bob is the new owner:

In [20]: fulfilled_transfer_tx['transaction']['conditions'][0]['owners_after'][0] == bob.verifying_key
Out[20]: True

Alice is the former owner:

In [21]: fulfilled_transfer_tx['transaction']['fulfillments'][0]['owners_before'][0] == alice.verifying_key
Out[21]: True

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')

# NOTE: You may need to change the URL.
# E.g.: 'http://localhost:9984/api/v1'
bdb = BigchainDB('http://bdb-server:9984/api/v1')
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

In BigchainDB all assets are non-divisible by default so if we want to make a divisible asset we need to explicitly mark it as divisible.

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 1 token corresponds to 1 hour of riding time:

In [22]: bicycle_token = {
   ....:     'divisible': True,
   ....:     'data': {
   ....:         'token_for': {
   ....:             'bicycle': {
   ....:                 'serial_number': 'abcd1234',
   ....:                 'manufacturer': 'bkfab'
   ....:             }
   ....:         },
   ....:         'description': 'Time share token. Each token equals 1 hour of riding.'
   ....:     }
   ....: }
   ....: 

Bob has now decided to issue 10 tokens and assign them to Carly.

In [23]: bob, carly = generate_keypair(), generate_keypair()

In [24]: prepared_token_tx = bdb.transactions.prepare(
   ....:     operation='CREATE',
   ....:     owners_before=bob.verifying_key,
   ....:     owners_after=[([carly.verifying_key], 10)],
   ....:     asset=bicycle_token
   ....: )
   ....: 

In [25]: fulfilled_token_tx = bdb.transactions.fulfill(
   ....:     prepared_token_tx, private_keys=bob.signing_key)
   ....: 

Sending the transaction:

>>> sent_token_tx = bdb.transactions.send(fulfilled_token_tx)

Note

Defining owners_after.

For divisible assets we need to specify the amounts togheter with the public keys. The way we do this is by passing a list of tuples in owners_after in which each tuple corresponds to a condition.

For instance instead of creating a transaction with 1 condition with amount=10 we could have created a transaction with 2 conditions with amount=5 with:

owners_after=[([carly.verifying_key], 5), ([carly.verifying_key], 5)]

The reason why the addresses are contained in lists is because each condition can have multiple ownership. For instance we can create a condition with amount=10 in which both Carly and Alice are owners with:

owners_after=[([carly.verifying_key, alice.verifying_key], 10)]
>>> sent_token_tx == fulfilled_token_tx
True

The fulfilled_token_tx dictionary should look something like:

In [26]: fulfilled_token_tx
Out[26]: 
{'id': 'c91e99c00c162b7303ed7e1942a966b6c3262b9732b0b701a7851e6d0cae44b8',
 'transaction': {'asset': {'data': {'description': 'Time share token. Each token equals 1 hour of riding.',
    'token_for': {'bicycle': {'manufacturer': 'bkfab',
      'serial_number': 'abcd1234'}}},
   'divisible': True,
   'id': 'b3497568-2e6a-41a2-9a67-eedfcb8fb60c',
   'refillable': False,
   'updatable': False},
  'conditions': [{'amount': 10,
    'cid': 0,
    'condition': {'details': {'bitmask': 32,
      'public_key': 'HvDFqsK5fyv7GSuSzFHgW3HoEYTjz4n5RwiVb22HHFPD',
      'signature': None,
      'type': 'fulfillment',
      'type_id': 4},
     'uri': 'cc:4:20:-1yhqfUZTEmCtzBW5kix_NxATAo9lbyMjYVjNmA5T2A:96'},
    'owners_after': ['HvDFqsK5fyv7GSuSzFHgW3HoEYTjz4n5RwiVb22HHFPD']}],
  'fulfillments': [{'fid': 0,
    'fulfillment': 'cf:4:r6LAw-pCeb5EkCPDpn8g_1_CEjH7S3vp3GJaUylitkw_4nUCsP2jOHW6wJYRBoWT45wX76V1wxT_pZ5eO70fVHcEaUteBRwJNwddbogqHyKXSh9xwpZdiETfKczcaRAD',
    'input': None,
    'owners_before': ['CpcHdP9XjrdxGQapfrZPz9VsukogyzSwcKHQzwd1zhw9']}],
  'metadata': None,
  'operation': 'CREATE'},
 'version': 1}

Bob is the issuer:

In [27]: fulfilled_token_tx['transaction']['fulfillments'][0]['owners_before'][0] == bob.verifying_key
Out[27]: True

Carly is the owner of 10 tokens:

In [28]: fulfilled_token_tx['transaction']['conditions'][0]['owners_after'][0] == carly.verifying_key
Out[28]: True

In [29]: fulfilled_token_tx['transaction']['conditions'][0]['amount'] == 10
Out[29]: True

Now Carly wants to ride the bicycle for 2 hours so she needs to send 2 tokens to Bob:

In [30]: cid = 0

In [31]: condition = prepared_token_tx['transaction']['conditions'][cid]

In [32]: transfer_input = {
   ....:     'fulfillment': condition['condition']['details'],
   ....:     'input': {
   ....:         'cid': cid,
   ....:         'txid': prepared_token_tx['id'],
   ....:     },
   ....:     'owners_before': condition['owners_after'],
   ....: }
   ....: 

In [33]: prepared_transfer_tx = bdb.transactions.prepare(
   ....:     operation='TRANSFER',
   ....:     asset=prepared_token_tx['transaction']['asset'],
   ....:     inputs=transfer_input,
   ....:     owners_after=[([bob.verifying_key], 2), ([carly.verifying_key], 8)]
   ....: )
   ....: 

In [34]: fulfilled_transfer_tx = bdb.transactions.fulfill(
   ....:     prepared_transfer_tx, private_keys=carly.signing_key)
   ....: 
>>> sent_transfer_tx = bdb.transactions.send(fulfilled_transfer_tx)
>>> sent_transfer_tx == fulfilled_transfer_tx
True

When transferring divisible assets BigchainDB makes sure that the amount being used is the same as the amount being spent. This ensures that no amounts are lost. For this reason, if Carly wants to transfer 2 tokens of her 10 tokens she needs to reassign the remaining 8 tokens to herself.

The fulfilled_transfer_tx with 2 conditions, one with amount=2 and the other with amount=8 dictionary should look something like:

In [35]: fulfilled_transfer_tx
Out[35]: 
{'id': '2d612ca55b2a07d9bd6f9cc1f7873dbe4d64a21639cc55cc7fc18d737e651dc2',
 'transaction': {'asset': {'id': 'b3497568-2e6a-41a2-9a67-eedfcb8fb60c'},
  'conditions': [{'amount': 2,
    'cid': 0,
    'condition': {'details': {'bitmask': 32,
      'public_key': 'CpcHdP9XjrdxGQapfrZPz9VsukogyzSwcKHQzwd1zhw9',
      'signature': None,
      'type': 'fulfillment',
      'type_id': 4},
     'uri': 'cc:4:20:r6LAw-pCeb5EkCPDpn8g_1_CEjH7S3vp3GJaUylitkw:96'},
    'owners_after': ['CpcHdP9XjrdxGQapfrZPz9VsukogyzSwcKHQzwd1zhw9']},
   {'amount': 8,
    'cid': 1,
    'condition': {'details': {'bitmask': 32,
      'public_key': 'HvDFqsK5fyv7GSuSzFHgW3HoEYTjz4n5RwiVb22HHFPD',
      'signature': None,
      'type': 'fulfillment',
      'type_id': 4},
     'uri': 'cc:4:20:-1yhqfUZTEmCtzBW5kix_NxATAo9lbyMjYVjNmA5T2A:96'},
    'owners_after': ['HvDFqsK5fyv7GSuSzFHgW3HoEYTjz4n5RwiVb22HHFPD']}],
  'fulfillments': [{'fid': 0,
    'fulfillment': 'cf:4:-1yhqfUZTEmCtzBW5kix_NxATAo9lbyMjYVjNmA5T2DorMgcSELZPlE1WdkN1aRQ5Dyad_KtnEuA_i-VUPTAy7a4TGvKsyera_iNvN9PEbKRFDvm7S2AwJB-xE-AC9YD',
    'input': {'cid': 0,
     'txid': 'c91e99c00c162b7303ed7e1942a966b6c3262b9732b0b701a7851e6d0cae44b8'},
    'owners_before': ['HvDFqsK5fyv7GSuSzFHgW3HoEYTjz4n5RwiVb22HHFPD']}],
  'metadata': None,
  'operation': 'TRANSFER'},
 'version': 1}