BigchainDB Python Driver¶
Important
Development Status: Alpha
BigchainDB Python Driver¶
- Free software: Apache Software License 2.0
- Documentation: https://docs.bigchaindb.com/projects/py-driver/
Features¶
- Support for preparing, fulfilling, and sending transactions to a BigchainDB node.
- Retrieval of transactions by id.
- Getting status of a transaction by id.
Compatibility Matrix¶
BigchainDB Server | BigchainDB Driver |
---|---|
0.8.x |
0.1.x |
Although we do our best to keep the master branches in sync, there may be occasional delays.
Credits¶
This package was initially created using Cookiecutter and the audreyr/cookiecutter-pypackage project template. Many BigchainDB developers have contributed since then.
Quickstart / Installation¶
The BigchainDB Python Driver depends on:
libffi/ffi.h
- Python 3.5+
- A recent Python 3 version of
pip
- A recent Python 3 version of
setuptools
If you’re missing one of those, then see below. Otherwise, you can install the BigchainDB Python Driver (bigchaindb_driver
) using:
pip install bigchaindb_driver
and then you can try the Basic Usage Examples.
How to Install the Dependencies¶
Dependency 1: ffi.h¶
BigchainDB (server and driver) depends on cryptoconditions,
which depends on PyNaCl (Networking and Cryptography library),
which depends on ffi.h
.
Hence, depending on your setup, you may need to install the
development files for libffi
.
On Ubuntu 14.04 and 16.04, this works:
sudo apt-get update
sudo apt-get install libffi-dev
On Fedora 23 and 24, this works:
sudo dnf update
sudo dnf install libffi-devel
For other operating systems, just do some web searches for “ffi.h” with the name of your OS.
Dependency 2: Python 3.5+¶
The BigchainDB Python Driver uses Python 3.5+. You can check your version of Python using:
python --version
An easy way to install a specific version of Python, and to switch between versions of Python, is to use virtualenv. Another option is conda.
Dependency 3: pip¶
You also need to get a recent, Python 3 version of pip
, the Python package manager.
If you’re using virtualenv or conda, then each virtual environment should include an appropriate version of pip
.
You can check your version of pip
using:
pip --version
pip
was at version 9.0.0 as of November 2016.
If you need to upgrade your version of pip
,
then see the pip documentation
or our page about that in the BigchainDB Server docs.
Dependency 4: setuptools¶
Once you have a recent Python 3 version of pip
, you should be able to upgrade setuptools
using:
pip install --upgrade setuptools
Installing from the Source Code¶
The source code for the BigchainDB Python Driver can be downloaded from the Github repo. You can either clone the public repository:
git clone git://github.com/bigchaindb/bigchaindb-driver
Or download the tarball:
curl -OL https://github.com/bigchaindb/bigchaindb-driver/tarball/master
Once you have a copy of the source code, you can install it by going to the directory containing setup.py
and doing:
python setup.py install
Installing latest master with pip¶
In order to work with the latest BigchainDB (server) master branch:
$ pip install --process-dependency-links git+https://github.com/bigchaindb/bigchaindb-driver.git
Point to some BigchainDB node, which is running BigchainDB server master
:
from bigchaindb_driver import BigchainDB
bdb = BigchainDB('http://here.be.dragons:9984/api/v1')
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': '928b5d3f506b63229689e5e796a67df68d2aad741b8cd28bd59d744e5dc1e03f',
'transaction': {'asset': {'data': {'bicycle': {'manufacturer': 'bkfab',
'serial_number': 'abcd1234'}},
'divisible': False,
'id': '0d9099d3-72af-4a26-9d9a-f27513f2826d',
'refillable': False,
'updatable': False},
'conditions': [{'amount': 1,
'cid': 0,
'condition': {'details': {'bitmask': 32,
'public_key': 'GtZZNoNb4B4PPnseS8Shw295zcf5Pgqb3LrNiCCSStXr',
'signature': None,
'type': 'fulfillment',
'type_id': 4},
'uri': 'cc:4:20:7BS9JG6ylfsyfBDV43f4OSTvyvzAW5f4rjDnFD-DJcE:96'},
'owners_after': ['GtZZNoNb4B4PPnseS8Shw295zcf5Pgqb3LrNiCCSStXr']}],
'fulfillments': [{'fid': 0,
'fulfillment': {'bitmask': 32,
'public_key': 'GtZZNoNb4B4PPnseS8Shw295zcf5Pgqb3LrNiCCSStXr',
'signature': None,
'type': 'fulfillment',
'type_id': 4},
'input': None,
'owners_before': ['GtZZNoNb4B4PPnseS8Shw295zcf5Pgqb3LrNiCCSStXr']}],
'metadata': {'data': {'planet': 'earth'},
'id': '18811fc6-9862-4800-bdf4-ae83319b6689'},
'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': '928b5d3f506b63229689e5e796a67df68d2aad741b8cd28bd59d744e5dc1e03f',
'transaction': {'asset': {'data': {'bicycle': {'manufacturer': 'bkfab',
'serial_number': 'abcd1234'}},
'divisible': False,
'id': '0d9099d3-72af-4a26-9d9a-f27513f2826d',
'refillable': False,
'updatable': False},
'conditions': [{'amount': 1,
'cid': 0,
'condition': {'details': {'bitmask': 32,
'public_key': 'GtZZNoNb4B4PPnseS8Shw295zcf5Pgqb3LrNiCCSStXr',
'signature': None,
'type': 'fulfillment',
'type_id': 4},
'uri': 'cc:4:20:7BS9JG6ylfsyfBDV43f4OSTvyvzAW5f4rjDnFD-DJcE:96'},
'owners_after': ['GtZZNoNb4B4PPnseS8Shw295zcf5Pgqb3LrNiCCSStXr']}],
'fulfillments': [{'fid': 0,
'fulfillment': 'cf:4:7BS9JG6ylfsyfBDV43f4OSTvyvzAW5f4rjDnFD-DJcGLdyjagCg1sWmMjzzx_-yw1YFDBlY4OGbUSC5w6_QJxT-Xvq0YaYsrjWMOtT0_n9ewmvCwqYwY4ZKHIieviLsD',
'input': None,
'owners_before': ['GtZZNoNb4B4PPnseS8Shw295zcf5Pgqb3LrNiCCSStXr']}],
'metadata': {'data': {'planet': 'earth'},
'id': '18811fc6-9862-4800-bdf4-ae83319b6689'},
'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]: '928b5d3f506b63229689e5e796a67df68d2aad741b8cd28bd59d744e5dc1e03f'
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': '2e534b77d233134c5aa7d3ce1997ca10adbbb418b90bb9c4ef03fd39a79157a6',
'transaction': {'asset': {'id': '0d9099d3-72af-4a26-9d9a-f27513f2826d'},
'conditions': [{'amount': 1,
'cid': 0,
'condition': {'details': {'bitmask': 32,
'public_key': 'DKbQHRdFQLTsbG9fAoAKXVvYhzot5EnBCFSnEcgtrxB3',
'signature': None,
'type': 'fulfillment',
'type_id': 4},
'uri': 'cc:4:20:tw-YcweLoIPxiPvRl4oLgR_S7I6UPUfRMsIEpS1uJno:96'},
'owners_after': ['DKbQHRdFQLTsbG9fAoAKXVvYhzot5EnBCFSnEcgtrxB3']}],
'fulfillments': [{'fid': 0,
'fulfillment': 'cf:4:7BS9JG6ylfsyfBDV43f4OSTvyvzAW5f4rjDnFD-DJcE5FcUyLwz7x9UBkGlkZqcG4sGSBSgkYVavs54YGNYyWBnJf0g-kEnkUCybcl7KV1Qm5nposMGhV7ZcYC3oj_AC',
'input': {'cid': 0,
'txid': '928b5d3f506b63229689e5e796a67df68d2aad741b8cd28bd59d744e5dc1e03f'},
'owners_before': ['GtZZNoNb4B4PPnseS8Shw295zcf5Pgqb3LrNiCCSStXr']}],
'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': 'cfdefcc4cd8ff33407abcf596b07f654f2e162091adfe214643a6e5848c5bcc5',
'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': '78772bc1-357c-42cc-98d1-dc929fe07e75',
'refillable': False,
'updatable': False},
'conditions': [{'amount': 10,
'cid': 0,
'condition': {'details': {'bitmask': 32,
'public_key': '2aE1Vd6Xi8EAu71HFP11TY4s4cApgLA4NPChZ7YRnaP5',
'signature': None,
'type': 'fulfillment',
'type_id': 4},
'uri': 'cc:4:20:F16P-QAZanLxHEQUGu8ZViBz-liOXLfnND0Mth1Dcww:96'},
'owners_after': ['2aE1Vd6Xi8EAu71HFP11TY4s4cApgLA4NPChZ7YRnaP5']}],
'fulfillments': [{'fid': 0,
'fulfillment': 'cf:4:t_IsrbuIwAHPKwrPnVIhy6oKy8uIKPfBJHt9KX1JCnWZrF7qsMNKcWGUN0wXpgpscqo5b4FMhhusLBmN3-OGavHI-b5e_xikvl1sI3yHJxTl2IpRJlkIH9nuj5BbT-QH',
'input': None,
'owners_before': ['DP3nmRn9aoSUGn2ZzZGQUUD9c2fFZaPhSoNYvN2JJyit']}],
'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