BigchainDB Python Driver¶
Quickstart / Installation¶
Warning
The instructions below were tested on Ubuntu 16.04 LTS. They should also work on other Linux distributions and on macOS. For other operating systems, we recommend using an Ubuntu virtual machine (VM).
The BigchainDB Python Driver depends on:
- Python 3.5+
- A recent Python 3 version of
pip
- A recent Python 3 version of
setuptools
- cryptography and cryptoconditions
If you’re missing one of those, then see “How to Install the Dependencies” below before proceeding.
Next, to install the latest stable BigchainDB Python Driver (bigchaindb_driver
) use:
$ pip3 install bigchaindb_driver
If you want to install an Alpha, Beta or RC version of the Python Driver, use something like:
$ pip3 install bigchaindb_driver==0.5.0a4
The above command will install version 0.5.0a4 (Alpha 4). You can find a list of all versions in the release history page on PyPI.
Next: determine the BigchainDB Root URL of the BigchainDB node or cluster you want to connect to.
How to Install the Dependencies¶
Dependency 1: Python 3.5+¶
The BigchainDB Python Driver uses Python 3.5+. You can check your version of Python using:
$ python --version
OR
$ python3 --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 2: 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
OR
$ pip3 --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.
Dependency 3: setuptools¶
Once you have a recent Python 3 version of pip
, you should be able to upgrade setuptools
using:
$ pip install --upgrade setuptools
OR
$ pip3 install --upgrade setuptools
Dependency 4: cryptography and cryptoconditions¶
BigchainDB(server and driver) also depends on cryptography and cryptoconditions.
- cryptography depends on libssl, libcrypto which also depends on (Python development library and header files).
- cryptoconditions depends on PyNaCl (Networking and Cryptography library) which depends on
ffi.h
.
On Ubuntu 14.04 and 16.04, this works:
$ sudo apt-get update
$ sudo apt-get install python3-dev libssl-dev libffi-dev
For other operating systems, please refer to the cryptography installation guide.
Installing the Driver¶
Now you can install the BigchainDB Python Driver (bigchaindb_driver
) using:
$ pip install bigchaindb_driver
OR
$ pip3 install bigchaindb_driver
Next: determine the BigchainDB Root URL of the BigchainDB node or cluster you want to connect to.
Advanced Installation Options¶
See the Advanced Installation Options page.
Installation Guide for Developers¶
Here’s how to set up bigchaindb-driver for local development.
Fork the bigchaindb-driver repo on GitHub.
Clone your fork locally and enter into the project:
$ git clone git@github.com:your_name_here/bigchaindb-driver.git $ cd bigchaindb-driver/
Create a branch for local development:
$ git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
When you’re done making changes, check that your changes pass flake8 and the tests. For the tests, you’ll need to start the MongoDB, Tendermint and BigchainDB servers:
$ docker-compose up -d bigchaindb
flake8 check:
$ docker-compose run --rm bigchaindb-driver flake8 bigchaindb_driver tests
To run the tests:
$ docker-compose run --rm bigchaindb-driver pytest -v
Commit your changes and push your branch to GitHub:
$ git add . $ git commit -m "Your detailed description of your changes." $ git push origin name-of-your-bugfix-or-feature
We use pre-commit which should be triggered with every commit. Some hooks will change files but others will give errors that needs to be fixed. Every time a hook is failing you need to add the changed files again. The hooks we use can be found in the yaml config file.
- Submit a pull request through the GitHub website.
Determine the BigchainDB Root URL¶
If you want to use the BigchainDB Python Driver to communicate with a BigchainDB node or cluster, then you will need its BigchainDB Root URL. This page is to help you determine it.
Case 1: BigchainDB on localhost¶
If a BigchainDB node is running locally
(and the BIGCHAINDB_SERVER_BIND
setting wasn’t changed
from the default localhost:9984
),
then the BigchainDB Root URL is:
bdb_root_url = 'http://localhost:9984'
Case 2: A Cluster Hosted by Someone Else¶
If you’re connecting to a BigchainDB cluster hosted by someone else, then they’ll tell you their BigchaindB Root URL. It can take many forms. It can use HTTP or HTTPS. It can use a hostname or an IP address. The port might not be 9984. Here are some examples:
bdb_root_url = 'http://example.com:9984'
bdb_root_url = 'http://api.example.com:9984'
bdb_root_url = 'http://example.com:1234'
bdb_root_url = 'http://example.com' # http is port 80 by default
bdb_root_url = 'https://example.com' # https is port 443 by default
bdb_root_url = 'http://12.34.56.123:9984'
bdb_root_url = 'http://12.34.56.123:5000'
Case 3: Docker Container on localhost¶
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 bigchaindb-driver
linked
(container) service, use:
bdb_root_url = 'http://bdb-server:9984'
Alternatively, you may connect to the containerized BigchainDB node from “outside”, in which case you need to know the port binding:
$ docker-compose port bigchaindb 9984
0.0.0.0:32780
or you can use the command specified in the Makefile:
$ make root-url
0.0.0.0:32780
bdb_root_url = 'http://0.0.0.0:32780'
Next, try some of the basic usage examples.
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': None,
'inputs': [{'fulfillment': {'public_key': 'DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK',
'type': 'ed25519-sha-256'},
'fulfills': None,
'owners_before': ['DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK']}],
'metadata': {'planet': 'earth'},
'operation': 'CREATE',
'outputs': [{'amount': '1',
'condition': {'details': {'public_key': 'DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK',
'type': 'ed25519-sha-256'},
'uri': 'ni:///sha-256;dD-Wnnr8RzsqVOb85S7J3p60HygiYKvvYDt6177qHjc?fpt=ed25519-sha-256&cost=131072'},
'public_keys': ['DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK']}],
'version': '2.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': '7ac83ca96673609ec935e4420b55014970474b3286e3d2386fc5dfcbd459528a',
'inputs': [{'fulfillment': 'pGSAILlLac2VNIVJuQFX0_sbKmEnqyqpBTo06n4-w3Rcy6B8gUCh2etTnpS01Cxi0hff0M3PHSJVocHoSpF4D7eGBoS8jKUYxXQ45EnsZMyN6U-EJGzn0b7FmBkcSIZfXWhLON0J',
'fulfills': None,
'owners_before': ['DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK']}],
'metadata': {'planet': 'earth'},
'operation': 'CREATE',
'outputs': [{'amount': '1',
'condition': {'details': {'public_key': 'DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK',
'type': 'ed25519-sha-256'},
'uri': 'ni:///sha-256;dD-Wnnr8RzsqVOb85S7J3p60HygiYKvvYDt6177qHjc?fpt=ed25519-sha-256&cost=131072'},
'public_keys': ['DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK']}],
'version': '2.0'}
And sent over to a BigchainDB node:
>>> sent_creation_tx = bdb.transactions.send_commit(fulfilled_creation_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 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]: '7ac83ca96673609ec935e4420b55014970474b3286e3d2386fc5dfcbd459528a'
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 not valid 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))
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_index': 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,
....: )
....:
The fulfilled_transfer_tx
dictionary should look something like:
In [24]: fulfilled_transfer_tx
Out[24]:
{'asset': {'id': '7ac83ca96673609ec935e4420b55014970474b3286e3d2386fc5dfcbd459528a'},
'id': '8709752ea307c1d2a3d599ef94bbfc75d7c3114db774c7ffe868f74a089d7ce8',
'inputs': [{'fulfillment': 'pGSAILlLac2VNIVJuQFX0_sbKmEnqyqpBTo06n4-w3Rcy6B8gUCxqyl7NN18mWEUavc8RqUxe9H2cVyJNZgkRqYYXHOm8WuBA9QbQf9k5jKshcf1O26AmZeXS4EdWVsvNLUABtUM',
'fulfills': {'output_index': 0,
'transaction_id': '7ac83ca96673609ec935e4420b55014970474b3286e3d2386fc5dfcbd459528a'},
'owners_before': ['DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK']}],
'metadata': None,
'operation': 'TRANSFER',
'outputs': [{'amount': '1',
'condition': {'details': {'public_key': '9a4ynyU3i1kNJ8qsaD1DPWScJEPB5EuZuPvWyBTL2JPM',
'type': 'ed25519-sha-256'},
'uri': 'ni:///sha-256;KKbKJAgYQib-SFpRKqJ3pRtK0Wpy-VJlTubjjxl27Mk?fpt=ed25519-sha-256&cost=131072'},
'public_keys': ['9a4ynyU3i1kNJ8qsaD1DPWScJEPB5EuZuPvWyBTL2JPM']}],
'version': '2.0'}
and finally send it to the connected BigchainDB node:
>>> sent_transfer_tx = bdb.transactions.send_commit(fulfilled_transfer_tx)
>>> sent_transfer_tx == fulfilled_transfer_tx
True
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
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_commit(fulfilled_creation_tx)
txid = fulfilled_creation_tx['id']
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_index': 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_commit(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)
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)
....:
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': '90834b801e33babaa972f946b36f6ddbacb76178d02f4fbd4d019726dd1fb1cc',
'inputs': [{'fulfillment': 'pGSAIKds4Gu9u7G3pfGriX4ksect1LhvpBMoFyut9XjWSnE0gUBEfKTLwTOB805Gjq0pMD1IWQrjIb2kU3wRaVRUnqLzs_tEA7HmQ2h-J4kXQ7NaSEkm4oEKNE0hcb62RXH7KqIM',
'fulfills': None,
'owners_before': ['CGZNqpfucgT9msUbrkJJgh7XWPC4U3FvN6KoaPbEfhW3']}],
'metadata': None,
'operation': 'CREATE',
'outputs': [{'amount': '10',
'condition': {'details': {'public_key': '2LcMpjQVUnVgJxG7VRqT3YnKi3iBjsYWtpUGcYaiBcUp',
'type': 'ed25519-sha-256'},
'uri': 'ni:///sha-256;-dyWQUGEsjiYY5KwZfhzOJvSpa270coqj33547w4S3s?fpt=ed25519-sha-256&cost=131072'},
'public_keys': ['2LcMpjQVUnVgJxG7VRqT3YnKi3iBjsYWtpUGcYaiBcUp']}],
'version': '2.0'}
Sending the transaction:
>>> sent_token_tx = bdb.transactions.send_commit(fulfilled_token_tx)
>>> sent_token_tx == fulfilled_token_tx
True
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
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)]
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'