From 13e98f43163c6f8b98fc1a218271301f329afe31 Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 10 Jun 2016 14:01:53 +0200 Subject: [PATCH 01/14] Update cc API usage --- bigchaindb/util.py | 6 +-- setup.py | 4 +- tests/db/test_bigchain_api.py | 53 +++++++++---------- .../doc/run_doc_python_server_api_examples.py | 14 ++--- 4 files changed, 38 insertions(+), 39 deletions(-) diff --git a/bigchaindb/util.py b/bigchaindb/util.py index 195eb35b..d9ddda48 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -275,7 +275,7 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None): conditions.append({ 'new_owners': new_owners, 'condition': { - 'details': rapidjson.loads(condition.serialize_json()), + 'details': condition.to_dict(), 'uri': condition.condition_uri }, 'cid': fulfillment['fid'] @@ -333,7 +333,7 @@ def sign_tx(transaction, signing_keys): # TODO: avoid instantiation, pass as argument! bigchain = bigchaindb.Bigchain() input_condition = get_input_condition(bigchain, fulfillment) - parsed_fulfillment = cc.Fulfillment.from_json(input_condition['condition']['details']) + parsed_fulfillment = cc.Fulfillment.from_dict(input_condition['condition']['details']) # for the case in which the type of fulfillment is not covered by this method parsed_fulfillment_signed = parsed_fulfillment @@ -520,7 +520,7 @@ def get_input_condition(bigchain, fulfillment): return { 'condition': { - 'details': rapidjson.loads(condition.serialize_json()), + 'details': condition.to_dict(), 'uri': condition.condition_uri } } diff --git a/setup.py b/setup.py index e3290394..6a782c2f 100644 --- a/setup.py +++ b/setup.py @@ -108,7 +108,7 @@ setup( tests_require=tests_require, extras_require={ 'test': tests_require, - 'dev': dev_require + tests_require + docs_require + benchmarks_require, - 'docs': docs_require, + 'dev': dev_require + tests_require + docs_require + benchmarks_require, + 'docs': docs_require, }, ) diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 33643c9b..37246537 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -2,7 +2,6 @@ import copy import multiprocessing as mp import random import time -import json import pytest import rethinkdb as r @@ -1504,9 +1503,9 @@ class TestCryptoconditions(object): tx = b.create_transaction(b.me, user_vk, None, 'CREATE') condition = tx['transaction']['conditions'][0]['condition'] condition_from_uri = cc.Condition.from_uri(condition['uri']) - condition_from_json = cc.Fulfillment.from_json(condition['details']).condition + condition_from_dict = cc.Fulfillment.from_dict(condition['details']).condition - assert condition_from_uri.serialize_uri() == condition_from_json.serialize_uri() + assert condition_from_uri.serialize_uri() == condition_from_dict.serialize_uri() assert condition['details']['public_key'] == user_vk tx_signed = b.sign_transaction(tx, b.me_private) @@ -1528,16 +1527,16 @@ class TestCryptoconditions(object): prev_tx = b.get_transaction(prev_tx_id['txid']) prev_condition = prev_tx['transaction']['conditions'][0]['condition'] prev_condition_from_uri = cc.Condition.from_uri(prev_condition['uri']) - prev_condition_from_json = cc.Fulfillment.from_json(prev_condition['details']).condition + prev_condition_from_dict = cc.Fulfillment.from_dict(prev_condition['details']).condition - assert prev_condition_from_uri.serialize_uri() == prev_condition_from_json.serialize_uri() + assert prev_condition_from_uri.serialize_uri() == prev_condition_from_dict.serialize_uri() assert prev_condition['details']['public_key'] == user_vk condition = tx['transaction']['conditions'][0]['condition'] condition_from_uri = cc.Condition.from_uri(condition['uri']) - condition_from_json = cc.Fulfillment.from_json(condition['details']).condition + condition_from_dict = cc.Fulfillment.from_dict(condition['details']).condition - assert condition_from_uri.serialize_uri() == condition_from_json.serialize_uri() + assert condition_from_uri.serialize_uri() == condition_from_dict.serialize_uri() assert condition['details']['public_key'] == other_vk tx_signed = b.sign_transaction(tx, user_sk) @@ -1554,7 +1553,7 @@ class TestCryptoconditions(object): tx = b.create_transaction(b.me, user_vk, None, 'CREATE') fulfillment = cc.Ed25519Fulfillment(public_key=user_vk) tx['transaction']['conditions'][0]['condition'] = { - 'details': json.loads(fulfillment.serialize_json()), + 'details': fulfillment.to_dict(), 'uri': fulfillment.condition.serialize_uri() } @@ -1577,7 +1576,7 @@ class TestCryptoconditions(object): fulfillment = cc.Ed25519Fulfillment(public_key=other_vk) tx['transaction']['conditions'][0]['condition'] = { - 'details': json.loads(fulfillment.serialize_json()), + 'details': fulfillment.to_dict(), 'uri': fulfillment.condition.serialize_uri() } @@ -1627,7 +1626,7 @@ class TestCryptoconditions(object): first_tx_condition = cc.Ed25519Fulfillment(public_key=other_vk) first_tx['transaction']['conditions'][0]['condition'] = { - 'details': json.loads(first_tx_condition.serialize_json()), + 'details': first_tx_condition.to_dict(), 'uri': first_tx_condition.condition.serialize_uri() } @@ -1674,7 +1673,7 @@ class TestCryptoconditions(object): first_tx_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=other3_vk)) first_tx['transaction']['conditions'][0]['condition'] = { - 'details': json.loads(first_tx_condition.serialize_json()), + 'details': first_tx_condition.to_dict(), 'uri': first_tx_condition.condition.serialize_uri() } # conditions have been updated, so hash needs updating @@ -1713,7 +1712,7 @@ class TestCryptoconditions(object): assert b.is_valid_transaction(next_tx) == next_tx @pytest.mark.usefixtures('inputs') - def test_override_condition_and_fulfillment_transfer_threshold_from_json(self, b, user_vk, user_sk): + def test_override_condition_and_fulfillment_transfer_threshold_from_dict(self, b, user_vk, user_sk): other1_sk, other1_vk = crypto.generate_key_pair() other2_sk, other2_vk = crypto.generate_key_pair() other3_sk, other3_vk = crypto.generate_key_pair() @@ -1727,7 +1726,7 @@ class TestCryptoconditions(object): first_tx_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=other3_vk)) first_tx['transaction']['conditions'][0]['condition'] = { - 'details': json.loads(first_tx_condition.serialize_json()), + 'details': first_tx_condition.to_dict(), 'uri': first_tx_condition.condition.serialize_uri() } # conditions have been updated, so hash needs updating @@ -1752,7 +1751,7 @@ class TestCryptoconditions(object): next_tx_fulfillment_message = util.get_fulfillment_message(next_tx, next_tx_fulfillment, serialized=True) # parse the threshold cryptocondition - next_tx_fulfillment = cc.Fulfillment.from_json(first_tx['transaction']['conditions'][0]['condition']['details']) + next_tx_fulfillment = cc.Fulfillment.from_dict(first_tx['transaction']['conditions'][0]['condition']['details']) subfulfillment1 = next_tx_fulfillment.get_subcondition_from_vk(other1_vk)[0] subfulfillment2 = next_tx_fulfillment.get_subcondition_from_vk(other2_vk)[0] @@ -1783,7 +1782,7 @@ class TestCryptoconditions(object): first_tx_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=other2_vk)) first_tx['transaction']['conditions'][0]['condition'] = { - 'details': json.loads(first_tx_condition.serialize_json()), + 'details': first_tx_condition.to_dict(), 'uri': first_tx_condition.condition.serialize_uri() } # conditions have been updated, so hash needs updating @@ -1835,7 +1834,7 @@ class TestCryptoconditions(object): expected_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=user_vk)) expected_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=user2_vk)) tx_expected_condition = { - 'details': json.loads(expected_condition.serialize_json()), + 'details': expected_condition.to_dict(), 'uri': expected_condition.condition.serialize_uri() } @@ -1857,7 +1856,7 @@ class TestCryptoconditions(object): tx_transfer_signed = b.sign_transaction(tx_transfer, [user_sk, user2_sk]) # expected fulfillment - expected_fulfillment = cc.Fulfillment.from_json( + expected_fulfillment = cc.Fulfillment.from_dict( tx_create['transaction']['conditions'][0]['condition']['details']) subfulfillment1 = expected_fulfillment.subconditions[0]['body'] subfulfillment2 = expected_fulfillment.subconditions[1]['body'] @@ -1881,7 +1880,7 @@ class TestCryptoconditions(object): hashlock_tx['transaction']['conditions'].append({ 'condition': { - 'details': json.loads(first_tx_condition.serialize_json()), + 'details': first_tx_condition.to_dict(), 'uri': first_tx_condition.condition.serialize_uri() }, 'cid': 0, @@ -1912,7 +1911,7 @@ class TestCryptoconditions(object): hashlock_tx['transaction']['conditions'].append({ 'condition': { - 'details': json.loads(first_tx_condition.serialize_json()), + 'details': first_tx_condition.to_dict(), 'uri': first_tx_condition.condition.serialize_uri() }, 'cid': 0, @@ -1943,7 +1942,7 @@ class TestCryptoconditions(object): hashlock_tx['transaction']['conditions'].append({ 'condition': { - 'details': json.loads(first_tx_condition.serialize_json()), + 'details': first_tx_condition.to_dict(), 'uri': first_tx_condition.condition.serialize_uri() }, 'cid': 0, @@ -2013,7 +2012,7 @@ class TestCryptoconditions(object): # create a transaction with multiple new_owners tx = b.create_transaction(b.me, new_owners, None, 'CREATE') - condition = cc.Fulfillment.from_json(tx['transaction']['conditions'][0]['condition']['details']) + condition = cc.Fulfillment.from_dict(tx['transaction']['conditions'][0]['condition']['details']) for new_owner in new_owners: subcondition = condition.get_subcondition_from_vk(new_owner)[0] @@ -2051,7 +2050,7 @@ class TestCryptoconditions(object): # Update the condition in the newly created transaction escrow_tx['transaction']['conditions'][0]['condition'] = { - 'details': json.loads(condition_escrow.serialize_json()), + 'details': condition_escrow.to_dict(), 'uri': condition_escrow.condition.serialize_uri() } @@ -2077,7 +2076,7 @@ class TestCryptoconditions(object): escrow_tx_transfer = b.create_transaction([user_vk, user2_vk], user2_vk, tx_retrieved_id, 'TRANSFER') # Parse the threshold cryptocondition - escrow_fulfillment = cc.Fulfillment.from_json( + escrow_fulfillment = cc.Fulfillment.from_dict( escrow_tx['transaction']['conditions'][0]['condition']['details']) subfulfillment_user = escrow_fulfillment.get_subcondition_from_vk(user_vk)[0] @@ -2118,7 +2117,7 @@ class TestCryptoconditions(object): escrow_tx_abort = b.create_transaction([user_vk, user2_vk], user_vk, tx_retrieved_id, 'TRANSFER') # Parse the threshold cryptocondition - escrow_fulfillment = cc.Fulfillment.from_json( + escrow_fulfillment = cc.Fulfillment.from_dict( escrow_tx['transaction']['conditions'][0]['condition']['details']) subfulfillment_user = escrow_fulfillment.get_subcondition_from_vk(user_vk)[0] @@ -2180,7 +2179,7 @@ class TestCryptoconditions(object): # Update the condition in the newly created transaction escrow_tx['transaction']['conditions'][0]['condition'] = { - 'details': json.loads(condition_escrow.serialize_json()), + 'details': condition_escrow.to_dict(), 'uri': condition_escrow.condition.serialize_uri() } @@ -2206,7 +2205,7 @@ class TestCryptoconditions(object): escrow_tx_transfer = b.create_transaction([user_vk, user2_vk], user2_vk, tx_retrieved_id, 'TRANSFER') # Parse the threshold cryptocondition - escrow_fulfillment = cc.Fulfillment.from_json( + escrow_fulfillment = cc.Fulfillment.from_dict( escrow_tx['transaction']['conditions'][0]['condition']['details']) subfulfillment_user = escrow_fulfillment.get_subcondition_from_vk(user_vk)[0] @@ -2253,7 +2252,7 @@ class TestCryptoconditions(object): escrow_tx_abort = b.create_transaction([user_vk, user2_vk], user_vk, tx_retrieved_id, 'TRANSFER') # Parse the threshold cryptocondition - escrow_fulfillment = cc.Fulfillment.from_json( + escrow_fulfillment = cc.Fulfillment.from_dict( escrow_tx['transaction']['conditions'][0]['condition']['details']) subfulfillment_user = escrow_fulfillment.get_subcondition_from_vk(user_vk)[0] diff --git a/tests/doc/run_doc_python_server_api_examples.py b/tests/doc/run_doc_python_server_api_examples.py index 2e132b04..8c92916a 100644 --- a/tests/doc/run_doc_python_server_api_examples.py +++ b/tests/doc/run_doc_python_server_api_examples.py @@ -182,7 +182,7 @@ threshold_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=threshol # update the condition in the newly created transaction threshold_tx['transaction']['conditions'][0]['condition'] = { - 'details': json.loads(threshold_condition.serialize_json()), + 'details': threshold_condition.to_dict(), 'uri': threshold_condition.condition.serialize_uri() } @@ -212,7 +212,7 @@ threshold_tx_transfer = b.create_transaction([thresholduser1_pub, thresholduser2 thresholduser4_pub, tx_retrieved_id, 'TRANSFER') # parse the threshold cryptocondition -threshold_fulfillment = cc.Fulfillment.from_json(threshold_tx['transaction']['conditions'][0]['condition']['details']) +threshold_fulfillment = cc.Fulfillment.from_dict(threshold_tx['transaction']['conditions'][0]['condition']['details']) subfulfillment1 = threshold_fulfillment.get_subcondition_from_vk(thresholduser1_pub)[0] subfulfillment2 = threshold_fulfillment.get_subcondition_from_vk(thresholduser2_pub)[0] @@ -323,7 +323,7 @@ condition_timeout = cc.TimeoutFulfillment(expire_time=time_expire) # The conditions list is empty, so we need to append a new condition tx_timeout['transaction']['conditions'].append({ 'condition': { - 'details': json.loads(condition_timeout.serialize_json()), + 'details': condition_timeout.to_dict(), 'uri': condition_timeout.condition.serialize_uri() }, 'cid': 0, @@ -347,7 +347,7 @@ tx_timeout_id = {'txid': tx_timeout['id'], 'cid': 0} tx_timeout_transfer = b.create_transaction(None, testuser1_pub, tx_timeout_id, 'TRANSFER') # Parse the threshold cryptocondition -timeout_fulfillment = cc.Fulfillment.from_json( +timeout_fulfillment = cc.Fulfillment.from_dict( tx_timeout['transaction']['conditions'][0]['condition']['details']) tx_timeout_transfer['transaction']['fulfillments'][0]['fulfillment'] = timeout_fulfillment.serialize_uri() @@ -392,7 +392,7 @@ condition_escrow.add_subfulfillment(condition_abort) # Update the condition in the newly created transaction tx_escrow['transaction']['conditions'][0]['condition'] = { - 'details': json.loads(condition_escrow.serialize_json()), + 'details': condition_escrow.to_dict(), 'uri': condition_escrow.condition.serialize_uri() } @@ -417,7 +417,7 @@ tx_escrow_id = {'txid': tx_escrow_signed['id'], 'cid': 0} tx_escrow_execute = b.create_transaction([testuser2_pub, testuser1_pub], testuser1_pub, tx_escrow_id, 'TRANSFER') # Parse the threshold cryptocondition -escrow_fulfillment = cc.Fulfillment.from_json( +escrow_fulfillment = cc.Fulfillment.from_dict( tx_escrow['transaction']['conditions'][0]['condition']['details']) subfulfillment_testuser1 = escrow_fulfillment.get_subcondition_from_vk(testuser1_pub)[0] @@ -453,7 +453,7 @@ tx_escrow_execute['transaction']['fulfillments'][0]['fulfillment'] = escrow_fulf tx_escrow_abort = b.create_transaction([testuser2_pub, testuser1_pub], testuser2_pub, tx_escrow_id, 'TRANSFER') # Parse the threshold cryptocondition -escrow_fulfillment = cc.Fulfillment.from_json( +escrow_fulfillment = cc.Fulfillment.from_dict( tx_escrow['transaction']['conditions'][0]['condition']['details']) subfulfillment_testuser1 = escrow_fulfillment.get_subcondition_from_vk(testuser1_pub)[0] From 449048c769ce1eb878272a8e93051b78c8688d65 Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 10 Jun 2016 16:23:46 +0200 Subject: [PATCH 02/14] Update docs --- .../servers/python-server-api-examples.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/source/servers/python-server-api-examples.md b/docs/source/servers/python-server-api-examples.md index 8aa97ee1..8f47234a 100644 --- a/docs/source/servers/python-server-api-examples.md +++ b/docs/source/servers/python-server-api-examples.md @@ -2,7 +2,7 @@ This section gives an example of using the Python Server API to interact _directly_ with a BigchainDB node running BigchainDB Server. That is, in this example, the Python code and BigchainDB Server run on the same machine. -(One can also interact with a BigchainDB node via other APIs, including the HTTP Client-Server API.) +(One can also interact with a BigchainDB node via other APIs, including the HTTP Client-Server API.) We create a digital asset, sign it, write it to a BigchainDB Server instance, read it, transfer it to a different user, and then attempt to transfer it to another user, resulting in a double-spend error. @@ -559,7 +559,7 @@ threshold_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=threshol # Update the condition in the newly created transaction threshold_tx['transaction']['conditions'][0]['condition'] = { - 'details': json.loads(threshold_condition.serialize_json()), + 'details': threshold_condition.to_dict(), 'uri': threshold_condition.condition.serialize_uri() } @@ -667,7 +667,7 @@ tx_retrieved_id = b.get_owned_ids(thresholduser1_pub).pop() threshold_tx_transfer = b.create_transaction([thresholduser1_pub, thresholduser2_pub, thresholduser3_pub], thresholduser4_pub, tx_retrieved_id, 'TRANSFER') # Parse the threshold cryptocondition -threshold_fulfillment = cc.Fulfillment.from_json(threshold_tx['transaction']['conditions'][0]['condition']['details']) +threshold_fulfillment = cc.Fulfillment.from_dict(threshold_tx['transaction']['conditions'][0]['condition']['details']) subfulfillment1 = threshold_fulfillment.get_subcondition_from_vk(thresholduser1_pub)[0] subfulfillment2 = threshold_fulfillment.get_subcondition_from_vk(thresholduser2_pub)[0] @@ -911,7 +911,7 @@ condition_timeout = cc.TimeoutFulfillment(expire_time=time_expire) # The conditions list is empty, so we need to append a new condition tx_timeout['transaction']['conditions'].append({ 'condition': { - 'details': json.loads(condition_timeout.serialize_json()), + 'details': condition_timeout.to_dict(), 'uri': condition_timeout.condition.serialize_uri() }, 'cid': 0, @@ -977,7 +977,7 @@ from time import sleep tx_timeout_transfer = b.create_transaction(None, testuser1_pub, {'txid': tx_timeout['id'], 'cid': 0}, 'TRANSFER') # Parse the timeout condition and create the corresponding fulfillment -timeout_fulfillment = cc.Fulfillment.from_json( +timeout_fulfillment = cc.Fulfillment.from_dict( tx_timeout['transaction']['conditions'][0]['condition']['details']) tx_timeout_transfer['transaction']['fulfillments'][0]['fulfillment'] = timeout_fulfillment.serialize_uri() @@ -1054,7 +1054,7 @@ tx_retrieved_id = b.get_owned_ids(testuser2_pub).pop() # Create a base template with the execute and abort address tx_escrow = b.create_transaction(testuser2_pub, [testuser2_pub, testuser1_pub], tx_retrieved_id, 'TRANSFER') -# Set expiry time - the execute address needs to fulfill before expiration +# Set expiry time - the execute address needs to fulfill before expiration time_sleep = 12 time_expire = str(float(util.timestamp()) + time_sleep) # 12 secs from now @@ -1073,12 +1073,12 @@ condition_escrow.add_subfulfillment(condition_execute) # Create the abort branch condition_abort = cc.ThresholdSha256Fulfillment(threshold=2) # AND gate condition_abort.add_subfulfillment(cc.Ed25519Fulfillment(public_key=testuser2_pub)) # abort address -condition_abort.add_subfulfillment(condition_timeout_inverted) +condition_abort.add_subfulfillment(condition_timeout_inverted) condition_escrow.add_subfulfillment(condition_abort) # Update the condition in the newly created transaction tx_escrow['transaction']['conditions'][0]['condition'] = { - 'details': json.loads(condition_escrow.serialize_json()), + 'details': condition_escrow.to_dict(), 'uri': condition_escrow.condition.serialize_uri() } @@ -1209,7 +1209,7 @@ In the case of `testuser1`, we create the `execute` fulfillment: tx_escrow_execute = b.create_transaction([testuser2_pub, testuser1_pub], testuser1_pub, {'txid': tx_escrow_signed['id'], 'cid': 0}, 'TRANSFER') # Parse the Escrow cryptocondition -escrow_fulfillment = cc.Fulfillment.from_json( +escrow_fulfillment = cc.Fulfillment.from_dict( tx_escrow['transaction']['conditions'][0]['condition']['details']) subfulfillment_testuser1 = escrow_fulfillment.get_subcondition_from_vk(testuser1_pub)[0] @@ -1250,7 +1250,7 @@ In the case of `testuser2`, we create the `abort` fulfillment: tx_escrow_abort = b.create_transaction([testuser2_pub, testuser1_pub], testuser2_pub, {'txid': tx_escrow_signed['id'], 'cid': 0}, 'TRANSFER') # Parse the threshold cryptocondition -escrow_fulfillment = cc.Fulfillment.from_json( +escrow_fulfillment = cc.Fulfillment.from_dict( tx_escrow['transaction']['conditions'][0]['condition']['details']) subfulfillment_testuser1 = escrow_fulfillment.get_subcondition_from_vk(testuser1_pub)[0] From 6f59867addfcad1478a97cf743635b21721124f8 Mon Sep 17 00:00:00 2001 From: troymc Date: Mon, 13 Jun 2016 11:45:42 +0200 Subject: [PATCH 03/14] Added v0.4.1 commit hash & time to CHANGELOG.md --- CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad931721..a5a72b51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,11 +15,19 @@ For reference, the possible headings are: * **Notes** -## [0.4.1] - 2016-06-13 -Tag name: v0.4.1 +## [Unreleased] - YYYY-MM-DD +Tag nem: = commit: committed: +(Add new stuff here.) + + +## [0.4.1] - 2016-06-13 +Tag name: v0.4.1 += commit: 9c4aa987bcbc294b6a5c3069e6c45a7ed77a4068 +committed: June 13, 2016, 9:52 AM GMT+2 + ### Added - Revert `bigchain` deletes: [Pull Request #330](https://github.com/bigchaindb/bigchaindb/pull/330) From bf51dfd389a9f9271a490b5f02cbbf6c1a2273db Mon Sep 17 00:00:00 2001 From: troymc Date: Mon, 13 Jun 2016 11:48:33 +0200 Subject: [PATCH 04/14] Fixed typo in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5a72b51..c4bd2566 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ For reference, the possible headings are: ## [Unreleased] - YYYY-MM-DD -Tag nem: +Tag name: = commit: committed: From 2245a3f78ab4c20163c711a5d2f53ae88225faa2 Mon Sep 17 00:00:00 2001 From: tim Date: Mon, 13 Jun 2016 16:11:21 +0200 Subject: [PATCH 05/14] Bump cc version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6a782c2f..1b755c08 100644 --- a/setup.py +++ b/setup.py @@ -95,7 +95,7 @@ setup( 'rethinkdb==2.3.0', 'pysha3==0.3', 'pytz==2015.7', - 'cryptoconditions==0.3.1', + 'cryptoconditions==0.4.1', 'statsd==3.2.1', 'python-rapidjson==0.0.6', 'logstats==0.2.1', From 78721a74f0a8b6400a0d779db4ebb28e3721414d Mon Sep 17 00:00:00 2001 From: tim Date: Mon, 13 Jun 2016 16:20:56 +0200 Subject: [PATCH 06/14] Fix: Remove notion of json --- docs/source/servers/python-server-api-examples.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/source/servers/python-server-api-examples.md b/docs/source/servers/python-server-api-examples.md index 8f47234a..3ebc66e7 100644 --- a/docs/source/servers/python-server-api-examples.md +++ b/docs/source/servers/python-server-api-examples.md @@ -535,7 +535,6 @@ We'll illustrate this by a threshold condition where 2 out of 3 `new_owners` nee ```python import copy -import json import cryptoconditions as cc from bigchaindb import util, crypto From a694efd9dc98de6f0cb81a5abe3294bd1ce17972 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 15 Jun 2016 09:55:30 +0200 Subject: [PATCH 07/14] Round timestamp and add uuid to payload. Timestamp is now returned in UTC with second precision. Payload hash is replaced by an uuid4. This allows us to distinguish between duplicated payloads. Removed a deprecated test related to the payload hash. Renamded secondary index payload_hash -> payload_uuid --- bigchaindb/db/utils.py | 2 +- bigchaindb/util.py | 10 ++++------ tests/db/test_bigchain_api.py | 18 ------------------ 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/bigchaindb/db/utils.py b/bigchaindb/db/utils.py index b0a44d6a..13a48a4a 100644 --- a/bigchaindb/db/utils.py +++ b/bigchaindb/db/utils.py @@ -54,7 +54,7 @@ def init(): .run(conn) # secondary index for payload hash r.db(dbname).table('bigchain')\ - .index_create('payload_hash', r.row['block']['transactions']['transaction']['data']['hash'], multi=True)\ + .index_create('payload_uuid', r.row['block']['transactions']['transaction']['data']['uuid'], multi=True)\ .run(conn) # wait for rethinkdb to finish creating secondary indexes diff --git a/bigchaindb/util.py b/bigchaindb/util.py index d9ddda48..3a9d03c2 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -4,7 +4,7 @@ import contextlib import threading import queue import multiprocessing as mp -from datetime import datetime +import uuid import rapidjson @@ -127,14 +127,13 @@ def deserialize(data): def timestamp(): - """Calculate a UTC timestamp with microsecond precision. + """Calculate a UTC timestamp with second precision. Returns: str: UTC timestamp. """ - dt = datetime.utcnow() - return "{0:.6f}".format(time.mktime(dt.timetuple()) + dt.microsecond / 1e6) + return str(round(time.time())) # TODO: Consider remove the operation (if there are no inputs CREATE else TRANSFER) @@ -224,9 +223,8 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None): data = None if payload is not None: if isinstance(payload, dict): - hash_payload = crypto.hash_data(serialize(payload)) data = { - 'hash': hash_payload, + 'uuid': str(uuid.uuid4()), 'payload': payload } else: diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 37246537..d349ca49 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -48,24 +48,6 @@ class TestBigchainApi(object): assert b.validate_fulfillments(tx) == False assert b.validate_fulfillments(tx_signed) == True - def test_transaction_hash(self, b, user_vk): - payload = {'cats': 'are awesome'} - tx = b.create_transaction(user_vk, user_vk, None, 'CREATE', payload) - tx_calculated = { - 'conditions': [{'cid': 0, - 'condition': tx['transaction']['conditions'][0]['condition'], - 'new_owners': [user_vk]}], - 'data': {'hash': crypto.hash_data(util.serialize(payload)), - 'payload': payload}, - 'fulfillments': [{'current_owners': [user_vk], - 'fid': 0, - 'fulfillment': None, - 'input': None}], - 'operation': 'CREATE', - 'timestamp': tx['transaction']['timestamp'] - } - assert tx['transaction']['data'] == tx_calculated['data'] - # assert tx_hash == tx_calculated_hash def test_transaction_signature(self, b, user_sk, user_vk): tx = b.create_transaction(user_vk, user_vk, None, 'CREATE') From 6d41f6971d0305e6cdf220aa29b54c35dee7184f Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 15 Jun 2016 12:07:53 +0200 Subject: [PATCH 08/14] UUID is added even if payload is None Prevent the creation of empty blocks Created and updated tests --- bigchaindb/core.py | 4 +++ bigchaindb/util.py | 15 ++++---- tests/db/test_bigchain_api.py | 66 ++++++++++++++++++++++++++--------- tests/db/test_voter.py | 59 +++++++++++++++++++++---------- 4 files changed, 101 insertions(+), 43 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index e1c91587..40d5aded 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -386,6 +386,10 @@ class Bigchain(object): dict: created block. """ + # Prevent the creation of empty blocks + if len(validated_transactions) == 0: + raise exceptions.OperationError('Empty block creation is not allowed') + # Create the new block block = { 'timestamp': util.timestamp(), diff --git a/bigchaindb/util.py b/bigchaindb/util.py index 3a9d03c2..36ea62e1 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -221,14 +221,13 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None): # handle payload data = None - if payload is not None: - if isinstance(payload, dict): - data = { - 'uuid': str(uuid.uuid4()), - 'payload': payload - } - else: - raise TypeError('`payload` must be an dict instance') + if isinstance(payload, (dict, type(None))): + data = { + 'uuid': str(uuid.uuid4()), + 'payload': payload + } + else: + raise TypeError('`payload` must be an dict instance or None') # handle inputs fulfillments = [] diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index d349ca49..7509fc68 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -22,6 +22,20 @@ def test_remove_unclosed_sockets(): pass +# Some util functions +def dummy_tx(): + b = bigchaindb.Bigchain() + tx = b.create_transaction(b.me, b.me, None, 'CREATE') + tx_signed = b.sign_transaction(tx, b.me_private) + return tx_signed + + +def dummy_block(): + b = bigchaindb.Bigchain() + block = b.create_block([dummy_tx()]) + return block + + class TestBigchainApi(object): def test_create_transaction_create(self, b, user_sk): tx = b.create_transaction(b.me, user_sk, None, 'CREATE') @@ -33,6 +47,17 @@ class TestBigchainApi(object): with pytest.raises(TypeError): b.create_transaction('a', 'b', 'c', 'd', payload=[]) + def test_create_transaction_payload_none(self, b, user_vk): + tx = b.create_transaction(b.me, user_vk, None, 'CREATE') + assert len(tx['transaction']['data']['uuid']) == 36 + assert tx['transaction']['data']['payload'] is None + + def test_create_transaction_payload(self, b, user_vk): + payload = {'msg': 'Hello BigchainDB!'} + tx = b.create_transaction(b.me, user_vk, None, 'CREATE', payload=payload) + assert len(tx['transaction']['data']['uuid']) == 36 + assert tx['transaction']['data']['payload'] == payload + @pytest.mark.usefixtures('inputs') def test_create_transaction_transfer(self, b, user_vk, user_sk): input_tx = b.get_owned_ids(user_vk).pop() @@ -200,7 +225,8 @@ class TestBigchainApi(object): assert prev_block_id == last_block['id'] def test_create_new_block(self, b): - new_block = b.create_block([]) + tx = dummy_tx() + new_block = b.create_block([tx]) block_hash = crypto.hash_data(util.serialize(new_block['block'])) assert new_block['block']['voters'] == [b.me] @@ -209,6 +235,12 @@ class TestBigchainApi(object): assert new_block['id'] == block_hash assert new_block['votes'] == [] + def test_create_empty_block(self, b): + with pytest.raises(exceptions.OperationError) as excinfo: + b.create_block([]) + + assert excinfo.value.args[0] == 'Empty block creation is not allowed' + def test_get_last_voted_block_returns_genesis_if_no_votes_has_been_casted(self, b): b.create_genesis_block() genesis = list(r.table('bigchain') @@ -221,9 +253,9 @@ class TestBigchainApi(object): assert b.get_last_voted_block() == genesis - block_1 = b.create_block([]) - block_2 = b.create_block([]) - block_3 = b.create_block([]) + block_1 = dummy_block() + block_2 = dummy_block() + block_3 = dummy_block() b.write_block(block_1, durability='hard') b.write_block(block_2, durability='hard') @@ -241,7 +273,7 @@ class TestBigchainApi(object): def test_no_vote_written_if_block_already_has_vote(self, b): b.create_genesis_block() - block_1 = b.create_block([]) + block_1 = dummy_block() b.write_block(block_1, durability='hard') @@ -387,7 +419,7 @@ class TestTransactionValidation(object): class TestBlockValidation(object): def test_wrong_block_hash(self, b): - block = b.create_block([]) + block = dummy_block() # change block hash block.update({'id': 'abc'}) @@ -428,7 +460,7 @@ class TestBlockValidation(object): assert excinfo.value.args[0] == 'current_owner `a` does not own the input `{}`'.format(valid_input) def test_invalid_block_id(self, b): - block = b.create_block([]) + block = dummy_block() # change block hash block.update({'id': 'abc'}) @@ -450,7 +482,7 @@ class TestBlockValidation(object): def test_invalid_signature(self, b): # create a valid block - block = b.create_block([]) + block = dummy_block() # replace the block signature with an invalid one block['signature'] = crypto.SigningKey(b.me_private).sign(b'wrongdata') @@ -462,7 +494,7 @@ class TestBlockValidation(object): def test_invalid_node_pubkey(self, b): # blocks can only be created by a federation node # create a valid block - block = b.create_block([]) + block = dummy_block() # create some temp keys tmp_sk, tmp_vk = crypto.generate_key_pair() @@ -488,7 +520,7 @@ class TestBigchainVoter(object): genesis = b.create_genesis_block() # create valid block - block = b.create_block([]) + block = dummy_block() # assert block is valid assert b.is_valid_block(block) b.write_block(block, durability='hard') @@ -560,7 +592,7 @@ class TestBigchainVoter(object): def test_vote_creation_valid(self, b): # create valid block - block = b.create_block([]) + block = dummy_block() # retrieve vote vote = b.vote(block, 'abc', True) @@ -574,7 +606,7 @@ class TestBigchainVoter(object): def test_vote_creation_invalid(self, b): # create valid block - block = b.create_block([]) + block = dummy_block() # retrieve vote vote = b.vote(block, 'abc', False) @@ -786,9 +818,9 @@ class TestBigchainBlock(object): def test_revert_delete_block(self, b): b.create_genesis_block() - block_1 = b.create_block([]) - block_2 = b.create_block([]) - block_3 = b.create_block([]) + block_1 = dummy_block() + block_2 = dummy_block() + block_3 = dummy_block() b.write_block(block_1, durability='hard') b.write_block(block_2, durability='hard') @@ -2088,7 +2120,7 @@ class TestCryptoconditions(object): assert b.is_valid_transaction(escrow_tx_transfer) == escrow_tx_transfer assert b.validate_transaction(escrow_tx_transfer) == escrow_tx_transfer - time.sleep(time_sleep) + time.sleep(time_sleep + 1) assert b.is_valid_transaction(escrow_tx_transfer) is False with pytest.raises(exceptions.InvalidSignature): @@ -2223,7 +2255,7 @@ class TestCryptoconditions(object): block = b.create_block([escrow_tx_transfer]) b.write_block(block, durability='hard') - time.sleep(time_sleep) + time.sleep(time_sleep + 1) assert b.is_valid_transaction(escrow_tx_transfer) is False with pytest.raises(exceptions.InvalidSignature): diff --git a/tests/db/test_voter.py b/tests/db/test_voter.py index 21fc8534..bbc5b873 100644 --- a/tests/db/test_voter.py +++ b/tests/db/test_voter.py @@ -9,6 +9,20 @@ from bigchaindb.voter import Voter, Election, BlockStream from bigchaindb import crypto, Bigchain +# Some util functions +def dummy_tx(): + b = Bigchain() + tx = b.create_transaction(b.me, b.me, None, 'CREATE') + tx_signed = b.sign_transaction(tx, b.me_private) + return tx_signed + + +def dummy_block(): + b = Bigchain() + block = b.create_block([dummy_tx()]) + return block + + class TestBigchainVoter(object): def test_valid_block_voting(self, b): @@ -17,7 +31,9 @@ class TestBigchainVoter(object): genesis = b.create_genesis_block() # create valid block - block = b.create_block([]) + # sleep so that `block` as a higher timestamp then genesis + time.sleep(1) + block = dummy_block() # assert block is valid assert b.is_valid_block(block) b.write_block(block, durability='hard') @@ -59,6 +75,8 @@ class TestBigchainVoter(object): assert b.is_valid_transaction(tx_signed) # create valid block + # sleep so that block as a higher timestamp then genesis + time.sleep(1) block = b.create_block([tx_signed]) # assert block is valid assert b.is_valid_block(block) @@ -172,6 +190,8 @@ class TestBigchainVoter(object): genesis = b.create_genesis_block() # create invalid block + # sleep so that `block` as a higher timestamp then `genesis` + time.sleep(1) block = b.create_block([transaction_signed]) # change transaction id to make it invalid block['block']['transactions'][0]['id'] = 'abc' @@ -201,7 +221,7 @@ class TestBigchainVoter(object): def test_vote_creation_valid(self, b): # create valid block - block = b.create_block([]) + block = dummy_block() # retrieve vote vote = b.vote(block, 'abc', True) @@ -215,7 +235,7 @@ class TestBigchainVoter(object): def test_vote_creation_invalid(self, b): # create valid block - block = b.create_block([]) + block = dummy_block() # retrieve vote vote = b.vote(block, 'abc', False) @@ -233,9 +253,9 @@ class TestBigchainVoter(object): # insert blocks in the database while the voter process is not listening # (these blocks won't appear in the changefeed) - block_1 = b.create_block([]) + block_1 = dummy_block() b.write_block(block_1, durability='hard') - block_2 = b.create_block([]) + block_2 = dummy_block() b.write_block(block_2, durability='hard') # voter is back online, we simulate that by creating a queue and a Voter instance @@ -243,7 +263,7 @@ class TestBigchainVoter(object): voter = Voter(q_new_block) # create a new block that will appear in the changefeed - block_3 = b.create_block([]) + block_3 = dummy_block() b.write_block(block_3, durability='hard') # put the last block in the queue @@ -266,9 +286,12 @@ class TestBigchainVoter(object): def test_voter_chains_blocks_with_the_previous_ones(self, b): b.create_genesis_block() - block_1 = b.create_block([]) + # sleep so that `block_*` as a higher timestamp then `genesis` + time.sleep(1) + block_1 = dummy_block() b.write_block(block_1, durability='hard') - block_2 = b.create_block([]) + time.sleep(1) + block_2 = dummy_block() b.write_block(block_2, durability='hard') q_new_block = mp.Queue() @@ -294,7 +317,7 @@ class TestBigchainVoter(object): def test_voter_checks_for_previous_vote(self, b): b.create_genesis_block() - block_1 = b.create_block([]) + block_1 = dummy_block() b.write_block(block_1, durability='hard') q_new_block = mp.Queue() @@ -326,7 +349,7 @@ class TestBlockElection(object): def test_quorum(self, b): # create a new block - test_block = b.create_block([]) + test_block = dummy_block() # simulate a federation with four voters key_pairs = [crypto.generate_key_pair() for _ in range(4)] @@ -393,7 +416,7 @@ class TestBlockElection(object): def test_quorum_odd(self, b): # test partial quorum situations for odd numbers of voters # create a new block - test_block = b.create_block([]) + test_block = dummy_block() # simulate a federation with four voters key_pairs = [crypto.generate_key_pair() for _ in range(5)] @@ -476,7 +499,7 @@ class TestBlockStream(object): b.federation_nodes.append(crypto.generate_key_pair()[1]) new_blocks = mp.Queue() bs = BlockStream(new_blocks) - block_1 = b.create_block([]) + block_1 = dummy_block() new_blocks.put(block_1) assert block_1 == bs.get() @@ -485,8 +508,8 @@ class TestBlockStream(object): bs = BlockStream(new_blocks) # create two blocks - block_1 = b.create_block([]) - block_2 = b.create_block([]) + block_1 = dummy_block() + block_2 = dummy_block() # write the blocks b.write_block(block_1, durability='hard') @@ -502,8 +525,8 @@ class TestBlockStream(object): def test_if_old_blocks_get_should_return_old_block_first(self, b): # create two blocks - block_1 = b.create_block([]) - block_2 = b.create_block([]) + block_1 = dummy_block() + block_2 = dummy_block() # write the blocks b.write_block(block_1, durability='hard') @@ -520,8 +543,8 @@ class TestBlockStream(object): # pp(block_2) # create two new blocks that will appear in the changefeed - block_3 = b.create_block([]) - block_4 = b.create_block([]) + block_3 = dummy_block() + block_4 = dummy_block() # simulate a changefeed new_blocks.put(block_3) From c56f745ba632800e72c340d6cf292c4af6f007af Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 15 Jun 2016 12:22:19 +0200 Subject: [PATCH 09/14] fixed failing test --- tests/db/test_voter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/db/test_voter.py b/tests/db/test_voter.py index bbc5b873..b3d6cc90 100644 --- a/tests/db/test_voter.py +++ b/tests/db/test_voter.py @@ -526,6 +526,8 @@ class TestBlockStream(object): def test_if_old_blocks_get_should_return_old_block_first(self, b): # create two blocks block_1 = dummy_block() + # sleep so that block_2 as an higher timestamp then block_1 + time.sleep(1) block_2 = dummy_block() # write the blocks From 9e00fbc1b0a5de2ee542502b96e8cfd71778a7af Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 15 Jun 2016 12:32:57 +0200 Subject: [PATCH 10/14] output bigchaindb version when starting bigchaindb --- bigchaindb/commands/bigchain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bigchaindb/commands/bigchain.py b/bigchaindb/commands/bigchain.py index 28831642..48ec6296 100644 --- a/bigchaindb/commands/bigchain.py +++ b/bigchaindb/commands/bigchain.py @@ -170,6 +170,7 @@ def run_start(args): processes = Processes() logger.info('Starting BigchainDB main process') + logger.info('BigchainDB Version {}'.format(bigchaindb.__version__)) processes.start() From 3cefcfa59b3334df82ee50b4071e8c9864d070b9 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 15 Jun 2016 12:36:03 +0200 Subject: [PATCH 11/14] Print the version in the beginning. This way we have information about the version even if something goes wrong during the initialization. --- bigchaindb/commands/bigchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigchaindb/commands/bigchain.py b/bigchaindb/commands/bigchain.py index 48ec6296..8a9c6888 100644 --- a/bigchaindb/commands/bigchain.py +++ b/bigchaindb/commands/bigchain.py @@ -151,6 +151,7 @@ def run_drop(args): def run_start(args): """Start the processes to run the node""" + logger.info('BigchainDB Version {}'.format(bigchaindb.__version__)) bigchaindb.config_utils.autoconfigure(filename=args.config, force=True) if args.start_rethinkdb: @@ -170,7 +171,6 @@ def run_start(args): processes = Processes() logger.info('Starting BigchainDB main process') - logger.info('BigchainDB Version {}'.format(bigchaindb.__version__)) processes.start() From 90dd60ca10dd85852be38caed94cf46c088f6f19 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 15 Jun 2016 13:17:40 +0200 Subject: [PATCH 12/14] updated documentation to reflect changes in the transaction model --- docs/source/topic-guides/models.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/topic-guides/models.md b/docs/source/topic-guides/models.md index a99b2c8a..2072a2a0 100644 --- a/docs/source/topic-guides/models.md +++ b/docs/source/topic-guides/models.md @@ -57,7 +57,7 @@ Also, note that timestamps come from clients and nodes. Unless you have some rea "operation": "", "timestamp": "", "data": { - "hash": "", + "uuid": "", "payload": "" } } @@ -78,7 +78,7 @@ Here's some explanation of the contents of a transaction: - `operation`: String representation of the operation being performed (currently either "CREATE" or "TRANSFER"). It determines how the transaction should be validated. - `timestamp`: Time of creation of the transaction in UTC. It's provided by the client. - `data`: - - `hash`: The hash of the serialized `payload`. + - `uuid`: UUID version 4 (random) converted to a string of hex digits in standard form. - `payload`: Can be any JSON document. It may be empty in the case of a transfer transaction. Later, when we get to the models for the block and the vote, we'll see that both include a signature (from the node which created it). You may wonder why transactions don't have signatures... The answer is that they do! They're just hidden inside the `fulfillment` string of each fulfillment. A creation transaction is signed by the node that created it. A transfer transaction is signed by whoever currently controls or owns it. From 09c5bc6346f216a148b85902c9bee1272b1325db Mon Sep 17 00:00:00 2001 From: troymc Date: Wed, 15 Jun 2016 13:38:07 +0200 Subject: [PATCH 13/14] Updated the changelog for the 0.4.2 release --- CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4bd2566..246e54fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,17 @@ For reference, the possible headings are: * **Notes** -## [Unreleased] - YYYY-MM-DD -Tag name: +## [0.4.2] - 2016-06-15 +Tag name: v0.4.2 = commit: committed: -(Add new stuff here.) +### Added +- Report the BigchainDB version number when starting BigchainDB: [Pull Request #385](https://github.com/bigchaindb/bigchaindb/pull/385) + +### Changed +- Round timestamps to a precision of one second, and replace payload hash with payload UUID in transactions: [Pull Request #384](https://github.com/bigchaindb/bigchaindb/pull/384) +- Updated cryptoconditions API usage: [Pull Request #373](https://github.com/bigchaindb/bigchaindb/pull/373) ## [0.4.1] - 2016-06-13 From a010144552307221ce9e1a83013c5b6f075c8e34 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Wed, 15 Jun 2016 13:44:16 +0200 Subject: [PATCH 14/14] bump version number to 0.4.2 --- bigchaindb/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigchaindb/version.py b/bigchaindb/version.py index ce4b94e3..ceb54ae0 100644 --- a/bigchaindb/version.py +++ b/bigchaindb/version.py @@ -1,2 +1,2 @@ -__version__ = '0.4.1' +__version__ = '0.4.2' __short_version__ = '0.4'