diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index acd281ca..65fdb205 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,17 @@ If you want to file a bug report, suggest a feature, or ask a code-related quest ## How to Contribute Code or Documentation -### Step 0 - Prepare and Familiarize Yourself +### Step 0 - Decide on an Issue to Resolve, or Create One + +We want you to feel like your contributions (pull requests) are welcome, but if you contribute something unnecessary, unwanted, or perplexing, then your experience may be unpleasant. Your pull request may sit gathering dust as everyone scratches their heads wondering what to do with it. + +To prevent that situation, we ask that all pull requests should resolve, address, or fix an existing issue. If there is no existing issue, then you should create one first. That way there can be commentary and discussion first, and you can have a better idea of what to expect when you create a corresponding pull request. + +When you submit a pull request, please mention the issue (or issues) that it resolves, e.g. "Resolves #123". + +Exception: hotfixes and minor changes don't require a pre-existing issue, but please write a thorough pull request description. + +### Step 1 - Prepare and Familiarize Yourself To contribute code or documentation, you need a [GitHub account](https://github.com/signup/free). @@ -23,16 +33,13 @@ Familiarize yourself with how we do coding and documentation in the BigchainDB p * the GitHub Flow (workflow) * [GitHub Guide: Understanding the GitHub Flow](https://guides.github.com/introduction/flow/) * [Scott Chacon's blog post about GitHub Flow](http://scottchacon.com/2011/08/31/github-flow.html) - * Note that we call the main branch `develop` rather than `master` * [semantic versioning](http://semver.org/) -Note: We have a slight variation on the GitHub Flow: we call the default branch `develop` rather than `master`. - -### Step 1 - Fork bigchaindb on GitHub +### Step 2 - Fork bigchaindb on GitHub In your web browser, go to [the BigchainDB repository on GitHub](https://github.com/bigchaindb/bigchaindb) and click the `Fork` button in the top right corner. This creates a new Git repository named `bigchaindb` in _your_ GitHub account. -### Step 2 - Clone Your Fork +### Step 3 - Clone Your Fork (This only has to be done once.) In your local terminal, use Git to clone _your_ `bigchaindb` repository to your local computer. Also add the original GitHub bigchaindb/bigchaindb repository as a remote named `upstream` (a convention): ```text @@ -41,16 +48,16 @@ cd bigchaindb git add upstream git@github.com:bigchaindb/bigchaindb.git ``` -### Step 3 - Fetch and Merge the Latest from `upstream/develop` +### Step 4 - Fetch and Merge the Latest from `upstream/master` -Switch to the `develop` branch locally, fetch all `upstream` branches, and merge the just-fetched `upstream/develop` branch with the local `develop` branch: +Switch to the `master` branch locally, fetch all `upstream` branches, and merge the just-fetched `upstream/master` branch with the local `master` branch: ```text -git checkout develop +git checkout master git fetch upstream -git merge upstream/develop +git merge upstream/master ``` -### Step 4 - Create a New Branch for Each Bug/Feature +### Step 5 - Create a New Branch for Each Bug/Feature If your new branch is to **fix a bug** identified in a specific GitHub Issue with number `ISSNO`, then name your new branch `bug/ISSNO/short-description-here`. For example, `bug/67/fix-leap-year-crash`. @@ -61,7 +68,7 @@ Otherwise, please give your new branch a short, descriptive, all-lowercase name. git checkout -b new-branch-name ``` -### Step 5 - Make Edits, git add, git commit +### Step 6 - Make Edits, git add, git commit With your new branch checked out locally, make changes or additions to the code or documentation. Remember to: @@ -79,27 +86,27 @@ git commit -m "Short description of new or changed things" You will want to merge changes from upstream (i.e. the original repository) into your new branch from time to time, using something like: ```text git fetch upstream -git merge upstream/develop +git merge upstream/master ``` Once you're done commiting a set of new things and you're ready to submit them for inclusion, please be sure to run all the tests (as per the instructions at the end of our [Python Style Guide](PYTHON_STYLE_GUIDE.md)). -If your addition or change is substantial, then please add a line or two to the [CHANGELOG.md file](https://github.com/bigchaindb/bigchaindb/blob/develop/CHANGELOG.md), following the guidelines given at the top of that file. +If your addition or change is substantial, then please add a line or two to the [CHANGELOG.md file](https://github.com/bigchaindb/bigchaindb/blob/master/CHANGELOG.md), following the guidelines given at the top of that file. (When you submit your pull request [following the instructions below], we run all the tests automatically, so we will see if some are failing. If you don't know why some tests are failing, you can still submit your pull request, but be sure to note the failing tests and to ask for help with resolving them.) -### Step 6 - Push Your New Branch to origin +### Step 7 - Push Your New Branch to origin Make sure you've commited all the additions or changes you want to include in your pull request. Then push your new branch to origin (i.e. _your_ remote bigchaindb repository). ```text git push origin new-branch-name ``` -### Step 7 - Create a Pull Request +### Step 8 - Create a Pull Request Go to the GitHub website and to _your_ remote bigchaindb repository (i.e. something like https://github.com/your-user-name/bigchaindb). -See [GitHub's documentation on how to initiate and send a pull request](https://help.github.com/articles/using-pull-requests/). Note that the destination repository should be `bigchaindb/bigchaindb` and the destination branch will be `develop` (usually, and if it's not, then we can change that if necessary). +See [GitHub's documentation on how to initiate and send a pull request](https://help.github.com/articles/using-pull-requests/). Note that the destination repository should be `bigchaindb/bigchaindb` and the destination branch will be `master` (usually, and if it's not, then we can change that if necessary). If this is the first time you've submitted a pull request to BigchainDB, then you must read and accept the Contributor License Agreement (CLA) before we can merge your contributions. That can be found at [https://www.bigchaindb.com/cla](https://www.bigchaindb.com/cla). @@ -115,4 +122,4 @@ Someone will then merge your branch or suggest changes. If we suggsest changes, * [BigchainDB Licenses](./LICENSES.md) * [Contributor License Agreement](https://www.bigchaindb.com/cla) -(Note: GitHub automatically links to CONTRIBUTING.md when a contributor creates an Issue or opens a Pull Request.) \ No newline at end of file +(Note: GitHub automatically links to CONTRIBUTING.md when a contributor creates an Issue or opens a Pull Request.) diff --git a/PYTHON_STYLE_GUIDE.md b/PYTHON_STYLE_GUIDE.md index 4d4d4888..536ed321 100644 --- a/PYTHON_STYLE_GUIDE.md +++ b/PYTHON_STYLE_GUIDE.md @@ -61,6 +61,6 @@ We write unit tests for our Python code using the [pytest](http://pytest.org/lat All tests go in the `bigchaindb/tests` directory or one of its subdirectories. You can use the tests already in there as templates or examples. -The BigchainDB Documentation has a [section explaining how to run all unit tests](http://bigchaindb.readthedocs.org/en/develop/running-unit-tests.html). +The BigchainDB Documentation has a [section explaining how to run all unit tests](http://bigchaindb.readthedocs.org/en/master/running-unit-tests.html). **Automated testing of pull requests.** We use [Travis CI](https://travis-ci.com/), so that whenever someone creates a new BigchainDB pull request on GitHub, Travis CI gets the new code and does _a bunch of stuff_. You can find out what we tell Travis CI to do in [the `.travis.yml` file](.travis.yml): it tells Travis CI how to install BigchainDB, how to run all the tests, and what to do "after success" (e.g. run `codecov`). (We use [Codecov](https://codecov.io/) to get a rough estimate of our test coverage.) diff --git a/README.md b/README.md index e72a55a8..5c38f639 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,21 @@ +[![Join the chat at https://gitter.im/bigchaindb/bigchaindb](https://badges.gitter.im/bigchaindb/bigchaindb.svg)](https://gitter.im/bigchaindb/bigchaindb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![PyPI](https://img.shields.io/pypi/v/bigchaindb.svg)](https://pypi.python.org/pypi/BigchainDB) +[![Travis branch](https://img.shields.io/travis/bigchaindb/bigchaindb/master.svg)](https://travis-ci.org/bigchaindb/bigchaindb) +[![Codecov branch](https://img.shields.io/codecov/c/github/bigchaindb/bigchaindb/master.svg)](https://codecov.io/github/bigchaindb/bigchaindb?branch=master) +[![Documentation Status](https://readthedocs.org/projects/bigchaindb/badge/?version=stable)](https://bigchaindb.readthedocs.org/en/stable/) + + # BigchainDB A scalable blockchain database. [The whitepaper](https://www.bigchaindb.com/whitepaper/) explains what that means. -[![Join the chat at https://gitter.im/bigchaindb/bigchaindb](https://badges.gitter.im/bigchaindb/bigchaindb.svg)](https://gitter.im/bigchaindb/bigchaindb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![PyPI](https://img.shields.io/pypi/v/bigchaindb.svg)](https://pypi.python.org/pypi/BigchainDB) -[![Travis branch](https://img.shields.io/travis/bigchaindb/bigchaindb/develop.svg)](https://travis-ci.org/bigchaindb/bigchaindb) -[![Codecov branch](https://img.shields.io/codecov/c/github/bigchaindb/bigchaindb/develop.svg)](https://codecov.io/github/bigchaindb/bigchaindb?branch=develop) -[![Documentation Status](https://readthedocs.org/projects/bigchaindb/badge/?version=develop)](http://bigchaindb.readthedocs.org/en/develop/?badge=develop) ## Quick Start -### [Install and Run BigchainDB Server](http://bigchaindb.readthedocs.org/en/develop/installing-server.html) -### [Run BigchainDB with Docker](http://bigchaindb.readthedocs.org/en/develop/installing-server.html#run-bigchaindb-with-docker) -### [The Python Server API by Example](http://bigchaindb.readthedocs.org/en/develop/python-server-api-examples.html) -### [The Python Driver API by Example](http://bigchaindb.readthedocs.org/en/develop/python-driver-api-examples.html) +### [Install and Run BigchainDB Server](http://bigchaindb.readthedocs.org/en/master/installing-server.html) +### [Run BigchainDB with Docker](http://bigchaindb.readthedocs.org/en/master/installing-server.html#run-bigchaindb-with-docker) +### [The Python Server API by Example](http://bigchaindb.readthedocs.org/en/master/python-server-api-examples.html) +### [The Python Driver API by Example](http://bigchaindb.readthedocs.org/en/master/python-driver-api-examples.html) ## Links for Everyone * [BigchainDB.com](https://www.bigchaindb.com/) - the main BigchainDB website, including newsletter signup @@ -24,7 +26,7 @@ A scalable blockchain database. [The whitepaper](https://www.bigchaindb.com/whit * [Google Group](https://groups.google.com/forum/#!forum/bigchaindb) ## Links for Developers -* [Documentation](http://bigchaindb.readthedocs.org/en/develop/#) - for developers +* [Documentation](http://bigchaindb.readthedocs.org/en/master/) - for developers * [CONTRIBUTING.md](CONTRIBUTING.md) - how to contribute * [Community guidelines](CODE_OF_CONDUCT.md) * [Open issues](https://github.com/bigchaindb/bigchaindb/issues) diff --git a/bigchaindb/config_utils.py b/bigchaindb/config_utils.py index 0ce41360..53eb7954 100644 --- a/bigchaindb/config_utils.py +++ b/bigchaindb/config_utils.py @@ -185,15 +185,14 @@ def autoconfigure(filename=None, config=None, force=False): newconfig = env_config(bigchaindb.config) - if config: - newconfig = update(newconfig, config) - try: - # import pdb; pdb.set_trace() newconfig = update(newconfig, file_config(filename=filename)) except FileNotFoundError as e: logger.warning('Cannot find config file `%s`.' % e.filename) + if config: + newconfig = update(newconfig, config) + dict_config(newconfig) return newconfig diff --git a/bigchaindb/consensus.py b/bigchaindb/consensus.py index 3c622280..593aea9f 100644 --- a/bigchaindb/consensus.py +++ b/bigchaindb/consensus.py @@ -153,12 +153,7 @@ class BaseConsensusRules(AbstractConsensusRules): 'input `{}` was already spent'.format(fulfillment['input'])) # Check hash of the transaction - # remove the fulfillment messages (signatures) - transaction_data = copy.deepcopy(transaction) - for fulfillment in transaction_data['transaction']['fulfillments']: - fulfillment['fulfillment'] = None - - calculated_hash = crypto.hash_data(util.serialize(transaction_data['transaction'])) + calculated_hash = util.get_hash_data(transaction) if calculated_hash != transaction['id']: raise exceptions.InvalidHash() diff --git a/bigchaindb/util.py b/bigchaindb/util.py index 9ad285f9..85ffc418 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -217,8 +217,7 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None): } # serialize and convert to bytes - tx_serialized = serialize(tx) - tx_hash = crypto.hash_data(tx_serialized) + tx_hash = get_hash_data(tx) # create the transaction transaction = { @@ -295,7 +294,7 @@ def create_and_sign_tx(private_key, current_owner, new_owner, tx_input, operatio def check_hash_and_signature(transaction): # Check hash of the transaction - calculated_hash = crypto.hash_data(serialize(transaction['transaction'])) + calculated_hash = get_hash_data(transaction) if calculated_hash != transaction['id']: raise exceptions.InvalidHash() @@ -336,7 +335,17 @@ def verify_signature(signed_transaction): return True -def get_fulfillment_message(transaction, fulfillment): +def get_fulfillment_message(transaction, fulfillment, serialized=False): + """Get the fulfillment message for signing a specific fulfillment in a transaction + + Args: + transaction (dict): a transaction + fulfillment (dict): a specific fulfillment (for a condition index) within the transaction + serialized (bool): False returns a dict, True returns a serialized string + + Returns: + str|dict: fulfillment message + """ b = bigchaindb.Bigchain() common_data = { @@ -365,9 +374,31 @@ def get_fulfillment_message(transaction, fulfillment): current_owner = transaction['transaction']['fulfillments'][0]['current_owners'][0] condition = json.loads(Ed25519Fulfillment(public_key=current_owner).serialize_json()) fulfillment_message['condition'] = {'condition': {'details': condition}} + if serialized: + return serialize(fulfillment_message) return fulfillment_message +def get_hash_data(transaction): + """ Get the hashed data that (should) correspond to the `transaction['id']` + + Args: + transaction (dict): the transaction to be hashed + + Returns: + str: the hash of the transaction + """ + tx = copy.deepcopy(transaction) + if 'transaction' in tx: + tx = tx['transaction'] + + # remove the fulfillment messages (signatures) + for fulfillment in tx['fulfillments']: + fulfillment['fulfillment'] = None + + return crypto.hash_data(serialize(tx)) + + def get_subcondition_from_vk(condition, vk): threshold_fulfillment = Fulfillment.from_json(condition['condition']['details']) for subcondition in threshold_fulfillment.subconditions: diff --git a/codecov.yml b/codecov.yml index 490d0805..27507adb 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,5 +1,5 @@ codecov: - branch: develop # the branch to show by default + branch: master # the branch to show by default # The help text for bot says: # "the username that will consume any oauth requests @@ -29,4 +29,4 @@ coverage: comment: layout: "header, diff, changes, sunburst, suggestions" - behavior: default \ No newline at end of file + behavior: default diff --git a/deploy-cluster-aws/create_rethinkdb_conf.py b/deploy-cluster-aws/create_rethinkdb_conf.py index 268dd710..4a11b462 100644 --- a/deploy-cluster-aws/create_rethinkdb_conf.py +++ b/deploy-cluster-aws/create_rethinkdb_conf.py @@ -8,7 +8,7 @@ from __future__ import unicode_literals import os import os.path import shutil -from hostlist import hosts_dev +from hostlist import public_dns_names # cwd = current working directory old_cwd = os.getcwd() @@ -22,7 +22,7 @@ shutil.copy2('rethinkdb.conf.template', 'rethinkdb.conf') # Append additional lines to rethinkdb.conf with open('rethinkdb.conf', 'a') as f: f.write('## The host:port of a node that RethinkDB will connect to\n') - for public_dns_name in hosts_dev: + for public_dns_name in public_dns_names: f.write('join=' + public_dns_name + ':29015\n') os.chdir(old_cwd) diff --git a/deploy-cluster-aws/fab_prepare_chain.py b/deploy-cluster-aws/fab_prepare_chain.py deleted file mode 100644 index caa827ad..00000000 --- a/deploy-cluster-aws/fab_prepare_chain.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- - -""" Generating genesis block -""" - -from __future__ import with_statement, unicode_literals - -from fabric import colors as c -from fabric.api import * -from fabric.api import local, puts, settings, hide, abort, lcd, prefix -from fabric.api import run, sudo, cd, get, local, lcd, env, hide -from fabric.api import task, parallel -from fabric.contrib import files -from fabric.contrib.files import append, exists -from fabric.contrib.console import confirm -from fabric.contrib.project import rsync_project -from fabric.operations import run, put -from fabric.context_managers import settings -from fabric.decorators import roles -from fabtools import * - -env.user = 'ubuntu' -env.key_filename = 'pem/bigchaindb.pem' - -@task -def init_bigchaindb(): - run('bigchaindb -y start &', pty = False) diff --git a/deploy-cluster-aws/fabfile.py b/deploy-cluster-aws/fabfile.py index 22af0f04..f385641b 100644 --- a/deploy-cluster-aws/fabfile.py +++ b/deploy-cluster-aws/fabfile.py @@ -1,47 +1,58 @@ # -*- coding: utf-8 -*- - -"""A fabfile with functionality to prepare, install, and configure -bigchaindb, including its storage backend. +"""A Fabric fabfile with functionality to prepare, install, and configure +BigchainDB, including its storage backend (RethinkDB). """ from __future__ import with_statement, unicode_literals -import requests -from time import * -import os -from datetime import datetime, timedelta -import json -from pprint import pprint - -from fabric import colors as c -from fabric.api import * -from fabric.api import local, puts, settings, hide, abort, lcd, prefix -from fabric.api import run, sudo, cd, get, local, lcd, env, hide +from fabric.api import sudo, env from fabric.api import task, parallel -from fabric.contrib import files -from fabric.contrib.files import append, exists -from fabric.contrib.console import confirm -from fabric.contrib.project import rsync_project +from fabric.contrib.files import sed from fabric.operations import run, put from fabric.context_managers import settings -from fabric.decorators import roles -from fabtools import * -from hostlist import hosts_dev +from hostlist import public_dns_names -env.hosts = hosts_dev -env.roledefs = { - "role1": hosts_dev, - "role2": [hosts_dev[0]], - } -env.roles = ["role1"] +# Ignore known_hosts +# http://docs.fabfile.org/en/1.10/usage/env.html#disable-known-hosts +env.disable_known_hosts = True + +# What remote servers should Fabric connect to? With what usernames? env.user = 'ubuntu' +env.hosts = public_dns_names + +# SSH key files to try when connecting: +# http://docs.fabfile.org/en/1.10/usage/env.html#key-filename env.key_filename = 'pem/bigchaindb.pem' +newrelic_license_key = 'you_need_a_real_license_key' + ###################################################################### -# base software rollout +# DON'T PUT @parallel +@task +def set_hosts(hosts): + """A helper function to change env.hosts from the + command line. + + Args: + hosts (str): 'one_node' or 'two_nodes' + + Example: + fab set_hosts:one_node init_bigchaindb + """ + if hosts == 'one_node': + env.hosts = public_dns_names[:1] + elif hosts == 'two_nodes': + env.hosts = public_dns_names[:2] + else: + raise ValueError('Invalid input to set_hosts.' + ' Expected one_node or two_nodes.' + ' Got {}'.format(hosts)) + + +# Install base software @task @parallel def install_base_software(): @@ -59,7 +70,7 @@ def install_base_software(): python3-pip ipython3 sysstat s3cmd') -# RethinkDB +# Install RethinkDB @task @parallel def install_rethinkdb(): @@ -67,7 +78,7 @@ def install_rethinkdb(): with settings(warn_only=True): # preparing filesystem sudo("mkdir -p /data") - # Locally mounted storage (m3.2xlarge, aber auch c3.xxx) + # Locally mounted storage (m3.2xlarge, but also c3.xxx) try: sudo("umount /mnt") sudo("mkfs -t ext4 /dev/xvdb") @@ -91,27 +102,48 @@ def install_rethinkdb(): sudo('chown -R rethinkdb:rethinkdb /data') # copy config file to target system put('conf/rethinkdb.conf', - '/etc/rethinkdb/instances.d/instance1.conf', mode=0600, use_sudo=True) + '/etc/rethinkdb/instances.d/instance1.conf', + mode=0600, + use_sudo=True) # initialize data-dir sudo('rm -rf /data/*') # finally restart instance sudo('/etc/init.d/rethinkdb restart') -# bigchaindb deployment +# Install BigchainDB (from PyPI) @task @parallel def install_bigchaindb(): sudo('python3 -m pip install bigchaindb') -# startup all nodes of bigchaindb in cluster +# Configure BigchainDB @task @parallel -def start_bigchaindb_nodes(): +def configure_bigchaindb(): + run('bigchaindb -y configure', pty=False) + + +# Initialize BigchainDB +# i.e. create the database, the tables, +# the indexes, and the genesis block. +# (This only needs to be run on one node.) +# Call using: +# fab set_hosts:one_node init_bigchaindb +@task +def init_bigchaindb(): + run('bigchaindb init', pty=False) + + +# Start BigchainDB using screen +@task +@parallel +def start_bigchaindb(): sudo('screen -d -m bigchaindb -y start &', pty=False) +# Install and run New Relic @task def install_newrelic(): with settings(warn_only=True): @@ -119,18 +151,18 @@ def install_newrelic(): # sudo('apt-key adv --keyserver hkp://subkeys.pgp.net --recv-keys 548C16BF') sudo('apt-get update') sudo('apt-get -y --force-yes install newrelic-sysmond') - sudo('nrsysmond-config --set license_key=c88af00c813983f8ee12e9b455aa13fde1cddaa8') + sudo('nrsysmond-config --set license_key=' + newrelic_license_key) sudo('/etc/init.d/newrelic-sysmond restart') -############################### -# Security / FirewallStuff next -############################### +########################### +# Security / Firewall Stuff +########################### @task def harden_sshd(): - """Security harden sshd.""" - + """Security harden sshd. + """ # Disable password authentication sed('/etc/ssh/sshd_config', '#PasswordAuthentication yes', @@ -147,7 +179,8 @@ def harden_sshd(): def disable_root_login(): """Disable `root` login for even more security. Access to `root` account is now possible by first connecting with your dedicated maintenance - account and then running ``sudo su -``.""" + account and then running ``sudo su -``. + """ sudo('passwd --lock root') @@ -172,7 +205,7 @@ def set_fw(): ######################################################### -# some helper-functions to handle bad behavior of cluster +# Some helper-functions to handle bad behavior of cluster ######################################################### # rebuild indexes diff --git a/deploy-cluster-aws/launch_ec2_nodes.py b/deploy-cluster-aws/launch_ec2_nodes.py index c531821b..9ebf3026 100644 --- a/deploy-cluster-aws/launch_ec2_nodes.py +++ b/deploy-cluster-aws/launch_ec2_nodes.py @@ -166,26 +166,31 @@ for i, instance in enumerate(instances_with_tag): format(instance.instance_id)) # Get a list of the pubic DNS names of the instances_with_tag -hosts_dev = [] +public_dns_names = [] for instance in instances_with_tag: public_dns_name = getattr(instance, 'public_dns_name', None) if public_dns_name is not None: - hosts_dev.append(public_dns_name) + public_dns_names.append(public_dns_name) # Write a shellscript to add remote keys to ~/.ssh/known_hosts print('Preparing shellscript to add remote keys to known_hosts') with open('add2known_hosts.sh', 'w') as f: f.write('#!/bin/bash\n') - for public_dns_name in hosts_dev: + for public_dns_name in public_dns_names: f.write('ssh-keyscan ' + public_dns_name + ' >> ~/.ssh/known_hosts\n') -# Create a file named hostlist.py containing hosts_dev. +# Create a file named hostlist.py containing public_dns_names. # If a hostlist.py already exists, it will be overwritten. print('Writing hostlist.py') with open('hostlist.py', 'w') as f: f.write('# -*- coding: utf-8 -*-\n') + f.write('"""A list of the public DNS names of all the nodes in this\n') + f.write('BigchainDB cluster/federation.\n') + f.write('"""\n') + f.write('\n') f.write('from __future__ import unicode_literals\n') - f.write('hosts_dev = {}\n'.format(hosts_dev)) + f.write('\n') + f.write('public_dns_names = {}\n'.format(public_dns_names)) # Wait wait_time = 45 diff --git a/deploy-cluster-aws/startup.sh b/deploy-cluster-aws/startup.sh index 84d420cb..9a73f7f8 100755 --- a/deploy-cluster-aws/startup.sh +++ b/deploy-cluster-aws/startup.sh @@ -55,27 +55,33 @@ chmod +x add2known_hosts.sh # (Re)create the RethinkDB configuration file conf/rethinkdb.conf python create_rethinkdb_conf.py -# rollout base packages (dependencies) needed before -# storage backend (rethinkdb) and bigchaindb can be rolled out +# Rollout base packages (dependencies) needed before +# storage backend (RethinkDB) and BigchainDB can be rolled out fab install_base_software -# rollout storage backend (rethinkdb) +# Rollout storage backend (RethinkDB) and start it fab install_rethinkdb -# rollout bigchaindb +# Rollout BigchainDB (but don't start it yet) fab install_bigchaindb -# generate genesis block -# HORST is the last public_dns_name listed in conf/rethinkdb.conf -# For example: -# ec2-52-58-86-145.eu-central-1.compute.amazonaws.com -HORST=`tail -1 conf/rethinkdb.conf|cut -d: -f1|cut -d= -f2` -fab -H $HORST -f fab_prepare_chain.py init_bigchaindb +# Configure BigchainDB on all nodes +fab configure_bigchaindb -# initiate sharding -fab start_bigchaindb_nodes +# TODO Get public keys from all nodes +# using e.g. bigchaindb export-pubkey + +# TODO Add list of public keys to keyring of all nodes +# using e.g. bigchaindb import-pubkey + +# Send a "bigchaindb init" command to one node +# to initialize the BigchainDB database +# i.e. create the database, the tables, +# the indexes, and the genesis block. +fab set_hosts:one_node init_bigchaindb + +# Start BigchainDB on all the nodes using "screen" +fab start_bigchaindb # cleanup rm add2known_hosts.sh - -# DONE diff --git a/docs/source/_static/tx_multi_condition_multi_fulfillment_v1.png b/docs/source/_static/tx_multi_condition_multi_fulfillment_v1.png new file mode 100644 index 00000000..985bdae7 Binary files /dev/null and b/docs/source/_static/tx_multi_condition_multi_fulfillment_v1.png differ diff --git a/docs/source/_static/tx_schematics.odg b/docs/source/_static/tx_schematics.odg new file mode 100644 index 00000000..2a1c2fa4 Binary files /dev/null and b/docs/source/_static/tx_schematics.odg differ diff --git a/docs/source/_static/tx_single_condition_single_fulfillment_v1.png b/docs/source/_static/tx_single_condition_single_fulfillment_v1.png new file mode 100644 index 00000000..4af1b22e Binary files /dev/null and b/docs/source/_static/tx_single_condition_single_fulfillment_v1.png differ diff --git a/docs/source/introduction.md b/docs/source/introduction.md index 3c1230ad..bb7f4e70 100644 --- a/docs/source/introduction.md +++ b/docs/source/introduction.md @@ -7,4 +7,4 @@ BigchainDB is a scalable blockchain database. You can read about its motivations 3. Developers of BigchainDB driver software (SDKs used to develop client software). 4. App developers who are developing client apps to talk to one or more live, operational BigchainDB clusters. They would use one of the BigchainDB drivers. -If you're curious about what's in our roadmap, see [the ROADMAP.md file](https://github.com/bigchaindb/bigchaindb/blob/develop/ROADMAP.md) and [the list of open issues](https://github.com/bigchaindb/bigchaindb/issues). If you want to request a feature, file a bug report, or make a pull request, see [the CONTRIBUTING.md file](https://github.com/bigchaindb/bigchaindb/blob/develop/CONTRIBUTING.md). +If you're curious about what's in our roadmap, see [the ROADMAP.md file](https://github.com/bigchaindb/bigchaindb/blob/master/ROADMAP.md) and [the list of open issues](https://github.com/bigchaindb/bigchaindb/issues). If you want to request a feature, file a bug report, or make a pull request, see [the CONTRIBUTING.md file](https://github.com/bigchaindb/bigchaindb/blob/master/CONTRIBUTING.md). diff --git a/docs/source/licenses.md b/docs/source/licenses.md index aa60e2bc..97699c56 100644 --- a/docs/source/licenses.md +++ b/docs/source/licenses.md @@ -1,3 +1,3 @@ # Licenses -Information about how the BigchainDB code and documentation are licensed can be found in [the LICENSES.md file](https://github.com/bigchaindb/bigchaindb/blob/develop/LICENSES.md) (in the root directory of the repository). \ No newline at end of file +Information about how the BigchainDB code and documentation are licensed can be found in [the LICENSES.md file](https://github.com/bigchaindb/bigchaindb/blob/master/LICENSES.md) (in the root directory of the repository). diff --git a/docs/source/python-server-api-examples.md b/docs/source/python-server-api-examples.md index 2b2bfd86..72dc4329 100644 --- a/docs/source/python-server-api-examples.md +++ b/docs/source/python-server-api-examples.md @@ -69,28 +69,73 @@ After a couple of seconds, we can check if the transactions was included in the # retrieve a transaction from the bigchain tx_retrieved = b.get_transaction(tx_signed['id']) - 'id': '6539dded9479c47b3c83385ae569ecaa90bcf387240d1ee2ea3ae0f7986aeddd', - 'transaction': { 'current_owner': 'pvGtcm5dvwWMzCqagki1N6CDKYs2J1cCwTNw8CqJic3Q', - 'data': { 'hash': '872fa6e6f46246cd44afdb2ee9cfae0e72885fb0910e2bcf9a5a2a4eadb417b8', - 'payload': {'msg': 'Hello BigchainDB!'}}, - 'input': None, - 'new_owner': 'ssQnnjketNYmbU3hwgFMEQsc4JVYAmZyWHnHCtFS8aeA', - 'operation': 'CREATE', - 'timestamp': '1455108421.753908'}} +{ + "id": "cdb6331f26ecec0ee7e67e4d5dcd63734e7f75bbd1ebe40699fc6d2960ae4cb2", + "transaction": { + "conditions": [ + { + "cid": 0, + "condition": { + "details": { + "bitmask": 32, + "public_key": "DTJCqP3sNkZcpoSA8bCtGwZ4ASfRLsMFXZDCmMHzCoeJ", + "signature": null, + "type": "fulfillment", + "type_id": 4 + }, + "uri": "cc:1:20:uQjL_E_uT1yUsJpVi1X7x2G7B15urzIlKN5fUufehTM:98" + }, + "new_owners": [ + "DTJCqP3sNkZcpoSA8bCtGwZ4ASfRLsMFXZDCmMHzCoeJ" + ] + } + ], + "data": { + "hash": "872fa6e6f46246cd44afdb2ee9cfae0e72885fb0910e2bcf9a5a2a4eadb417b8", + "payload": { + "msg": "Hello BigchainDB!" + } + }, + "fulfillments": [ + { + "current_owners": [ + "3LQ5dTiddXymDhNzETB1rEkp4mA7fEV1Qeiu5ghHiJm9" + ], + "fid": 0, + "fulfillment": "cf:1:4:ICKvgXHM8K2jNlKRfkwz3cCvH0OiF5A_-riWsQWXffOMQCyqbFgSDfKTaKRQHypHr5z5jsXzCQ4dKgYkmUo55CMxYs3TT2OxGiV0bZ7Tzn1lcLhpyutGZWm8xIyJKJmmSQQ", + "input": null + } + ], + "operation": "CREATE", + "timestamp": "1460450439.267737" + }, + "version": 1 +} + ``` -The new owner of the digital asset is now `ssQnnjketNYmbU3hwgFMEQsc4JVYAmZyWHnHCtFS8aeA`, which is the public key of `testuser1`. +The new owner of the digital asset is now `DTJCqP3sNkZcpoSA8bCtGwZ4ASfRLsMFXZDCmMHzCoeJ`, which is the public key of `testuser1`. + +Note that the current owner with public key `3LQ5dTiddXymDhNzETB1rEkp4mA7fEV1Qeiu5ghHiJm9` refers to one of the federation nodes that actually created the asset and assigned it to `testuser1`. ## Transfer the Digital Asset -Now that `testuser1` has a digital asset assigned to him, he can transfer it to another user. Transfer transactions require an input. The input will be the transaction id of a digital asset that was assigned to `testuser1`, which in our case is `6539dded9479c47b3c83385ae569ecaa90bcf387240d1ee2ea3ae0f7986aeddd`. +Now that `testuser1` has a digital asset assigned to him, he can transfer it to another user. Transfer transactions require an input. The input will be the transaction id of a digital asset that was assigned to `testuser1`, which in our case is `cdb6331f26ecec0ee7e67e4d5dcd63734e7f75bbd1ebe40699fc6d2960ae4cb2`. + +Since a transaction can have multiple outputs with each their own (crypto)condition, each transaction input should also refer to the condition index `cid`. ```python # create a second testuser -testuser2_priv, testuser2_pub = b.generate_keys() +testuser2_priv, testuser2_pub = crypto.generate_key_pair() + +# retrieve the transaction with condition id +tx_retrieved_id = b.get_owned_ids(testuser1_pub).pop() + {'cid': 0, + 'txid': 'cdb6331f26ecec0ee7e67e4d5dcd63734e7f75bbd1ebe40699fc6d2960ae4cb2'} + # create a transfer transaction -tx_transfer = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved['id'], 'TRANSFER') +tx_transfer = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved_id, 'TRANSFER') # sign the transaction tx_transfer_signed = b.sign_transaction(tx_transfer, testuser1_priv) @@ -101,14 +146,47 @@ b.write_transaction(tx_transfer_signed) # check if the transaction is already in the bigchain tx_transfer_retrieved = b.get_transaction(tx_transfer_signed['id']) -{ 'id': '1b78c313257540189f27da480152ed8c0b758569cdadd123d9810c057da408c3', - 'signature': '3045022056166de447001db8ef024cfa1eecdba4306f92688920ac24325729d5a5068d47022100fbd495077cb1040c48bd7dc050b2515b296ca215cb5ce3369f094928e31955f6', - 'transaction': { 'current_owner': 'ssQnnjketNYmbU3hwgFMEQsc4JVYAmZyWHnHCtFS8aeA', - 'data': None, - 'input': '6539dded9479c47b3c83385ae569ecaa90bcf387240d1ee2ea3ae0f7986aeddd', - 'new_owner': 'zVzophT73m4Wvf3f8gFYokddkYe3b9PbaMzobiUK7fmP', - 'operation': 'TRANSFER', - 'timestamp': '1455109497.480323'}} +{ + "id": "86ce10d653c69acf422a6d017a4ccd27168cdcdac99a49e4a38fb5e0d280c579", + "transaction": { + "conditions": [ + { + "cid": 0, + "condition": { + "details": { + "bitmask": 32, + "public_key": "7MUjLUFEu12Hk5jb8BZEFgM5JWgSya47SVbqzDqF6ZFQ", + "signature": null, + "type": "fulfillment", + "type_id": 4 + }, + "uri": "cc:1:20:XmUXkarmpe3n17ITJpi-EFy40qvGZ1C9aWphiiRfjOs:98" + }, + "new_owners": [ + "7MUjLUFEu12Hk5jb8BZEFgM5JWgSya47SVbqzDqF6ZFQ" + ] + } + ], + "data": null, + "fulfillments": [ + { + "current_owners": [ + "DTJCqP3sNkZcpoSA8bCtGwZ4ASfRLsMFXZDCmMHzCoeJ" + ], + "fid": 0, + "fulfillment": "cf:1:4:ILkIy_xP7k9clLCaVYtV-8dhuwdebq8yJSjeX1Ln3oUzQPKxMGutQV0EIRYxg81_Z6gdUHQYHkEyTKxwN7zRFjHNAnIdyU1NxqqohhFQSR-qYho-L-uqPRJcAed-SI7xwAI", + "input": { + "cid": 0, + "txid": "cdb6331f26ecec0ee7e67e4d5dcd63734e7f75bbd1ebe40699fc6d2960ae4cb2" + } + } + ], + "operation": "TRANSFER", + "timestamp": "1460450449.289641" + }, + "version": 1 +} + ``` ## Double Spends @@ -119,12 +197,234 @@ If we try to create another transaction with the same input as before, the trans ```python # create another transfer transaction with the same input -tx_transfer2 = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved['id'], 'TRANSFER') +tx_transfer2 = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved_id, 'TRANSFER') # sign the transaction tx_transfer_signed2 = b.sign_transaction(tx_transfer2, testuser1_priv) # check if the transaction is valid b.validate_transaction(tx_transfer_signed2) -Exception: input `6539dded9479c47b3c83385ae569ecaa90bcf387240d1ee2ea3ae0f7986aeddd` was already spent +DoubleSpend: input `cdb6331f26ecec0ee7e67e4d5dcd63734e7f75bbd1ebe40699fc6d2960ae4cb2` was already spent +``` + +## Crypto-Conditions + +BigchainDB makes use of the crypto-conditions library to both cryptographically lock and unlock transactions. +The locking script is refered to as a `condition` and a corresponding `fulfillment` unlocks the condition of the `input_tx`. + +![BigchainDB transactions connecting fulfillments with conditions](./_static/tx_single_condition_single_fulfillment_v1.png) + + +### Introduction + +Crypto-conditions provide a mechanism to describe a signed message such that multiple actors in a distributed system can all verify the same signed message and agree on whether it matches the description. + +This provides a useful primitive for event-based systems that are distributed on the Internet since we can describe events in a standard deterministic manner (represented by signed messages) and therefore define generic authenticated event handlers. + +Crypto-conditions are part of the Interledger protocol and the full specification can be found [here](https://interledger.org/five-bells-condition/spec.html). + +Implementations of the crypto-conditions are available in [Python](https://github.com/bigchaindb/cryptoconditions) and [JavaScript](https://github.com/interledger/five-bells-condition). + + +### Threshold Conditions + +Threshold conditions introduce multi-signatures, m-of-n signatures or even more complex binary Merkle trees to BigchainDB. + +Setting up a generic threshold condition is a bit more elaborate than regular transaction signing but allow for flexible signing between multiple parties or groups. + +The basic workflow for creating a more complex cryptocondition is the following: + +1. Create a transaction template that include the public key of all (nested) parties as `new_owners` +2. Set up the threshold condition using the [cryptocondition library](https://github.com/bigchaindb/cryptoconditions) +3. Update the condition and hash in the transaction template + +We'll illustrate this by an example: + +```python +import copy +import json + +from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment +from bigchaindb import util, crypto + +# create some new testusers +thresholduser1_priv, thresholduser1_pub = crypto.generate_key_pair() +thresholduser2_priv, thresholduser2_pub = crypto.generate_key_pair() + +# retrieve the last transaction of testuser2 +tx_retrieved_id = b.get_owned_ids(testuser2_pub).pop() + +# create a base template for a 1-input/2-output transaction +threshold_tx = b.create_transaction(testuser2_pub, [thresholduser1_pub, thresholduser2_pub], tx_retrieved_id, 'TRANSFER') + +# create a Threshold Cryptocondition +threshold_condition = ThresholdSha256Fulfillment(threshold=2) +threshold_condition.add_subfulfillment(Ed25519Fulfillment(public_key=thresholduser1_pub)) +threshold_condition.add_subfulfillment(Ed25519Fulfillment(public_key=thresholduser2_pub)) + +# update the condition in the newly created transaction +threshold_tx['transaction']['conditions'][0]['condition'] = { + 'details': json.loads(threshold_condition.serialize_json()), + 'uri': threshold_condition.condition.serialize_uri() +} + +# conditions have been updated, so hash needs updating +threshold_tx['id'] = util.get_hash_data(threshold_tx) + +# sign the transaction +threshold_tx_signed = b.sign_transaction(threshold_tx, testuser2_priv) + +# write the transaction +b.write_transaction(threshold_tx_signed) + +# check if the transaction is already in the bigchain +tx_threshold_retrieved = b.get_transaction(threshold_tx_signed['id']) + +{ + "id": "f0ea4a96afb3b8cafd6336aa3c4b44d1bb0f2b801f61fcb6a44eea4b870ff2e2", + "transaction": { + "conditions": [ + { + "cid": 0, + "condition": { + "details": { + "bitmask": 41, + "subfulfillments": [ + { + "bitmask": 32, + "public_key": "3tuSZ4FitNVWRgK7bGe6pEia7ERmxHmhCxFfFEVbD7g4", + "signature": null, + "type": "fulfillment", + "type_id": 4, + "weight": 1 + }, + { + "bitmask": 32, + "public_key": "8CvrriTsPZULEXTZW2Hnmg7najZsvXzgTi9NKpJaUdHS", + "signature": null, + "type": "fulfillment", + "type_id": 4, + "weight": 1 + } + ], + "threshold": 2, + "type": "fulfillment", + "type_id": 2 + }, + "uri": "cc:1:29:kiQHpdEiRe24L62KzwQgLu7dxCHaLBfEFLr_xzlswT4:208" + }, + "new_owners": [ + "3tuSZ4FitNVWRgK7bGe6pEia7ERmxHmhCxFfFEVbD7g4", + "8CvrriTsPZULEXTZW2Hnmg7najZsvXzgTi9NKpJaUdHS" + ] + } + ], + "data": null, + "fulfillments": [ + { + "current_owners": [ + "7MUjLUFEu12Hk5jb8BZEFgM5JWgSya47SVbqzDqF6ZFQ" + ], + "fid": 0, + "fulfillment": "cf:1:4:IF5lF5Gq5qXt59eyEyaYvhBcuNKrxmdQvWlqYYokX4zrQDSWz8yxBCFaYFKZOLai5ZCoVq28LVoiQ7TL5zkajG-I-BYH2NaKj7CfPBIZHWkMGWfd_UuQWkbhyx07MJ_1Jww", + "input": { + "cid": 0, + "txid": "86ce10d653c69acf422a6d017a4ccd27168cdcdac99a49e4a38fb5e0d280c579" + } + } + ], + "operation": "TRANSFER", + "timestamp": "1460450459.321600" + }, + "version": 1 +} + +``` + +The transaction can now be transfered by fulfilling the threshold condition. + +The fulfillment involves: + +1. Create a transaction template that include the public key of all (nested) parties as `current_owners` +2. Parsing the threshold condition into a fulfillment using the [cryptocondition library](https://github.com/bigchaindb/cryptoconditions) +3. Signing all necessary subfulfillments and updating the fulfillment field in the transaction + + +```python +from cryptoconditions.fulfillment import Fulfillment + +thresholduser3_priv, thresholduser3_pub = crypto.generate_key_pair() + +# retrieve the last transaction of thresholduser1_pub +tx_retrieved_id = b.get_owned_ids(thresholduser1_pub).pop() + +# create a base template for a 2-input/1-output transaction +threshold_tx_transfer = b.create_transaction([thresholduser1_pub, thresholduser2_pub], thresholduser3_pub, tx_retrieved_id, 'TRANSFER') + +# parse the threshold cryptocondition +threshold_fulfillment = Fulfillment.from_json(threshold_tx['transaction']['conditions'][0]['condition']['details']) +subfulfillment1 = threshold_fulfillment.subconditions[0]['body'] +subfulfillment2 = threshold_fulfillment.subconditions[1]['body'] + +# get the fulfillment message to sign +threshold_tx_fulfillment_message = util.get_fulfillment_message(threshold_tx_transfer, + threshold_tx_transfer['transaction']['fulfillments'][0], + serialized=True) + +# sign the subconditions +subfulfillment1.sign(threshold_tx_fulfillment_message, crypto.SigningKey(thresholduser1_priv)) +subfulfillment2.sign(threshold_tx_fulfillment_message, crypto.SigningKey(thresholduser2_priv)) + +# update the fulfillment +threshold_tx_transfer['transaction']['fulfillments'][0]['fulfillment'] = threshold_fulfillment.serialize_uri() + +# optional validation checks +assert threshold_fulfillment.validate(threshold_tx_fulfillment_message) == True +assert b.verify_signature(threshold_tx_transfer) == True +assert b.validate_transaction(threshold_tx_transfer) == True + +b.write_transaction(threshold_tx_transfer) + +{ + "id": "27d1e780526e172fdafb6cfec24b43878b0f8a2c34e962546ba4932ef7662646", + "transaction": { + "conditions": [ + { + "cid": 0, + "condition": { + "details": { + "bitmask": 32, + "public_key": "4SwVNiYRykGw1ixgKH75k97ipCnmm5QpwNwzQdCKLCzH", + "signature": null, + "type": "fulfillment", + "type_id": 4 + }, + "uri": "cc:1:20:MzgxMS8Zt2XZrSA_dFk1d64nwUz16knOeKkxc5LyIv4:98" + }, + "new_owners": [ + "4SwVNiYRykGw1ixgKH75k97ipCnmm5QpwNwzQdCKLCzH" + ] + } + ], + "data": null, + "fulfillments": [ + { + "current_owners": [ + "3tuSZ4FitNVWRgK7bGe6pEia7ERmxHmhCxFfFEVbD7g4", + "8CvrriTsPZULEXTZW2Hnmg7najZsvXzgTi9NKpJaUdHS" + ], + "fid": 0, + "fulfillment": "cf:1:2:AgIBYwQgKwNKM5oJUhL3lUJ3Xj0dzePTH_1BOxcIry5trRxnNXFANabre0P23pzs3liGozZ-cua3zLZuZIc4UA-2Eb_3oi0zFZKHlL6_PrfxpZFp4Mafsl3Iz1yGVz8s-x5jcbahDwABYwQgaxAYvRMOihIk-M4AZYFB2mlf4XjEqhiOaWpqinOYiXFAuQm7AMeXDs4NCeFI4P6YeL3RqNZqyTr9OsNHZ9JgJLZ2ER1nFpwsLhOt4TJZ01Plon7r7xA2GFKFkw511bRWAQA", + "input": { + "cid": 0, + "txid": "f0ea4a96afb3b8cafd6336aa3c4b44d1bb0f2b801f61fcb6a44eea4b870ff2e2" + } + } + ], + "operation": "TRANSFER", + "timestamp": "1460450469.337543" + }, + "version": 1 +} + ``` diff --git a/docs/source/release-notes.md b/docs/source/release-notes.md index 1a80c120..5b4f1e88 100644 --- a/docs/source/release-notes.md +++ b/docs/source/release-notes.md @@ -4,6 +4,6 @@ You can find a list of all BigchainDB releases and release notes on GitHub at: [https://github.com/bigchaindb/bigchaindb/releases](https://github.com/bigchaindb/bigchaindb/releases) -The [CHANGELOG.md file](https://github.com/bigchaindb/bigchaindb/blob/develop/CHANGELOG.md) contains much the same information, but it also has notes about what to expect in the _next_ release. +The [CHANGELOG.md file](https://github.com/bigchaindb/bigchaindb/blob/master/CHANGELOG.md) contains much the same information, but it also has notes about what to expect in the _next_ release. -We also have [a roadmap document in ROADMAP.md](https://github.com/bigchaindb/bigchaindb/blob/develop/ROADMAP.md). +We also have [a roadmap document in ROADMAP.md](https://github.com/bigchaindb/bigchaindb/blob/master/ROADMAP.md). diff --git a/tests/conftest.py b/tests/conftest.py index ff93ab6e..69cf968f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -42,6 +42,7 @@ def ignore_local_config_file(monkeypatch): @pytest.fixture +@pytest.fixture(scope='function', autouse=True) def restore_config(request, node_config): from bigchaindb import config_utils config_utils.dict_config(node_config) diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 3dda8a6d..df2cfa6d 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -1268,9 +1268,9 @@ class TestCryptoconditions(object): def test_override_fulfillment_create(self, b, user_vk): tx = b.create_transaction(b.me, user_vk, None, 'CREATE') original_fulfillment = tx['transaction']['fulfillments'][0] - fulfillment_message = util.get_fulfillment_message(tx, original_fulfillment) + fulfillment_message = util.get_fulfillment_message(tx, original_fulfillment, serialized=True) fulfillment = Ed25519Fulfillment(public_key=b.me) - fulfillment.sign(util.serialize(fulfillment_message), crypto.SigningKey(b.me_private)) + fulfillment.sign(fulfillment_message, crypto.SigningKey(b.me_private)) tx['transaction']['fulfillments'][0]['fulfillment'] = fulfillment.serialize_uri() @@ -1285,9 +1285,9 @@ class TestCryptoconditions(object): tx = b.create_transaction(user_vk, other_vk, prev_tx_id, 'TRANSFER') original_fulfillment = tx['transaction']['fulfillments'][0] - fulfillment_message = util.get_fulfillment_message(tx, original_fulfillment) + fulfillment_message = util.get_fulfillment_message(tx, original_fulfillment, serialized=True) fulfillment = Ed25519Fulfillment(public_key=user_vk) - fulfillment.sign(util.serialize(fulfillment_message), crypto.SigningKey(user_sk)) + fulfillment.sign(fulfillment_message, crypto.SigningKey(user_sk)) tx['transaction']['fulfillments'][0]['fulfillment'] = fulfillment.serialize_uri() @@ -1307,9 +1307,9 @@ class TestCryptoconditions(object): } first_tx_fulfillment = first_tx['transaction']['fulfillments'][0] - first_tx_fulfillment_message = util.get_fulfillment_message(first_tx, first_tx_fulfillment) + first_tx_fulfillment_message = util.get_fulfillment_message(first_tx, first_tx_fulfillment, serialized=True) first_tx_fulfillment = Ed25519Fulfillment(public_key=user_vk) - first_tx_fulfillment.sign(util.serialize(first_tx_fulfillment_message), crypto.SigningKey(user_sk)) + first_tx_fulfillment.sign(first_tx_fulfillment_message, crypto.SigningKey(user_sk)) first_tx['transaction']['fulfillments'][0]['fulfillment'] = first_tx_fulfillment.serialize_uri() assert b.validate_transaction(first_tx) @@ -1326,9 +1326,9 @@ class TestCryptoconditions(object): next_tx = b.create_transaction(other_vk, user_vk, next_input_tx, 'TRANSFER') next_tx_fulfillment = next_tx['transaction']['fulfillments'][0] - next_tx_fulfillment_message = util.get_fulfillment_message(next_tx, next_tx_fulfillment) + next_tx_fulfillment_message = util.get_fulfillment_message(next_tx, next_tx_fulfillment, serialized=True) next_tx_fulfillment = Ed25519Fulfillment(public_key=other_vk) - next_tx_fulfillment.sign(util.serialize(next_tx_fulfillment_message), crypto.SigningKey(other_sk)) + next_tx_fulfillment.sign(next_tx_fulfillment_message, crypto.SigningKey(other_sk)) next_tx['transaction']['fulfillments'][0]['fulfillment'] = next_tx_fulfillment.serialize_uri() assert b.validate_transaction(next_tx) @@ -1350,14 +1350,8 @@ class TestCryptoconditions(object): 'details': json.loads(first_tx_condition.serialize_json()), 'uri': first_tx_condition.condition.serialize_uri() } - # conditions have been updated, so hash needs updating - transaction_data = copy.deepcopy(first_tx) - for fulfillment in transaction_data['transaction']['fulfillments']: - fulfillment['fulfillment'] = None - - calculated_hash = crypto.hash_data(util.serialize(transaction_data['transaction'])) - first_tx['id'] = calculated_hash + first_tx['id'] = util.get_hash_data(first_tx) first_tx_signed = b.sign_transaction(first_tx, user_sk) @@ -1375,13 +1369,13 @@ class TestCryptoconditions(object): next_tx = b.create_transaction([other1_vk, other2_vk], user_vk, next_input_tx, 'TRANSFER') next_tx_fulfillment = next_tx['transaction']['fulfillments'][0] - next_tx_fulfillment_message = util.get_fulfillment_message(next_tx, next_tx_fulfillment) + next_tx_fulfillment_message = util.get_fulfillment_message(next_tx, next_tx_fulfillment, serialized=True) next_tx_fulfillment = ThresholdSha256Fulfillment(threshold=2) next_tx_subfulfillment1 = Ed25519Fulfillment(public_key=other1_vk) - next_tx_subfulfillment1.sign(util.serialize(next_tx_fulfillment_message), crypto.SigningKey(other1_sk)) + next_tx_subfulfillment1.sign(next_tx_fulfillment_message, crypto.SigningKey(other1_sk)) next_tx_fulfillment.add_subfulfillment(next_tx_subfulfillment1) next_tx_subfulfillment2 = Ed25519Fulfillment(public_key=other2_vk) - next_tx_subfulfillment2.sign(util.serialize(next_tx_fulfillment_message), crypto.SigningKey(other2_sk)) + next_tx_subfulfillment2.sign(next_tx_fulfillment_message, crypto.SigningKey(other2_sk)) next_tx_fulfillment.add_subfulfillment(next_tx_subfulfillment2) next_tx['transaction']['fulfillments'][0]['fulfillment'] = next_tx_fulfillment.serialize_uri() @@ -1404,14 +1398,8 @@ class TestCryptoconditions(object): 'details': json.loads(first_tx_condition.serialize_json()), 'uri': first_tx_condition.condition.serialize_uri() } - # conditions have been updated, so hash needs updating - transaction_data = copy.deepcopy(first_tx) - for fulfillment in transaction_data['transaction']['fulfillments']: - fulfillment['fulfillment'] = None - - calculated_hash = crypto.hash_data(util.serialize(transaction_data['transaction'])) - first_tx['id'] = calculated_hash + first_tx['id'] = util.get_hash_data(first_tx) first_tx_signed = b.sign_transaction(first_tx, user_sk) @@ -1429,15 +1417,15 @@ class TestCryptoconditions(object): next_tx = b.create_transaction([other1_vk, other2_vk], user_vk, next_input_tx, 'TRANSFER') next_tx_fulfillment = next_tx['transaction']['fulfillments'][0] - next_tx_fulfillment_message = util.get_fulfillment_message(next_tx, next_tx_fulfillment) + next_tx_fulfillment_message = util.get_fulfillment_message(next_tx, next_tx_fulfillment, serialized=True) next_tx_fulfillment = ThresholdSha256Fulfillment(threshold=2) next_tx_subfulfillment1 = Ed25519Fulfillment(public_key=other1_vk) - next_tx_subfulfillment1.sign(util.serialize(next_tx_fulfillment_message), crypto.SigningKey(other1_sk)) + next_tx_subfulfillment1.sign(next_tx_fulfillment_message, crypto.SigningKey(other1_sk)) next_tx_fulfillment.add_subfulfillment(next_tx_subfulfillment1) # Wrong signing happens here next_tx_subfulfillment2 = Ed25519Fulfillment(public_key=other1_vk) - next_tx_subfulfillment2.sign(util.serialize(next_tx_fulfillment_message), crypto.SigningKey(other1_sk)) + next_tx_subfulfillment2.sign(next_tx_fulfillment_message, crypto.SigningKey(other1_sk)) next_tx_fulfillment.add_subfulfillment(next_tx_subfulfillment2) next_tx['transaction']['fulfillments'][0]['fulfillment'] = next_tx_fulfillment.serialize_uri() diff --git a/tests/doc/__init__.py b/tests/doc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/doc/run_doc_python_server_api_examples.py b/tests/doc/run_doc_python_server_api_examples.py new file mode 100644 index 00000000..9d46e938 --- /dev/null +++ b/tests/doc/run_doc_python_server_api_examples.py @@ -0,0 +1,132 @@ +import copy +import json +from time import sleep + +from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment +from cryptoconditions.fulfillment import Fulfillment + +from bigchaindb import Bigchain, util, crypto, exceptions + + +b = Bigchain() + +# create a test user +testuser1_priv, testuser1_pub = crypto.generate_key_pair() + +# define a digital asset data payload +digital_asset_payload = {'msg': 'Hello BigchainDB!'} + +# a create transaction uses the operation `CREATE` and has no inputs +tx = b.create_transaction(b.me, testuser1_pub, None, 'CREATE', payload=digital_asset_payload) + +# all transactions need to be signed by the user creating the transaction +tx_signed = b.sign_transaction(tx, b.me_private) + +# write the transaction to the bigchain +# the transaction will be stored in a backlog where it will be validated, +# included in a block, and written to the bigchain +b.write_transaction(tx_signed) + +sleep(10) + +tx_retrieved = b.get_transaction(tx_signed['id']) + +# create a second testuser +testuser2_priv, testuser2_pub = crypto.generate_key_pair() + +# retrieve the transaction with condition id +tx_retrieved_id = b.get_owned_ids(testuser1_pub).pop() + +# create a transfer transaction +tx_transfer = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved_id, 'TRANSFER') + +# sign the transaction +tx_transfer_signed = b.sign_transaction(tx_transfer, testuser1_priv) + +# write the transaction +b.write_transaction(tx_transfer_signed) + +sleep(10) + +# check if the transaction is already in the bigchain +tx_transfer_retrieved = b.get_transaction(tx_transfer_signed['id']) + +# create another transfer transaction with the same input +tx_transfer2 = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved_id, 'TRANSFER') + +# sign the transaction +tx_transfer_signed2 = b.sign_transaction(tx_transfer2, testuser1_priv) + +# check if the transaction is valid +try: + b.validate_transaction(tx_transfer_signed2) +except exceptions.DoubleSpend as e: + print(e) + +# create some new testusers +thresholduser1_priv, thresholduser1_pub = crypto.generate_key_pair() +thresholduser2_priv, thresholduser2_pub = crypto.generate_key_pair() + +# retrieve the last transaction of testuser2 +tx_retrieved_id = b.get_owned_ids(testuser2_pub).pop() + +# create a base template for a 1-input/2-output transaction +threshold_tx = b.create_transaction(testuser2_pub, [thresholduser1_pub, thresholduser2_pub], tx_retrieved_id, 'TRANSFER') + +# create a Threshold Cryptocondition +threshold_condition = ThresholdSha256Fulfillment(threshold=2) +threshold_condition.add_subfulfillment(Ed25519Fulfillment(public_key=thresholduser1_pub)) +threshold_condition.add_subfulfillment(Ed25519Fulfillment(public_key=thresholduser2_pub)) + +# update the condition in the newly created transaction +threshold_tx['transaction']['conditions'][0]['condition'] = { + 'details': json.loads(threshold_condition.serialize_json()), + 'uri': threshold_condition.condition.serialize_uri() +} + +# conditions have been updated, so hash needs updating +threshold_tx['id'] = util.get_hash_data(threshold_tx) + +# sign the transaction +threshold_tx_signed = b.sign_transaction(threshold_tx, testuser2_priv) + +# write the transaction +b.write_transaction(threshold_tx_signed) + +sleep(10) + +# check if the transaction is already in the bigchain +tx_threshold_retrieved = b.get_transaction(threshold_tx_signed['id']) + +thresholduser3_priv, thresholduser3_pub = crypto.generate_key_pair() + +# retrieve the last transaction of thresholduser1_pub +tx_retrieved_id = b.get_owned_ids(thresholduser1_pub).pop() + +# create a base template for a 2-input/1-output transaction +threshold_tx_transfer = b.create_transaction([thresholduser1_pub, thresholduser2_pub], thresholduser3_pub, tx_retrieved_id, 'TRANSFER') + +# parse the threshold cryptocondition +threshold_fulfillment = Fulfillment.from_json(threshold_tx['transaction']['conditions'][0]['condition']['details']) +subfulfillment1 = threshold_fulfillment.subconditions[0]['body'] +subfulfillment2 = threshold_fulfillment.subconditions[1]['body'] + +# get the fulfillment message to sign +threshold_tx_fulfillment_message = util.get_fulfillment_message(threshold_tx_transfer, + threshold_tx_transfer['transaction']['fulfillments'][0], + serialized=True) + +# sign the subconditions +subfulfillment1.sign(threshold_tx_fulfillment_message, crypto.SigningKey(thresholduser1_priv)) +subfulfillment2.sign(threshold_tx_fulfillment_message, crypto.SigningKey(thresholduser2_priv)) + +assert threshold_fulfillment.validate(threshold_tx_fulfillment_message) == True + +threshold_tx_transfer['transaction']['fulfillments'][0]['fulfillment'] = threshold_fulfillment.serialize_uri() + +assert b.verify_signature(threshold_tx_transfer) == True + +assert b.validate_transaction(threshold_tx_transfer) == True + +b.write_transaction(threshold_tx_transfer) + diff --git a/tests/test_commands.py b/tests/test_commands.py index 64918cd4..c289f279 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -94,15 +94,18 @@ def test_bigchain_run_start_assume_yes_create_default_config(monkeypatch, mock_p # TODO Please beware, that if debugging, the "-s" switch for pytest will # interfere with capsys. # See related issue: https://github.com/pytest-dev/pytest/issues/128 +@pytest.mark.usefixtures('restore_config') def test_bigchain_show_config(capsys): from bigchaindb import config from bigchaindb.commands.bigchain import run_show_config + args = Namespace(config=None) _, _ = capsys.readouterr() run_show_config(args) - output_config, _ = capsys.readouterr() + output_config = json.loads(capsys.readouterr()[0]) del config['CONFIGURED'] - assert output_config.strip() == json.dumps(config, indent=4, sort_keys=True) + config['keypair']['private'] = 'x' * 45 + assert output_config == config def test_bigchain_run_init_when_db_exists(mock_db_init_with_existing_db):