From b3b54e752937343fb7fb1ed625eb8e9dede68d87 Mon Sep 17 00:00:00 2001 From: vrde Date: Mon, 22 Feb 2016 23:46:32 +0100 Subject: [PATCH 01/29] Move common code to util module --- bigchaindb/core.py | 64 +++++++------------------------- bigchaindb/exceptions.py | 4 ++ bigchaindb/util.py | 35 +++++++++++++++++ tests/db/test_bigchain_api.py | 21 ++++++----- tests/db/test_voter.py | 9 +++-- tests/utils/test_config_utils.py | 3 +- 6 files changed, 71 insertions(+), 65 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 76d90a58..b8179a47 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -1,12 +1,11 @@ import rethinkdb as r -import time import random import json import rapidjson -from datetime import datetime import bigchaindb +from bigchaindb import util from bigchaindb import config_utils from bigchaindb import exceptions from bigchaindb.crypto import hash_data, PublicKey, PrivateKey, generate_key_pair @@ -16,10 +15,6 @@ class GenesisBlockAlreadyExistsError(Exception): pass -class KeypairNotFoundException(Exception): - pass - - class Bigchain(object): """Bigchain API @@ -55,7 +50,7 @@ class Bigchain(object): self.federation_nodes = keyring or bigchaindb.config['keyring'] if not self.me or not self.me_private: - raise KeypairNotFoundException() + raise exceptions.KeypairNotFoundException() self._conn = None @@ -100,7 +95,7 @@ class Bigchain(object): data = None if payload is not None: if isinstance(payload, dict): - hash_payload = hash_data(self.serialize(payload)) + hash_payload = hash_data(util.serialize(payload)) data = { 'hash': hash_payload, 'payload': payload @@ -108,7 +103,7 @@ class Bigchain(object): else: raise TypeError('`payload` must be an dict instance') - hash_payload = hash_data(self.serialize(payload)) + hash_payload = hash_data(util.serialize(payload)) data = { 'hash': hash_payload, 'payload': payload @@ -119,12 +114,12 @@ class Bigchain(object): 'new_owner': new_owner, 'input': tx_input, 'operation': operation, - 'timestamp': self.timestamp(), + 'timestamp': util.timestamp(), 'data': data } # serialize and convert to bytes - tx_serialized = self.serialize(tx) + tx_serialized = util.serialize(tx) tx_hash = hash_data(tx_serialized) # create the transaction @@ -149,7 +144,7 @@ class Bigchain(object): """ private_key = PrivateKey(private_key) - signature = private_key.sign(self.serialize(transaction)) + signature = private_key.sign(util.serialize(transaction)) signed_transaction = transaction.copy() signed_transaction.update({'signature': signature}) return signed_transaction @@ -175,7 +170,7 @@ class Bigchain(object): signature = data.pop('signature') public_key_base58 = signed_transaction['transaction']['current_owner'] public_key = PublicKey(public_key_base58) - return public_key.verify(self.serialize(data), signature) + return public_key.verify(util.serialize(data), signature) def write_transaction(self, signed_transaction): """Write the transaction to bigchain. @@ -360,7 +355,7 @@ class Bigchain(object): transaction['transaction']['input'])) # Check hash of the transaction - calculated_hash = hash_data(self.serialize(transaction['transaction'])) + calculated_hash = hash_data(util.serialize(transaction['transaction'])) if calculated_hash != transaction['id']: raise exceptions.InvalidHash() @@ -405,14 +400,14 @@ class Bigchain(object): """ # Create the new block block = { - 'timestamp': self.timestamp(), + 'timestamp': util.timestamp(), 'transactions': validated_transactions, 'node_pubkey': self.me, 'voters': self.federation_nodes + [self.me] } # Calculate the hash of the new block - block_data = self.serialize(block) + block_data = util.serialize(block) block_hash = hash_data(block_data) block_signature = PrivateKey(self.me_private).sign(block_data) @@ -439,7 +434,7 @@ class Bigchain(object): """ # 1. Check if current hash is correct - calculated_hash = hash_data(self.serialize(block['block'])) + calculated_hash = hash_data(util.serialize(block['block'])) if calculated_hash != block['id']: raise exceptions.InvalidHash() @@ -531,10 +526,10 @@ class Bigchain(object): 'previous_block': previous_block_id, 'is_block_valid': decision, 'invalid_reason': invalid_reason, - 'timestamp': self.timestamp() + 'timestamp': util.timestamp() } - vote_data = self.serialize(vote) + vote_data = util.serialize(vote) signature = PrivateKey(self.me_private).sign(vote_data) vote_signed = { @@ -597,26 +592,6 @@ class Bigchain(object): return unvoted - @staticmethod - def serialize(data): - """Static method used to serialize a dict into a JSON formatted string. - - This method enforces rules like the separator and order of keys. This ensures that all dicts - are serialized in the same way. - - This is specially important for hashing data. We need to make sure that everyone serializes their data - in the same way so that we do not have hash mismatches for the same structure due to serialization - differences. - - Args: - data (dict): dict to serialize - - Returns: - str: JSON formatted string - - """ - return json.dumps(data, skipkeys=False, ensure_ascii=False, - separators=(',', ':'), sort_keys=True) @staticmethod def deserialize(data): @@ -631,17 +606,6 @@ class Bigchain(object): """ return json.loads(data, encoding="utf-8") - @staticmethod - def timestamp(): - """Static method to calculate a UTC timestamp with microsecond precision. - - Returns: - str: UTC timestamp. - - """ - dt = datetime.utcnow() - return "{0:.6f}".format(time.mktime(dt.timetuple()) + dt.microsecond / 1e6) - @staticmethod def generate_keys(): """Generates a key pair. diff --git a/bigchaindb/exceptions.py b/bigchaindb/exceptions.py index 684ae58c..0baa4ad2 100644 --- a/bigchaindb/exceptions.py +++ b/bigchaindb/exceptions.py @@ -25,3 +25,7 @@ class DatabaseAlreadyExists(Exception): class DatabaseDoesNotExist(Exception): """Raised when trying to delete the database but the db is not there""" +class KeypairNotFoundException(Exception): + """Raised if operation cannot proceed because the keypair was not given""" + + diff --git a/bigchaindb/util.py b/bigchaindb/util.py index 42589f2a..0714c2f4 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -1,4 +1,7 @@ import multiprocessing as mp +import json +import time +from datetime import datetime class ProcessGroup(object): @@ -22,3 +25,35 @@ class ProcessGroup(object): proc.start() self.processes.append(proc) + +def serialize(data): + """Function used to serialize a dict into a JSON formatted string. + + This function enforces rules like the separator and order of keys. This ensures that all dicts + are serialized in the same way. + + This is specially important for hashing data. We need to make sure that everyone serializes their data + in the same way so that we do not have hash mismatches for the same structure due to serialization + differences. + + Args: + data (dict): dict to serialize + + Returns: + str: JSON formatted string + + """ + return json.dumps(data, skipkeys=False, ensure_ascii=False, + separators=(',', ':'), sort_keys=True) + + +def timestamp(): + """Function to calculate a UTC timestamp with microsecond precision. + + Returns: + str: UTC timestamp. + + """ + dt = datetime.utcnow() + return "{0:.6f}".format(time.mktime(dt.timetuple()) + dt.microsecond / 1e6) + diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index edea3a9e..bd794700 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -6,6 +6,7 @@ import pytest import rethinkdb as r import bigchaindb +from bigchaindb import util from bigchaindb import exceptions from bigchaindb import Bigchain from bigchaindb.crypto import hash_data, PrivateKey, PublicKey, generate_key_pair @@ -69,7 +70,7 @@ class TestBigchainApi(object): 'operation': 'd', 'timestamp': tx['transaction']['timestamp'], 'data': { - 'hash': hash_data(b.serialize(payload)), + 'hash': hash_data(util.serialize(payload)), 'payload': payload } } @@ -86,7 +87,7 @@ class TestBigchainApi(object): def test_serializer(self, b): tx = b.create_transaction('a', 'b', 'c', 'd') - assert b.deserialize(b.serialize(tx)) == tx + assert b.deserialize(util.serialize(tx)) == tx @pytest.mark.usefixtures('inputs') def test_write_transaction(self, b, user_public_key, user_private_key): @@ -114,7 +115,7 @@ class TestBigchainApi(object): b.write_block(block, durability='hard') response = b.get_transaction(tx_signed["id"]) - assert b.serialize(tx_signed) == b.serialize(response) + assert util.serialize(tx_signed) == util.serialize(response) @pytest.mark.usefixtures('inputs') def test_assign_transaction_one_node(self, b, user_public_key, user_private_key): @@ -210,11 +211,11 @@ class TestBigchainApi(object): def test_create_new_block(self, b): new_block = b.create_block([]) - block_hash = hash_data(b.serialize(new_block['block'])) + block_hash = hash_data(util.serialize(new_block['block'])) assert new_block['block']['voters'] == [b.me] assert new_block['block']['node_pubkey'] == b.me - assert PublicKey(b.me).verify(b.serialize(new_block['block']), new_block['signature']) is True + assert PublicKey(b.me).verify(util.serialize(new_block['block']), new_block['signature']) is True assert new_block['id'] == block_hash assert new_block['votes'] == [] @@ -378,7 +379,7 @@ class TestBlockValidation(object): 'voters': b.federation_nodes } - block_data = b.serialize(block) + block_data = util.serialize(block) block_hash = hash_data(block_data) block_signature = PrivateKey(b.me_private).sign(block_data) @@ -491,7 +492,7 @@ class TestBigchainVoter(object): assert vote['vote']['is_block_valid'] is True assert vote['vote']['invalid_reason'] is None assert vote['node_pubkey'] == b.me - assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) is True + assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True def test_invalid_block_voting(self, b, user_public_key): # create queue and voter @@ -533,7 +534,7 @@ class TestBigchainVoter(object): assert vote['vote']['is_block_valid'] is False assert vote['vote']['invalid_reason'] is None assert vote['node_pubkey'] == b.me - assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) is True + assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True def test_vote_creation_valid(self, b): # create valid block @@ -547,7 +548,7 @@ class TestBigchainVoter(object): assert vote['vote']['is_block_valid'] is True assert vote['vote']['invalid_reason'] is None assert vote['node_pubkey'] == b.me - assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) is True + assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True def test_vote_creation_invalid(self, b): # create valid block @@ -561,7 +562,7 @@ class TestBigchainVoter(object): assert vote['vote']['is_block_valid'] is False assert vote['vote']['invalid_reason'] is None assert vote['node_pubkey'] == b.me - assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) is True + assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True class TestBigchainBlock(object): diff --git a/tests/db/test_voter.py b/tests/db/test_voter.py index 550c5da9..3a503aaa 100644 --- a/tests/db/test_voter.py +++ b/tests/db/test_voter.py @@ -4,6 +4,7 @@ import rethinkdb as r import multiprocessing as mp from bigchaindb import Bigchain +from bigchaindb import util from bigchaindb.voter import Voter, BlockStream from bigchaindb.crypto import PublicKey @@ -45,7 +46,7 @@ class TestBigchainVoter(object): assert vote['vote']['is_block_valid'] is True assert vote['vote']['invalid_reason'] is None assert vote['node_pubkey'] == b.me - assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) is True + assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True def test_invalid_block_voting(self, b, user_public_key): @@ -86,7 +87,7 @@ class TestBigchainVoter(object): assert vote['vote']['is_block_valid'] is False assert vote['vote']['invalid_reason'] is None assert vote['node_pubkey'] == b.me - assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) is True + assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True def test_vote_creation_valid(self, b): # create valid block @@ -100,7 +101,7 @@ class TestBigchainVoter(object): assert vote['vote']['is_block_valid'] is True assert vote['vote']['invalid_reason'] is None assert vote['node_pubkey'] == b.me - assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) is True + assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True def test_vote_creation_invalid(self, b): # create valid block @@ -114,7 +115,7 @@ class TestBigchainVoter(object): assert vote['vote']['is_block_valid'] is False assert vote['vote']['invalid_reason'] is None assert vote['node_pubkey'] == b.me - assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) is True + assert PublicKey(b.me).verify(util.serialize(vote['vote']), vote['signature']) is True def test_voter_considers_unvoted_blocks_when_single_node(self, b): # simulate a voter going donw in a single node environment diff --git a/tests/utils/test_config_utils.py b/tests/utils/test_config_utils.py index b95db9aa..9453482f 100644 --- a/tests/utils/test_config_utils.py +++ b/tests/utils/test_config_utils.py @@ -3,6 +3,7 @@ import copy import pytest import bigchaindb +from bigchaindb import exceptions ORIGINAL_CONFIG = copy.deepcopy(bigchaindb.config) @@ -34,5 +35,5 @@ def test_bigchain_instance_raises_when_not_configured(monkeypatch): # from existing configurations monkeypatch.setattr(config_utils, 'autoconfigure', lambda: 0) - with pytest.raises(bigchaindb.core.KeypairNotFoundException): + with pytest.raises(exceptions.KeypairNotFoundException): bigchaindb.Bigchain() From cdf63ef1512e7f4478bec584943a70d4314dc857 Mon Sep 17 00:00:00 2001 From: vrde Date: Tue, 23 Feb 2016 01:59:33 +0100 Subject: [PATCH 02/29] Move other common code to the util module --- bigchaindb/core.py | 79 ++-------------------------------------- bigchaindb/util.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 75 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index b8179a47..c1be8b5b 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -66,88 +66,17 @@ class Bigchain(object): def create_transaction(self, current_owner, new_owner, tx_input, operation, payload=None): """Create a new transaction - A transaction in the bigchain is a transfer of a digital asset between two entities represented - by public keys. - - Currently the bigchain supports two types of operations: - - `CREATE` - Only federation nodes are allowed to use this operation. In a create operation - a federation node creates a digital asset in the bigchain and assigns that asset to a public - key. The owner of the private key can then decided to transfer this digital asset by using the - `transaction id` of the transaction as an input in a `TRANSFER` transaction. - - `TRANSFER` - A transfer operation allows for a transfer of the digital assets between entities. - - Args: - current_owner (str): base58 encoded public key of the current owner of the asset. - new_owner (str): base58 encoded public key of the new owner of the digital asset. - tx_input (str): id of the transaction to use as input. - operation (str): Either `CREATE` or `TRANSFER` operation. - payload (Optional[dict]): dictionary with information about asset. - - Returns: - dict: unsigned transaction. - - - Raises: - TypeError: if the optional ``payload`` argument is not a ``dict``. + Refer to the documentation of ``bigchaindb.util.create_tx`` """ - data = None - if payload is not None: - if isinstance(payload, dict): - hash_payload = hash_data(util.serialize(payload)) - data = { - 'hash': hash_payload, - 'payload': payload - } - else: - raise TypeError('`payload` must be an dict instance') - hash_payload = hash_data(util.serialize(payload)) - data = { - 'hash': hash_payload, - 'payload': payload - } - - tx = { - 'current_owner': current_owner, - 'new_owner': new_owner, - 'input': tx_input, - 'operation': operation, - 'timestamp': util.timestamp(), - 'data': data - } - - # serialize and convert to bytes - tx_serialized = util.serialize(tx) - tx_hash = hash_data(tx_serialized) - - # create the transaction - transaction = { - 'id': tx_hash, - 'transaction': tx - } - - return transaction + return util.create_tx(current_owner, new_owner, tx_input, operation, payload) def sign_transaction(self, transaction, private_key): """Sign a transaction - A transaction signed with the `current_owner` corresponding private key. - - Args: - transaction (dict): transaction to sign. - private_key (str): base58 encoded private key to create a signature of the transaction. - - Returns: - dict: transaction with the `signature` field included. - + Refer to the documentation of ``bigchaindb.util.sign_tx`` """ - private_key = PrivateKey(private_key) - signature = private_key.sign(util.serialize(transaction)) - signed_transaction = transaction.copy() - signed_transaction.update({'signature': signature}) - return signed_transaction + return util.sign_tx(transaction, private_key) def verify_signature(self, signed_transaction): """Verify the signature of a transaction diff --git a/bigchaindb/util.py b/bigchaindb/util.py index 0714c2f4..f49e51ad 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -3,6 +3,8 @@ import json import time from datetime import datetime +from bigchaindb.crypto import hash_data, PrivateKey + class ProcessGroup(object): @@ -57,3 +59,92 @@ def timestamp(): dt = datetime.utcnow() return "{0:.6f}".format(time.mktime(dt.timetuple()) + dt.microsecond / 1e6) + +def create_tx(current_owner, new_owner, tx_input, operation, payload=None): + """Create a new transaction + + A transaction in the bigchain is a transfer of a digital asset between two entities represented + by public keys. + + Currently the bigchain supports two types of operations: + + `CREATE` - Only federation nodes are allowed to use this operation. In a create operation + a federation node creates a digital asset in the bigchain and assigns that asset to a public + key. The owner of the private key can then decided to transfer this digital asset by using the + `transaction id` of the transaction as an input in a `TRANSFER` transaction. + + `TRANSFER` - A transfer operation allows for a transfer of the digital assets between entities. + + Args: + current_owner (str): base58 encoded public key of the current owner of the asset. + new_owner (str): base58 encoded public key of the new owner of the digital asset. + tx_input (str): id of the transaction to use as input. + operation (str): Either `CREATE` or `TRANSFER` operation. + payload (Optional[dict]): dictionary with information about asset. + + Returns: + dict: unsigned transaction. + + + Raises: + TypeError: if the optional ``payload`` argument is not a ``dict``. + """ + + data = None + if payload is not None: + if isinstance(payload, dict): + hash_payload = hash_data(serialize(payload)) + data = { + 'hash': hash_payload, + 'payload': payload + } + else: + raise TypeError('`payload` must be an dict instance') + + hash_payload = hash_data(serialize(payload)) + data = { + 'hash': hash_payload, + 'payload': payload + } + + tx = { + 'current_owner': current_owner, + 'new_owner': new_owner, + 'input': tx_input, + 'operation': operation, + 'timestamp': timestamp(), + 'data': data + } + + # serialize and convert to bytes + tx_serialized = serialize(tx) + tx_hash = hash_data(tx_serialized) + + # create the transaction + transaction = { + 'id': tx_hash, + 'transaction': tx + } + + return transaction + + +def sign_tx(transaction, private_key): + """Sign a transaction + + A transaction signed with the `current_owner` corresponding private key. + + Args: + transaction (dict): transaction to sign. + private_key (str): base58 encoded private key to create a signature of the transaction. + + Returns: + dict: transaction with the `signature` field included. + + """ + private_key = PrivateKey(private_key) + signature = private_key.sign(serialize(transaction)) + signed_transaction = transaction.copy() + signed_transaction.update({'signature': signature}) + return signed_transaction + From 0f3a15ba6e1fae9711ca4736b8056bf55f0f2432 Mon Sep 17 00:00:00 2001 From: vrde Date: Tue, 23 Feb 2016 02:02:41 +0100 Subject: [PATCH 03/29] Fix docstring formatting --- bigchaindb/core.py | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index c1be8b5b..d9f09092 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -76,6 +76,7 @@ class Bigchain(object): Refer to the documentation of ``bigchaindb.util.sign_tx`` """ + return util.sign_tx(transaction, private_key) def verify_signature(self, signed_transaction): @@ -88,8 +89,8 @@ class Bigchain(object): Returns: bool: True if the signature is correct, False otherwise. - """ + data = signed_transaction.copy() # if assignee field in the transaction, remove it @@ -143,8 +144,8 @@ class Bigchain(object): A dict with the transaction details if the transaction was found. If no transaction with that `txid` was found it returns `None` - """ + response = r.table('bigchain').concat_map(lambda doc: doc['block']['transactions'])\ .filter(lambda transaction: transaction['id'] == txid).run(self.conn) @@ -174,8 +175,8 @@ class Bigchain(object): Returns: A list of transactions containing that payload. If no transaction exists with that payload it returns `None` - """ + cursor = r.table('bigchain')\ .get_all(payload_hash, index='payload_hash')\ .run(self.conn) @@ -194,7 +195,6 @@ class Bigchain(object): Returns: The transaction that used the `txid` as an input if it exists else it returns `None` - """ # checks if an input was already spent @@ -221,8 +221,8 @@ class Bigchain(object): Returns: list: list of `txids` currently owned by `owner` - """ + response = r.table('bigchain')\ .concat_map(lambda doc: doc['block']['transactions'])\ .filter({'transaction': {'new_owner': owner}})\ @@ -255,6 +255,7 @@ class Bigchain(object): InvalidHash: if the hash of the transaction is wrong InvalidSignature: if the signature of the transaction is wrong """ + # If the operation is CREATE the transaction should have no inputs and should be signed by a # federation node if transaction['transaction']['operation'] == 'CREATE': @@ -304,8 +305,8 @@ class Bigchain(object): Returns: bool: `True` if the transaction is valid, `False` otherwise - """ + try: self.validate_transaction(transaction) return transaction @@ -325,8 +326,8 @@ class Bigchain(object): Returns: dict: created block. - """ + # Create the new block block = { 'timestamp': util.timestamp(), @@ -359,7 +360,6 @@ class Bigchain(object): Returns: The block if the block is valid else it raises and exception describing the reason why the block is invalid. - """ # 1. Check if current hash is correct @@ -385,8 +385,8 @@ class Bigchain(object): Returns: bool: `True` if the block is valid, `False` otherwise. - """ + try: self.validate_block(block) return True @@ -398,8 +398,8 @@ class Bigchain(object): Args: block (dict): block to write to bigchain. - """ + block_serialized = rapidjson.dumps(block) r.table('bigchain').insert(r.json(block_serialized), durability=durability).run(self.conn) @@ -448,8 +448,8 @@ class Bigchain(object): previous_block_id (str): The id of the previous block. decision (bool): Whether the block is valid or invalid. invalid_reason (Optional[str]): Reason the block is invalid - """ + vote = { 'voting_for_block': block['id'], 'previous_block': previous_block_id, @@ -470,9 +470,8 @@ class Bigchain(object): return vote_signed def write_vote(self, block, vote, block_number): - """ - Write the vote to the database - """ + """Write the vote to the database.""" + update = {'votes': r.row['votes'].append(vote)} # We need to *not* override the existing block_number, if any @@ -486,9 +485,8 @@ class Bigchain(object): .run(self.conn) def get_last_voted_block(self): - """ - Returns the last block that this node voted on - """ + """Returns the last block that this node voted on.""" + # query bigchain for all blocks this node is a voter but didn't voted on last_voted = r.table('bigchain')\ .filter(r.row['block']['voters'].contains(self.me))\ @@ -507,9 +505,7 @@ class Bigchain(object): return last_voted[0] def get_unvoted_blocks(self): - """ - Return all the blocks that has not been voted by this node. - """ + """Return all the blocks that has not been voted by this node.""" unvoted = r.table('bigchain')\ .filter(lambda doc: doc['votes'].contains(lambda vote: vote['node_pubkey'] == self.me).not_())\ @@ -531,8 +527,8 @@ class Bigchain(object): Returns: dict: dict resulting from the serialization of a JSON formatted string. - """ + return json.loads(data, encoding="utf-8") @staticmethod @@ -542,7 +538,7 @@ class Bigchain(object): Returns: tuple: `(private_key, public_key)`. ECDSA key pair using the secp256k1 curve encoded in base58. - """ + # generates and returns the keys serialized in hex return generate_key_pair() From 56357e978db994552d28cc74cb89fa23638674ab Mon Sep 17 00:00:00 2001 From: vrde Date: Tue, 23 Feb 2016 03:37:33 +0100 Subject: [PATCH 04/29] Add simple flask webserver and tests --- bigchaindb/web/__init__.py | 0 bigchaindb/web/server.py | 15 ++++++++++++++ bigchaindb/web/views.py | 13 ++++++++++++ setup.py | 2 ++ tests/db/conftest.py | 22 ++++++++++++++++++++ tests/db/test_bigchain_api.py | 28 +------------------------- tests/web/__init__.py | 0 tests/web/conftest.py | 38 +++++++++++++++++++++++++++++++++++ tests/web/test_basic_views.py | 10 +++++++++ 9 files changed, 101 insertions(+), 27 deletions(-) create mode 100644 bigchaindb/web/__init__.py create mode 100644 bigchaindb/web/server.py create mode 100644 bigchaindb/web/views.py create mode 100644 tests/web/__init__.py create mode 100644 tests/web/conftest.py create mode 100644 tests/web/test_basic_views.py diff --git a/bigchaindb/web/__init__.py b/bigchaindb/web/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bigchaindb/web/server.py b/bigchaindb/web/server.py new file mode 100644 index 00000000..ff184be5 --- /dev/null +++ b/bigchaindb/web/server.py @@ -0,0 +1,15 @@ +from flask import Flask + +from bigchaindb.web import views + + +def create_app(debug=False): + app = Flask(__name__) + app.debug = debug + app.register_blueprint(views.basic_views) + return app + + +if __name__ == '__main__': + create_app().run() + diff --git a/bigchaindb/web/views.py b/bigchaindb/web/views.py new file mode 100644 index 00000000..b106658d --- /dev/null +++ b/bigchaindb/web/views.py @@ -0,0 +1,13 @@ +import flask +from flask import Blueprint + +from bigchaindb import Bigchain + +basic_views = Blueprint('basic_views', __name__) +b = Bigchain() + +@basic_views.route('/tx/') +def show(tx_id): + tx = b.get_transaction(tx_id) + return flask.jsonify(**tx) + diff --git a/setup.py b/setup.py index 3fd8dae4..27ef2e7b 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ tests_require = [ 'pytest', 'pytest-cov', 'pytest-xdist', + 'pytest-flask', ] dev_require = [ @@ -72,6 +73,7 @@ setup( 'logstats==0.2.1', 'base58==0.2.2', 'bitcoin==1.1.42', + 'flask==0.10.1', ], setup_requires=['pytest-runner'], tests_require=tests_require, diff --git a/tests/db/conftest.py b/tests/db/conftest.py index b0545464..ae4abfd7 100644 --- a/tests/db/conftest.py +++ b/tests/db/conftest.py @@ -9,6 +9,7 @@ Tasks: import pytest import rethinkdb as r +import bigchaindb from bigchaindb import Bigchain from bigchaindb.db import get_conn @@ -83,3 +84,24 @@ def cleanup_tables(request, node_config): def b(): return Bigchain() +@pytest.fixture +def inputs(user_public_key, amount=1, b=None): + # 1. create the genesis block + b = b or Bigchain() + try: + b.create_genesis_block() + except bigchaindb.core.GenesisBlockAlreadyExistsError: + pass + + # 2. create block with transactions for `USER` to spend + transactions = [] + for i in range(amount): + tx = b.create_transaction(b.me, user_public_key, None, 'CREATE') + tx_signed = b.sign_transaction(tx, b.me_private) + transactions.append(tx_signed) + b.write_transaction(tx_signed) + + block = b.create_block(transactions) + b.write_block(block, durability='hard') + return block + diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index bd794700..92878810 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -8,37 +8,11 @@ import rethinkdb as r import bigchaindb from bigchaindb import util from bigchaindb import exceptions -from bigchaindb import Bigchain from bigchaindb.crypto import hash_data, PrivateKey, PublicKey, generate_key_pair from bigchaindb.voter import Voter from bigchaindb.block import Block -def create_inputs(user_public_key, amount=1, b=None): - # 1. create the genesis block - b = b or Bigchain() - try: - b.create_genesis_block() - except bigchaindb.core.GenesisBlockAlreadyExistsError: - pass - - # 2. create block with transactions for `USER` to spend - transactions = [] - for i in range(amount): - tx = b.create_transaction(b.me, user_public_key, None, 'CREATE') - tx_signed = b.sign_transaction(tx, b.me_private) - transactions.append(tx_signed) - b.write_transaction(tx_signed) - - block = b.create_block(transactions) - b.write_block(block, durability='hard') - return block - - -@pytest.fixture -def inputs(user_public_key): - return create_inputs(user_public_key) - @pytest.mark.skipif(reason='Some tests throw a ResourceWarning that might result in some weird ' 'exceptions while running the tests. The problem seems to *not* ' @@ -130,11 +104,11 @@ class TestBigchainApi(object): # check if the assignee is the current node assert response['assignee'] == b.me + @pytest.mark.usefixtures('inputs') def test_assign_transaction_multiple_nodes(self, b, user_public_key, user_private_key): # create 5 federation nodes for _ in range(5): b.federation_nodes.append(b.generate_keys()[1]) - create_inputs(user_public_key, amount=20, b=b) # test assignee for several transactions for _ in range(20): diff --git a/tests/web/__init__.py b/tests/web/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/web/conftest.py b/tests/web/conftest.py new file mode 100644 index 00000000..2d7fb98e --- /dev/null +++ b/tests/web/conftest.py @@ -0,0 +1,38 @@ +import pytest +from ..db import conftest + + +@pytest.fixture(autouse=True) +def restore_config(request, node_config): + conftest.restore_config(request, node_config) + + +@pytest.fixture(scope='module', autouse=True) +def setup_database(request, node_config): + conftest.setup_database(request, node_config) + + +@pytest.fixture(scope='function', autouse=True) +def cleanup_tables(request, node_config): + conftest.cleanup_tables(request, node_config) + + +@pytest.fixture +def b(): + return conftest.b() + + +@pytest.fixture +def app(request, node_config): + # XXX: For whatever reason this fixture runs before `restore_config`, + # so we need to manually call it. + conftest.restore_config(request, node_config) + from bigchaindb.web import server + app = server.create_app() + return app + + +@pytest.fixture +def inputs(user_public_key): + conftest.inputs(user_public_key) + diff --git a/tests/web/test_basic_views.py b/tests/web/test_basic_views.py new file mode 100644 index 00000000..bb629cca --- /dev/null +++ b/tests/web/test_basic_views.py @@ -0,0 +1,10 @@ +import pytest + + +@pytest.mark.usefixtures('inputs') +def test_tx_endpoint(b, client, user_public_key): + input_tx = b.get_owned_ids(user_public_key).pop() + tx = b.get_transaction(input_tx) + + res = client.get('/tx/{}'.format(input_tx)) + assert tx == res.json From 2425336dc4fce12968cb0e529f4838b21c7f3c9a Mon Sep 17 00:00:00 2001 From: vrde Date: Tue, 23 Feb 2016 15:33:22 +0100 Subject: [PATCH 05/29] Bind to 0.0.0.0 --- bigchaindb/web/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigchaindb/web/server.py b/bigchaindb/web/server.py index ff184be5..b27307f3 100644 --- a/bigchaindb/web/server.py +++ b/bigchaindb/web/server.py @@ -11,5 +11,5 @@ def create_app(debug=False): if __name__ == '__main__': - create_app().run() + create_app().run(host='0.0.0.0') From 445bacce0db37543fe561ff3dd9ad1ec1a2e90e0 Mon Sep 17 00:00:00 2001 From: vrde Date: Tue, 23 Feb 2016 18:14:01 +0100 Subject: [PATCH 06/29] Move some other code to the util module --- bigchaindb/core.py | 34 +++++++---------------- bigchaindb/crypto.py | 5 ---- bigchaindb/util.py | 51 +++++++++++++++++++++++++++++++++-- tests/db/test_bigchain_api.py | 3 ++- 4 files changed, 61 insertions(+), 32 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index d9f09092..030d831c 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -8,7 +8,7 @@ import bigchaindb from bigchaindb import util from bigchaindb import config_utils from bigchaindb import exceptions -from bigchaindb.crypto import hash_data, PublicKey, PrivateKey, generate_key_pair +from bigchaindb import crypto class GenesisBlockAlreadyExistsError(Exception): @@ -80,15 +80,9 @@ class Bigchain(object): return util.sign_tx(transaction, private_key) def verify_signature(self, signed_transaction): - """Verify the signature of a transaction + """Verify the signature of a transaction. - A valid transaction should have been signed `current_owner` corresponding private key. - - Args: - signed_transaction (dict): a transaction with the `signature` included. - - Returns: - bool: True if the signature is correct, False otherwise. + Refer to the documentation of ``bigchaindb.crypto.verify_signature`` """ data = signed_transaction.copy() @@ -99,7 +93,7 @@ class Bigchain(object): signature = data.pop('signature') public_key_base58 = signed_transaction['transaction']['current_owner'] - public_key = PublicKey(public_key_base58) + public_key = crypto.PublicKey(public_key_base58) return public_key.verify(util.serialize(data), signature) def write_transaction(self, signed_transaction): @@ -284,15 +278,7 @@ class Bigchain(object): raise exceptions.DoubleSpend('input `{}` was already spent'.format( transaction['transaction']['input'])) - # Check hash of the transaction - calculated_hash = hash_data(util.serialize(transaction['transaction'])) - if calculated_hash != transaction['id']: - raise exceptions.InvalidHash() - - # Check signature - if not self.verify_signature(transaction): - raise exceptions.InvalidSignature() - + util.check_hash_and_signature(transaction) return transaction def is_valid_transaction(self, transaction): @@ -338,8 +324,8 @@ class Bigchain(object): # Calculate the hash of the new block block_data = util.serialize(block) - block_hash = hash_data(block_data) - block_signature = PrivateKey(self.me_private).sign(block_data) + block_hash = util.hash_data(block_data) + block_signature = crypto.PrivateKey(self.me_private).sign(block_data) block = { 'id': block_hash, @@ -363,7 +349,7 @@ class Bigchain(object): """ # 1. Check if current hash is correct - calculated_hash = hash_data(util.serialize(block['block'])) + calculated_hash = util.hash_data(util.serialize(block['block'])) if calculated_hash != block['id']: raise exceptions.InvalidHash() @@ -459,7 +445,7 @@ class Bigchain(object): } vote_data = util.serialize(vote) - signature = PrivateKey(self.me_private).sign(vote_data) + signature = crypto.PrivateKey(self.me_private).sign(vote_data) vote_signed = { 'node_pubkey': self.me, @@ -541,4 +527,4 @@ class Bigchain(object): """ # generates and returns the keys serialized in hex - return generate_key_pair() + return crypto.generate_key_pair() diff --git a/bigchaindb/crypto.py b/bigchaindb/crypto.py index 8eedae65..fcec3686 100644 --- a/bigchaindb/crypto.py +++ b/bigchaindb/crypto.py @@ -1,7 +1,5 @@ # Separate all crypto code so that we can easily test several implementations -import hashlib -import sha3 import binascii import base58 import bitcoin @@ -146,6 +144,3 @@ def generate_key_pair(): return (private_value_base58, public_value_compressed_base58) - -def hash_data(data): - return hashlib.sha3_256(data.encode()).hexdigest() diff --git a/bigchaindb/util.py b/bigchaindb/util.py index f49e51ad..e501ed96 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -1,9 +1,12 @@ -import multiprocessing as mp +import sha3 + import json import time +import multiprocessing as mp from datetime import datetime -from bigchaindb.crypto import hash_data, PrivateKey +from bigchaindb import exceptions +from bigchaindb.crypto import PrivateKey, PublicKey class ProcessGroup(object): @@ -148,3 +151,47 @@ def sign_tx(transaction, private_key): signed_transaction.update({'signature': signature}) return signed_transaction + +def create_and_sign_tx(private_key, current_owner, new_owner, tx_input, operation='TRANSFER', payload=None): + tx = create_tx(current_owner, new_owner, tx_input, operation, payload) + return sign_tx(private_key, tx) + + +def hash_data(data): + return sha3.sha3_256(data.encode()).hexdigest() + + +def check_hash_and_signature(transaction): + # Check hash of the transaction + calculated_hash = hash_data(serialize(transaction['transaction'])) + if calculated_hash != transaction['id']: + raise exceptions.InvalidHash() + + # Check signature + if not verify_signature(transaction): + raise exceptions.InvalidSignature() + + +def verify_signature(signed_transaction): + """Verify the signature of a transaction + + A valid transaction should have been signed `current_owner` corresponding private key. + + Args: + signed_transaction (dict): a transaction with the `signature` included. + + Returns: + bool: True if the signature is correct, False otherwise. + """ + + data = signed_transaction.copy() + + # if assignee field in the transaction, remove it + if 'assignee' in data: + data.pop('assignee') + + signature = data.pop('signature') + public_key_base58 = signed_transaction['transaction']['current_owner'] + public_key = PublicKey(public_key_base58) + return public_key.verify(serialize(data), signature) + diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 92878810..10c1a955 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -8,7 +8,8 @@ import rethinkdb as r import bigchaindb from bigchaindb import util from bigchaindb import exceptions -from bigchaindb.crypto import hash_data, PrivateKey, PublicKey, generate_key_pair +from bigchaindb.crypto import PrivateKey, PublicKey, generate_key_pair +from bigchaindb.util import hash_data from bigchaindb.voter import Voter from bigchaindb.block import Block From aeede798460d3a892e38d39f3e4f5d9161927d17 Mon Sep 17 00:00:00 2001 From: vrde Date: Wed, 24 Feb 2016 02:38:30 +0100 Subject: [PATCH 07/29] Add asset create and transfer --- bigchaindb/core.py | 4 ++-- bigchaindb/util.py | 17 ++++++++++++++++- bigchaindb/web/views.py | 23 +++++++++++++++++++++-- tests/conftest.py | 7 +++++++ tests/db/conftest.py | 4 ---- tests/db/test_utils.py | 1 + tests/test_util.py | 12 ++++++++++++ tests/web/test_basic_views.py | 34 +++++++++++++++++++++++++++++++++- 8 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 tests/test_util.py diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 030d831c..61c2ad2f 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -96,7 +96,7 @@ class Bigchain(object): public_key = crypto.PublicKey(public_key_base58) return public_key.verify(util.serialize(data), signature) - def write_transaction(self, signed_transaction): + def write_transaction(self, signed_transaction, durability='soft'): """Write the transaction to bigchain. When first writing a transaction to the bigchain the transaction will be kept in a backlog until @@ -122,7 +122,7 @@ class Bigchain(object): signed_transaction.update({'assignee': assignee}) # write to the backlog - response = r.table('backlog').insert(signed_transaction, durability='soft').run(self.conn) + response = r.table('backlog').insert(signed_transaction, durability=durability).run(self.conn) return response # TODO: the same `txid` can be in two different blocks diff --git a/bigchaindb/util.py b/bigchaindb/util.py index e501ed96..b8c32b74 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -5,6 +5,7 @@ import time import multiprocessing as mp from datetime import datetime +import bigchaindb from bigchaindb import exceptions from bigchaindb.crypto import PrivateKey, PublicKey @@ -154,7 +155,7 @@ def sign_tx(transaction, private_key): def create_and_sign_tx(private_key, current_owner, new_owner, tx_input, operation='TRANSFER', payload=None): tx = create_tx(current_owner, new_owner, tx_input, operation, payload) - return sign_tx(private_key, tx) + return sign_tx(tx, private_key) def hash_data(data): @@ -195,3 +196,17 @@ def verify_signature(signed_transaction): public_key = PublicKey(public_key_base58) return public_key.verify(serialize(data), signature) + +def transform_create(tx): + """Change the owner and signature for a ``CREATE`` transaction created by a node""" + + # XXX: the next instruction opens a new connection to the DB, consider using a singleton or a global + # if you need a Bigchain instance. + b = bigchaindb.Bigchain() + transaction = tx['transaction'] + payload = None + if transaction['data'] and 'payload' in transaction['data']: + payload = transaction['data']['payload'] + new_tx = create_tx(b.me, transaction['current_owner'], None, 'CREATE', payload=payload) + return new_tx + diff --git a/bigchaindb/web/views.py b/bigchaindb/web/views.py index b106658d..9d5afbe7 100644 --- a/bigchaindb/web/views.py +++ b/bigchaindb/web/views.py @@ -1,13 +1,32 @@ import flask -from flask import Blueprint +from flask import request, Blueprint +from bigchaindb import util from bigchaindb import Bigchain basic_views = Blueprint('basic_views', __name__) b = Bigchain() + @basic_views.route('/tx/') -def show(tx_id): +def get_transaction(tx_id): tx = b.get_transaction(tx_id) return flask.jsonify(**tx) + +@basic_views.route('/tx/', methods=['POST']) +def create_transaction(): + val = {} + tx = request.get_json(force=True) + + if tx['operation'] == 'CREATE': + tx = util.transform_create(tx) + tx = util.sign_tx(tx, b.me_private) + + if not util.verify_signature(tx): + val['error'] = 'Invalid transaction signature' + + val = b.write_transaction(tx) + + return flask.jsonify(**tx) + diff --git a/tests/conftest.py b/tests/conftest.py index 2191f73b..96be00f6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,3 +47,10 @@ def user_private_key(): @pytest.fixture def user_public_key(): return USER_PUBLIC_KEY + + +@pytest.fixture +def b(): + from bigchaindb import Bigchain + return Bigchain() + diff --git a/tests/db/conftest.py b/tests/db/conftest.py index ae4abfd7..d79ee846 100644 --- a/tests/db/conftest.py +++ b/tests/db/conftest.py @@ -80,10 +80,6 @@ def cleanup_tables(request, node_config): request.addfinalizer(fin) -@pytest.fixture -def b(): - return Bigchain() - @pytest.fixture def inputs(user_public_key, amount=1, b=None): # 1. create the genesis block diff --git a/tests/db/test_utils.py b/tests/db/test_utils.py index 287fdfcf..9e032b25 100644 --- a/tests/db/test_utils.py +++ b/tests/db/test_utils.py @@ -4,6 +4,7 @@ import pytest import rethinkdb as r import bigchaindb +from bigchaindb import util from bigchaindb.db import utils from .conftest import setup_database as _setup_database diff --git a/tests/test_util.py b/tests/test_util.py new file mode 100644 index 00000000..f4708b59 --- /dev/null +++ b/tests/test_util.py @@ -0,0 +1,12 @@ +from bigchaindb import util + + +def test_transform_create(b, user_private_key, user_public_key): + tx = util.create_tx(user_public_key, user_public_key, None, 'CREATE') + tx = util.transform_create(tx) + tx = util.sign_tx(tx, b.me_private) + + assert tx['transaction']['current_owner'] == b.me + assert tx['transaction']['new_owner'] == user_public_key + assert util.verify_signature(tx) + diff --git a/tests/web/test_basic_views.py b/tests/web/test_basic_views.py index bb629cca..eaa72e85 100644 --- a/tests/web/test_basic_views.py +++ b/tests/web/test_basic_views.py @@ -1,10 +1,42 @@ +import json + import pytest +from bigchaindb import crypto +from bigchaindb import util @pytest.mark.usefixtures('inputs') -def test_tx_endpoint(b, client, user_public_key): +def test_get_transaction_endpoint(b, client, user_public_key): input_tx = b.get_owned_ids(user_public_key).pop() tx = b.get_transaction(input_tx) res = client.get('/tx/{}'.format(input_tx)) assert tx == res.json + + +def test_post_create_transaction_endpoint(b, client): + keypair = crypto.generate_key_pair() + + tx = util.create_and_sign_tx(keypair[0], keypair[1], keypair[1], None, 'CREATE') + + res = client.post('/tx/', data=json.dumps(tx)) + from pprint import pprint as pp + pp(res.body) + assert res.json['transaction']['current_owner'] == b.me + assert res.json['transaction']['new_owner'] == keypair[1] + + +def test_post_transfer_transaction_endpoint(b, client): + from_keypair = crypto.generate_key_pair() + to_keypair = crypto.generate_key_pair() + + tx = util.create_and_sign_tx(from_keypair[0], from_keypair[1], from_keypair[1], None, 'CREATE') + res = client.post('/tx/', data=json.dumps(tx)) + tx_id = res.json['id'] + + transfer = util.create_and_sign_tx(from_keypair[0], from_keypair[1], to_keypair[1], tx_id) + res = client.post('/tx/', data=json.dumps(transfer)) + + assert res.json['transaction']['current_owner'] == from_keypair[1] + assert res.json['transaction']['new_owner'] == to_keypair[1] + From 9383fa84bec16dc466296a16ae854814cb36c7c7 Mon Sep 17 00:00:00 2001 From: vrde Date: Wed, 24 Feb 2016 11:09:59 +0100 Subject: [PATCH 08/29] Remove useless print --- tests/web/test_basic_views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/web/test_basic_views.py b/tests/web/test_basic_views.py index eaa72e85..ff7f5ff3 100644 --- a/tests/web/test_basic_views.py +++ b/tests/web/test_basic_views.py @@ -20,8 +20,6 @@ def test_post_create_transaction_endpoint(b, client): tx = util.create_and_sign_tx(keypair[0], keypair[1], keypair[1], None, 'CREATE') res = client.post('/tx/', data=json.dumps(tx)) - from pprint import pprint as pp - pp(res.body) assert res.json['transaction']['current_owner'] == b.me assert res.json['transaction']['new_owner'] == keypair[1] From e6005b9fe05be5929c655b0297c17bf8e48dadd0 Mon Sep 17 00:00:00 2001 From: vrde Date: Wed, 24 Feb 2016 14:08:01 +0100 Subject: [PATCH 09/29] Add comment --- bigchaindb/web/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bigchaindb/web/views.py b/bigchaindb/web/views.py index 6f827cbb..805c25e7 100644 --- a/bigchaindb/web/views.py +++ b/bigchaindb/web/views.py @@ -17,6 +17,9 @@ def get_transaction(tx_id): @basic_views.route('/tx/', methods=['POST']) def create_transaction(): val = {} + + # `force` will try to format the body of the POST request even if the `content-type` header is not + # set to `application/json` tx = request.get_json(force=True) if tx['transaction']['operation'] == 'CREATE': From 70692a851c3e8ca66273cb2565d653087d27414c Mon Sep 17 00:00:00 2001 From: vrde Date: Fri, 26 Feb 2016 16:23:50 +0100 Subject: [PATCH 10/29] Add client code to create/transfer txs --- bigchaindb/__init__.py | 3 +- bigchaindb/client.py | 113 ++++++++++++++++++++++++++++++++++++++++ bigchaindb/core.py | 2 +- bigchaindb/processes.py | 7 +++ setup.py | 1 + tests/test_client.py | 94 +++++++++++++++++++++++++++++++++ 6 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 bigchaindb/client.py create mode 100644 tests/test_client.py diff --git a/bigchaindb/__init__.py b/bigchaindb/__init__.py index 4bb76c44..4982052f 100644 --- a/bigchaindb/__init__.py +++ b/bigchaindb/__init__.py @@ -39,7 +39,8 @@ config = { 'host': e('BIGCHAIN_STATSD_HOST', default='localhost'), 'port': e('BIGCHAIN_STATSD_PORT', default=8125), 'rate': e('BIGCHAIN_STATSD_SAMPLERATE', default=0.01) - } + }, + 'api_endpoint': 'http://localhost:8008/api/v1' } # We need to maintain a backup copy of the original config dict in case diff --git a/bigchaindb/client.py b/bigchaindb/client.py new file mode 100644 index 00000000..ad2944f2 --- /dev/null +++ b/bigchaindb/client.py @@ -0,0 +1,113 @@ +import requests + +import bigchaindb +from bigchaindb import util +from bigchaindb import config_utils +from bigchaindb import exceptions +from bigchaindb import crypto + + +class Client: + """Client for BigchainDB. + + A Client is initialized with a keypair and is able to create, sign, and submit transactions to a Node + in the Federation. At the moment, a Client instance is bounded to a specific ``host`` in the Federation. + In the future, a Client might connect to >1 hosts. + """ + + def __init__(self, public_key=None, private_key=None, api_endpoint=None): + """Initialize the Client instance + + There are three ways in which the Client instance can get its parameters. + The order by which the parameters are chosen are: + + 1. Setting them by passing them to the `__init__` method itself. + 2. Setting them as environment variables + 3. Reading them from the `config.json` file. + + Args: + public_key (str): the base58 encoded public key for the ECDSA secp256k1 curve. + private_key (str): the base58 encoded private key for the ECDSA secp256k1 curve. + host (str): hostname where the rethinkdb is running. + port (int): port in which rethinkb is running (usually 28015). + """ + + config_utils.autoconfigure() + + self.public_key = public_key or bigchaindb.config['keypair']['public'] + self.private_key = private_key or bigchaindb.config['keypair']['private'] + self.api_endpoint = api_endpoint or bigchaindb.config['api_endpoint'] + + if not self.public_key or not self.private_key: + raise exceptions.KeypairNotFoundException() + + def make_tx(self, new_owner, tx_input, operation='TRANSFER', payload=None): + """Make a new transaction + + Refer to the documentation of ``bigchaindb.util.create_tx`` + """ + + return util.create_tx(self.public_key, new_owner, tx_input, operation, payload) + + def sign_tx(self, tx): + """Sign a transaction + + Refer to the documentation of ``bigchaindb.util.sign_tx`` + """ + + return util.sign_tx(tx, self.private_key) + + def push_tx(self, tx): + """Submit a transaction to the Federation. + + Args: + tx (dict): the transaction to be pushed to the Federation. + + Return: + The transaction pushed to the Federation. + """ + + res = requests.post(self.api_endpoint + '/tx/', json=tx) + return res.json() + + def create(self, payload=None): + """Create a transaction. + + Args: + payload (dict): the payload for the transaction. + + Return: + The transaction pushed to the Federation. + """ + + tx = self.make_tx(self.public_key, None, operation='CREATE', payload=payload) + signed_tx = self.sign_tx(tx) + return self.push_tx(signed_tx) + + def transfer(self, new_owner, tx_input, payload=None): + """Transfer a transaction. + + Args: + new_owner (str): the public key of the new owner + tx_input (str): the id of the transaction to use as input + payload (dict, optional): the payload for the transaction. + + Return: + The transaction pushed to the Federation. + """ + + tx = self.make_tx(new_owner, tx_input, payload=payload) + signed_tx = self.sign_tx(tx) + return self.push_tx(signed_tx) + + +def temp_client(): + """Create a new temporary client. + + Return: + A client initialized with a keypair generated on the fly. + """ + + private_key, public_key = crypto.generate_key_pair() + return Client(private_key=private_key, public_key=public_key, api_endpoint='http://localhost:5000') + diff --git a/bigchaindb/core.py b/bigchaindb/core.py index d69b009c..9122c13f 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -43,8 +43,8 @@ class Bigchain(object): public_key (str): the base58 encoded public key for the ECDSA secp256k1 curve. private_key (str): the base58 encoded private key for the ECDSA secp256k1 curve. keyring (list[str]): list of base58 encoded public keys of the federation nodes. - """ + config_utils.autoconfigure() self.host = host or bigchaindb.config['database']['host'] self.port = port or bigchaindb.config['database']['port'] diff --git a/bigchaindb/processes.py b/bigchaindb/processes.py index 003f23ba..29289f5f 100644 --- a/bigchaindb/processes.py +++ b/bigchaindb/processes.py @@ -6,6 +6,7 @@ import rethinkdb as r from bigchaindb import Bigchain from bigchaindb.voter import Voter from bigchaindb.block import Block +from bigchaindb.web import server logger = logging.getLogger(__name__) @@ -80,3 +81,9 @@ class Processes(object): logger.info('starting voter') p_voter.start() + + # start the web api + webapi = server.create_app() + p_webapi = mp.Process(name='webapi', target=webapi.run, kwargs={'host': '0.0.0.0'}) + p_webapi.start() + diff --git a/setup.py b/setup.py index 27ef2e7b..30a7793c 100644 --- a/setup.py +++ b/setup.py @@ -74,6 +74,7 @@ setup( 'base58==0.2.2', 'bitcoin==1.1.42', 'flask==0.10.1', + 'requests==2.9', ], setup_requires=['pytest-runner'], tests_require=tests_require, diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 00000000..5943bb36 --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,94 @@ +import pytest + + +@pytest.fixture +def client(): + from bigchaindb.client import temp_client + return temp_client() + + +@pytest.fixture +def mock_requests_post(monkeypatch): + class MockResponse: + def __init__(self, json): + self._json = json + + def json(self): + return self._json + + def mockreturn(*args, **kwargs): + return MockResponse(kwargs.get('json')) + + monkeypatch.setattr('requests.post', mockreturn) + + +def test_temp_client_returns_a_temp_client(): + from bigchaindb.client import temp_client + client = temp_client() + assert client.public_key + assert client.private_key + + +def test_client_can_make_transactions(client): + tx = client.make_tx('a', 123) + + assert tx['transaction']['current_owner'] == client.public_key + assert tx['transaction']['new_owner'] == 'a' + assert tx['transaction']['input'] == 123 + + +def test_client_can_sign_transactions(client): + from bigchaindb import util + + tx = client.make_tx('a', 123) + signed_tx = client.sign_tx(tx) + + assert signed_tx['transaction']['current_owner'] == client.public_key + assert signed_tx['transaction']['new_owner'] == 'a' + assert signed_tx['transaction']['input'] == 123 + + assert util.verify_signature(signed_tx) + + +def test_client_can_push_transactions(mock_requests_post, client): + from bigchaindb import util + + tx = client.make_tx('a', 123) + signed_tx = client.sign_tx(tx) + ret_tx = client.push_tx(signed_tx) + + assert ret_tx['transaction']['current_owner'] == client.public_key + assert ret_tx['transaction']['new_owner'] == 'a' + assert ret_tx['transaction']['input'] == 123 + + assert util.verify_signature(ret_tx) + + +def test_client_can_create_transactions_using_shortcut_method(mock_requests_post, client): + from bigchaindb import util + + tx = client.create() + + # XXX: `CREATE` operations require the node that receives the transaction to modify the data in + # the transaction itself. + # `current_owner` will be overwritten with the public key of the node in the federation + # that will create the real transaction. `signature` will be overwritten with the new signature. + # Note that this scenario is ignored by this test. + assert tx['transaction']['current_owner'] == client.public_key + assert tx['transaction']['new_owner'] == client.public_key + assert tx['transaction']['input'] == None + + assert util.verify_signature(tx) + + +def test_client_can_transfer_transactions_using_shortcut_method(mock_requests_post, client): + from bigchaindb import util + + tx = client.transfer('a', 123) + + assert tx['transaction']['current_owner'] == client.public_key + assert tx['transaction']['new_owner'] == 'a' + assert tx['transaction']['input'] == 123 + + assert util.verify_signature(tx) + From 8938811c670f5a9e5c140c9c7bbcaa1ddb2cc175 Mon Sep 17 00:00:00 2001 From: vrde Date: Mon, 29 Feb 2016 18:28:04 +0100 Subject: [PATCH 11/29] Update endpoints names --- bigchaindb/client.py | 2 +- bigchaindb/web/server.py | 2 +- bigchaindb/web/views.py | 4 ++-- tests/web/test_basic_views.py | 11 +++++++---- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/bigchaindb/client.py b/bigchaindb/client.py index ad2944f2..f32f246c 100644 --- a/bigchaindb/client.py +++ b/bigchaindb/client.py @@ -67,7 +67,7 @@ class Client: The transaction pushed to the Federation. """ - res = requests.post(self.api_endpoint + '/tx/', json=tx) + res = requests.post(self.api_endpoint + '/transactions/', json=tx) return res.json() def create(self, payload=None): diff --git a/bigchaindb/web/server.py b/bigchaindb/web/server.py index b27307f3..cec595a6 100644 --- a/bigchaindb/web/server.py +++ b/bigchaindb/web/server.py @@ -6,7 +6,7 @@ from bigchaindb.web import views def create_app(debug=False): app = Flask(__name__) app.debug = debug - app.register_blueprint(views.basic_views) + app.register_blueprint(views.basic_views, url_prefix='/api/v1') return app diff --git a/bigchaindb/web/views.py b/bigchaindb/web/views.py index 805c25e7..b4f12f9d 100644 --- a/bigchaindb/web/views.py +++ b/bigchaindb/web/views.py @@ -8,13 +8,13 @@ basic_views = Blueprint('basic_views', __name__) b = Bigchain() -@basic_views.route('/tx/') +@basic_views.route('/transactions/') def get_transaction(tx_id): tx = b.get_transaction(tx_id) return flask.jsonify(**tx) -@basic_views.route('/tx/', methods=['POST']) +@basic_views.route('/transactions/', methods=['POST']) def create_transaction(): val = {} diff --git a/tests/web/test_basic_views.py b/tests/web/test_basic_views.py index ff7f5ff3..9bea7782 100644 --- a/tests/web/test_basic_views.py +++ b/tests/web/test_basic_views.py @@ -5,12 +5,15 @@ from bigchaindb import crypto from bigchaindb import util +TX_ENDPOINT = '/api/v1/transactions/' + + @pytest.mark.usefixtures('inputs') def test_get_transaction_endpoint(b, client, user_public_key): input_tx = b.get_owned_ids(user_public_key).pop() tx = b.get_transaction(input_tx) - res = client.get('/tx/{}'.format(input_tx)) + res = client.get(TX_ENDPOINT + input_tx) assert tx == res.json @@ -19,7 +22,7 @@ def test_post_create_transaction_endpoint(b, client): tx = util.create_and_sign_tx(keypair[0], keypair[1], keypair[1], None, 'CREATE') - res = client.post('/tx/', data=json.dumps(tx)) + res = client.post(TX_ENDPOINT, data=json.dumps(tx)) assert res.json['transaction']['current_owner'] == b.me assert res.json['transaction']['new_owner'] == keypair[1] @@ -29,11 +32,11 @@ def test_post_transfer_transaction_endpoint(b, client): to_keypair = crypto.generate_key_pair() tx = util.create_and_sign_tx(from_keypair[0], from_keypair[1], from_keypair[1], None, 'CREATE') - res = client.post('/tx/', data=json.dumps(tx)) + res = client.post(TX_ENDPOINT, data=json.dumps(tx)) tx_id = res.json['id'] transfer = util.create_and_sign_tx(from_keypair[0], from_keypair[1], to_keypair[1], tx_id) - res = client.post('/tx/', data=json.dumps(transfer)) + res = client.post(TX_ENDPOINT, data=json.dumps(transfer)) assert res.json['transaction']['current_owner'] == from_keypair[1] assert res.json['transaction']['new_owner'] == to_keypair[1] From 6f689b0ac396c1b6ede16a290fcec74809bf8367 Mon Sep 17 00:00:00 2001 From: vrde Date: Tue, 1 Mar 2016 10:32:31 +0100 Subject: [PATCH 12/29] Update URL in temp_client --- bigchaindb/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigchaindb/client.py b/bigchaindb/client.py index f32f246c..6feca7fc 100644 --- a/bigchaindb/client.py +++ b/bigchaindb/client.py @@ -109,5 +109,5 @@ def temp_client(): """ private_key, public_key = crypto.generate_key_pair() - return Client(private_key=private_key, public_key=public_key, api_endpoint='http://localhost:5000') + return Client(private_key=private_key, public_key=public_key, api_endpoint='http://localhost:5000/api/v1') From 456517443a89f250ae9a1a9aaefcd51088f684c3 Mon Sep 17 00:00:00 2001 From: vrde Date: Tue, 1 Mar 2016 17:55:37 +0100 Subject: [PATCH 13/29] Add more docs --- bigchaindb/web/server.py | 12 ++++++--- bigchaindb/web/views.py | 22 +++++++++++++++++ docs/source/api.md | 53 ++++++++++++++++++++++++++++++++++++++++ docs/source/index.rst | 1 + 4 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 docs/source/api.md diff --git a/bigchaindb/web/server.py b/bigchaindb/web/server.py index cec595a6..76681b2d 100644 --- a/bigchaindb/web/server.py +++ b/bigchaindb/web/server.py @@ -1,15 +1,19 @@ +"""This module contains basic functions to instantiate the BigchainDB API. """ + from flask import Flask from bigchaindb.web import views def create_app(debug=False): + """Return an instance of the Flask application. + + Args: + debug (bool): a flag to activate the debug mode for the app (default: False). + """ + app = Flask(__name__) app.debug = debug app.register_blueprint(views.basic_views, url_prefix='/api/v1') return app - -if __name__ == '__main__': - create_app().run(host='0.0.0.0') - diff --git a/bigchaindb/web/views.py b/bigchaindb/web/views.py index b4f12f9d..ff46e94f 100644 --- a/bigchaindb/web/views.py +++ b/bigchaindb/web/views.py @@ -1,21 +1,43 @@ +"""This module provides the blueprint for some basic API endpoints. + +For more information please refer to the documentation in Apiary: + - http://docs.bigchaindb.apiary.io/ +""" + import flask from flask import request, Blueprint from bigchaindb import util from bigchaindb import Bigchain + basic_views = Blueprint('basic_views', __name__) b = Bigchain() @basic_views.route('/transactions/') def get_transaction(tx_id): + """API endpoint to get details about a transaction. + + Args: + tx_id (str): the id of the transaction. + + Return: + A JSON string containing the data about the transaction. + """ + tx = b.get_transaction(tx_id) return flask.jsonify(**tx) @basic_views.route('/transactions/', methods=['POST']) def create_transaction(): + """API endpoint to push transactions to the Federation. + + Return: + A JSON string containing the data about the transaction. + """ + val = {} # `force` will try to format the body of the POST request even if the `content-type` header is not diff --git a/docs/source/api.md b/docs/source/api.md new file mode 100644 index 00000000..84c64650 --- /dev/null +++ b/docs/source/api.md @@ -0,0 +1,53 @@ +# Getting started with the HTTP API + +The preferred way to communicate with a Node in the BigchainDB Federation is via HTTP requests. +Each Node exposes a simple HTTP API that provides, right now, two endpoints, one to get information about a specific +transaction id, one to push transactions to the BigchainDB network. + +The endpoints are documented in [Apiary](http://docs.bigchaindb.apiary.io/). + + +## Usage example using the Python client + +```python +In [1]: from bigchaindb.client import temp_client +In [2]: c1 = temp_client() +In [3]: c2 = temp_client() +In [4]: tx1 = c1.create() +In [5]: tx1 +Out[5]: +{'assignee': '2Bi5NUv1UL7h3ZGs5AsE6Gr3oPQhE2vGsYCapNYrAU4pr', +'id': '26f21d8b5f9731cef631733b8cd1da05f87aa59eb2f939277a2fefeb774ae133', +'signature': '304402201b904f22e9f5a502070244b64822adf28...', +'transaction': {'current_owner': '2Bi5NUv1UL7h3ZGs5AsE6Gr3oPQhE2vGsYCapNYrAU4pr', + 'data': {'hash': 'efbde2c3aee204a69b7696d4b10ff31137fe78e3946306284f806e2dfc68b805', + 'payload': None}, + 'input': None, + 'new_owner': '247epGEcoX9m6yvR6sEZvYGb1XCpUUWtCNUVKgJGrFWCr', + 'operation': 'CREATE', + 'timestamp': '1456763521.824126'}} +In [7]: c1.transfer(c2.public_key, tx1['id']) +Out[7]: +{'assignee': '2Bi5NUv1UL7h3ZGs5AsE6Gr3oPQhE2vGsYCapNYrAU4pr', +'id': '34b62c9fdfd93f5907f35e2495239ae1cb62e9519ff64a8710f3f77a9f040857', +'signature': '3046022100b2b2432c20310dfcda6a2bab3c893b0cd17e70fe...', +'transaction': {'current_owner': '247epGEcoX9m6yvR6sEZvYGb1XCpUUWtCNUVKgJGrFWCr', + 'data': {'hash': 'efbde2c3aee204a69b7696d4b10ff31137fe78e3946306284f806e2dfc68b805', + 'payload': None}, + 'input': '26f21d8b5f9731cef631733b8cd1da05f87aa59eb2f939277a2fefeb774ae133', + 'new_owner': 'p5Ci1KJkPHvRBnxqyq36m8GXwkWSuhMiZSg8aB1ZrZgJ', + 'operation': 'TRANSFER', + 'timestamp': '1456763549.446138'}} +``` + + +# Roadmap + +The development of the API is still at the beginning and you can follow it on +[GitHub](https://github.com/bigchaindb/bigchaindb/issues?q=is%3Aissue+is%3Aopen+label%3Arest-api) + +There are several key features still missing like: + - validating the structure of the transaction + - returns the correct error codes if something goes wrong + - add an endpoint to query unspents for a given public key + diff --git a/docs/source/index.rst b/docs/source/index.rst index d15ff99b..05ff7d4c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -16,6 +16,7 @@ Table of Contents installing getting-started bigchaindb-cli + api admin cryptography models From 3a714a7f8ee0a9c9f739bf095b0ae24da511657c Mon Sep 17 00:00:00 2001 From: vrde Date: Thu, 3 Mar 2016 01:40:40 +0100 Subject: [PATCH 14/29] Add config to blueprint and fix tests madness There was a problem related to the import of the module `bigchaindb.web.views`. The module, when imported, inizialises a new `Bigchain` instance, and this is wrong for testing and because it's a bad practice. I spent more or less 2h finding out the problem. --- bigchaindb/web/server.py | 2 ++ bigchaindb/web/views.py | 23 +++++++++++++++++------ tests/conftest.py | 8 +++++--- tests/test_commands.py | 2 +- tests/test_core.py | 13 ++++--------- tests/utils/test_config_utils.py | 4 ++-- tests/web/conftest.py | 8 +++++--- tests/web/test_basic_views.py | 1 - 8 files changed, 36 insertions(+), 25 deletions(-) diff --git a/bigchaindb/web/server.py b/bigchaindb/web/server.py index 76681b2d..a062e4ba 100644 --- a/bigchaindb/web/server.py +++ b/bigchaindb/web/server.py @@ -2,6 +2,7 @@ from flask import Flask +from bigchaindb import Bigchain from bigchaindb.web import views @@ -14,6 +15,7 @@ def create_app(debug=False): app = Flask(__name__) app.debug = debug + app.config['bigchain'] = Bigchain() app.register_blueprint(views.basic_views, url_prefix='/api/v1') return app diff --git a/bigchaindb/web/views.py b/bigchaindb/web/views.py index ff46e94f..04d3992d 100644 --- a/bigchaindb/web/views.py +++ b/bigchaindb/web/views.py @@ -5,14 +5,22 @@ For more information please refer to the documentation in Apiary: """ import flask -from flask import request, Blueprint +from flask import current_app, request, Blueprint from bigchaindb import util -from bigchaindb import Bigchain basic_views = Blueprint('basic_views', __name__) -b = Bigchain() + + +@basic_views.record +def get_bigchain(state): + bigchain = state.app.config.get('bigchain') + + if bigchain is None: + raise Exception('This blueprint expects you to provide ' + 'database access through `bigchain`') + @basic_views.route('/transactions/') @@ -26,7 +34,9 @@ def get_transaction(tx_id): A JSON string containing the data about the transaction. """ - tx = b.get_transaction(tx_id) + bigchain = current_app.config['bigchain'] + + tx = bigchain.get_transaction(tx_id) return flask.jsonify(**tx) @@ -37,6 +47,7 @@ def create_transaction(): Return: A JSON string containing the data about the transaction. """ + bigchain = current_app.config['bigchain'] val = {} @@ -46,12 +57,12 @@ def create_transaction(): if tx['transaction']['operation'] == 'CREATE': tx = util.transform_create(tx) - tx = util.sign_tx(tx, b.me_private) + tx = util.sign_tx(tx, bigchain.me_private) if not util.verify_signature(tx): val['error'] = 'Invalid transaction signature' - val = b.write_transaction(tx) + val = bigchain.write_transaction(tx) return flask.jsonify(**tx) diff --git a/tests/conftest.py b/tests/conftest.py index 96be00f6..3781b2b6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,13 +7,14 @@ Tasks: """ import os +import copy import pytest DB_NAME = 'bigchain_test_{}'.format(os.getpid()) -config = { +CONFIG = { 'database': { 'name': DB_NAME }, @@ -36,7 +37,7 @@ def restore_config(request, node_config): @pytest.fixture(scope='module') def node_config(): - return config + return copy.deepcopy(CONFIG) @pytest.fixture @@ -50,7 +51,8 @@ def user_public_key(): @pytest.fixture -def b(): +def b(request, node_config): + restore_config(request, node_config) from bigchaindb import Bigchain return Bigchain() diff --git a/tests/test_commands.py b/tests/test_commands.py index 57ebdbe9..6e731c13 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -87,7 +87,7 @@ def test_bigchain_run_start_assume_yes_create_default_config(monkeypatch, mock_p value['return'] = newconfig monkeypatch.setattr(config_utils, 'write_config', mock_write_config) - monkeypatch.setattr(config_utils, 'file_config', lambda x: config_utils.dict_config(value['return'])) + monkeypatch.setattr(config_utils, 'file_config', lambda x: config_utils.dict_config(expected_config)) monkeypatch.setattr('os.path.exists', lambda path: False) args = Namespace(config=None, yes=True) diff --git a/tests/test_core.py b/tests/test_core.py index 6374b8be..9857b37f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,10 +1,8 @@ -import copy import pytest @pytest.fixture -def config(request): - import bigchaindb +def config(request, monkeypatch): config = { 'database': { 'host': 'host', @@ -18,13 +16,10 @@ def config(request): 'keyring': [], 'CONFIGURED': True, } - bigchaindb.config.update(config) - def fin(): - bigchaindb.config = bigchaindb._config - bigchaindb._config = copy.deepcopy(bigchaindb._config) - request.addfinalizer(fin) - return bigchaindb.config + monkeypatch.setattr('bigchaindb.config', config) + + return config def test_bigchain_class_default_initialization(config): diff --git a/tests/utils/test_config_utils.py b/tests/utils/test_config_utils.py index d57753cf..2cf57242 100644 --- a/tests/utils/test_config_utils.py +++ b/tests/utils/test_config_utils.py @@ -10,8 +10,8 @@ ORIGINAL_CONFIG = copy.deepcopy(bigchaindb._config) @pytest.fixture(scope='function', autouse=True) -def clean_config(): - bigchaindb.config = copy.deepcopy(ORIGINAL_CONFIG) +def clean_config(monkeypatch): + monkeypatch.setattr('bigchaindb.config', copy.deepcopy(ORIGINAL_CONFIG)) def test_bigchain_instance_is_initialized_when_conf_provided(): diff --git a/tests/web/conftest.py b/tests/web/conftest.py index b3e50341..099f2fd3 100644 --- a/tests/web/conftest.py +++ b/tests/web/conftest.py @@ -4,7 +4,8 @@ from ..db import conftest @pytest.fixture(autouse=True) def restore_config(request, node_config): - conftest.restore_config(request, node_config) + from bigchaindb import config_utils + config_utils.dict_config(node_config) @pytest.fixture(scope='module', autouse=True) @@ -21,9 +22,10 @@ def cleanup_tables(request, node_config): def app(request, node_config): # XXX: For whatever reason this fixture runs before `restore_config`, # so we need to manually call it. - conftest.restore_config(request, node_config) + restore_config(request, node_config) + from bigchaindb.web import server - app = server.create_app() + app = server.create_app(debug=True) return app diff --git a/tests/web/test_basic_views.py b/tests/web/test_basic_views.py index 9bea7782..04a1c292 100644 --- a/tests/web/test_basic_views.py +++ b/tests/web/test_basic_views.py @@ -12,7 +12,6 @@ TX_ENDPOINT = '/api/v1/transactions/' def test_get_transaction_endpoint(b, client, user_public_key): input_tx = b.get_owned_ids(user_public_key).pop() tx = b.get_transaction(input_tx) - res = client.get(TX_ENDPOINT + input_tx) assert tx == res.json From fc33cf850a30a9f21e4fce888477c78a5fb70fda Mon Sep 17 00:00:00 2001 From: vrde Date: Thu, 3 Mar 2016 01:52:38 +0100 Subject: [PATCH 15/29] Update skipped test --- tests/db/test_bigchain_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index dbfb19fa..bb20e25a 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -365,7 +365,7 @@ class TestBlockValidation(object): # create a block with invalid transactions block = { - 'timestamp': b.timestamp(), + 'timestamp': util.timestamp(), 'transactions': [tx_invalid], 'node_pubkey': b.me, 'voters': b.federation_nodes From 0d30747ea749c2c41ec7e98b6aad64603663012e Mon Sep 17 00:00:00 2001 From: vrde Date: Thu, 3 Mar 2016 01:54:12 +0100 Subject: [PATCH 16/29] Bind to localhost --- bigchaindb/processes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigchaindb/processes.py b/bigchaindb/processes.py index 29289f5f..c7cf09f0 100644 --- a/bigchaindb/processes.py +++ b/bigchaindb/processes.py @@ -84,6 +84,6 @@ class Processes(object): # start the web api webapi = server.create_app() - p_webapi = mp.Process(name='webapi', target=webapi.run, kwargs={'host': '0.0.0.0'}) + p_webapi = mp.Process(name='webapi', target=webapi.run, kwargs={'host': 'localhost'}) p_webapi.start() From 2bafabdfe21fa25ce4ab8f61d6dae18884530776 Mon Sep 17 00:00:00 2001 From: vrde Date: Thu, 3 Mar 2016 01:55:33 +0100 Subject: [PATCH 17/29] Rename api doc --- docs/source/index.rst | 2 +- docs/source/{api.md => python-api-tutorial.md} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/source/{api.md => python-api-tutorial.md} (100%) diff --git a/docs/source/index.rst b/docs/source/index.rst index 05ff7d4c..6934f335 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -16,7 +16,7 @@ Table of Contents installing getting-started bigchaindb-cli - api + python-api-tutorial admin cryptography models diff --git a/docs/source/api.md b/docs/source/python-api-tutorial.md similarity index 100% rename from docs/source/api.md rename to docs/source/python-api-tutorial.md From 1475a3cbb1d63797e4f59e8f4f28fbf74346b5cb Mon Sep 17 00:00:00 2001 From: vrde Date: Thu, 3 Mar 2016 02:02:17 +0100 Subject: [PATCH 18/29] Move hash_data to the crypto module --- bigchaindb/core.py | 4 ++-- bigchaindb/crypto.py | 9 +++++++++ bigchaindb/util.py | 7 +------ tests/db/test_bigchain_api.py | 3 +-- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 9122c13f..54f8f212 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -331,7 +331,7 @@ class Bigchain(object): # Calculate the hash of the new block block_data = util.serialize(block) - block_hash = util.hash_data(block_data) + block_hash = crypto.hash_data(block_data) block_signature = crypto.PrivateKey(self.me_private).sign(block_data) block = { @@ -357,7 +357,7 @@ class Bigchain(object): """ # 1. Check if current hash is correct - calculated_hash = util.hash_data(util.serialize(block['block'])) + calculated_hash = crypto.hash_data(util.serialize(block['block'])) if calculated_hash != block['id']: raise exceptions.InvalidHash() diff --git a/bigchaindb/crypto.py b/bigchaindb/crypto.py index fcec3686..bcbe0863 100644 --- a/bigchaindb/crypto.py +++ b/bigchaindb/crypto.py @@ -2,6 +2,8 @@ import binascii import base58 + +import sha3 import bitcoin from cryptography.hazmat.backends import default_backend @@ -144,3 +146,10 @@ def generate_key_pair(): return (private_value_base58, public_value_compressed_base58) + +def hash_data(data): + """Hash the provided data using SHA3-256""" + + return sha3.sha3_256(data.encode()).hexdigest() + + diff --git a/bigchaindb/util.py b/bigchaindb/util.py index b8c32b74..c6ca4d36 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -1,4 +1,3 @@ -import sha3 import json import time @@ -7,7 +6,7 @@ from datetime import datetime import bigchaindb from bigchaindb import exceptions -from bigchaindb.crypto import PrivateKey, PublicKey +from bigchaindb.crypto import PrivateKey, PublicKey, hash_data class ProcessGroup(object): @@ -158,10 +157,6 @@ def create_and_sign_tx(private_key, current_owner, new_owner, tx_input, operatio return sign_tx(tx, private_key) -def hash_data(data): - return sha3.sha3_256(data.encode()).hexdigest() - - def check_hash_and_signature(transaction): # Check hash of the transaction calculated_hash = hash_data(serialize(transaction['transaction'])) diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index bb20e25a..747f4a9a 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -8,8 +8,7 @@ import rethinkdb as r import bigchaindb from bigchaindb import util from bigchaindb import exceptions -from bigchaindb.crypto import PrivateKey, PublicKey, generate_key_pair -from bigchaindb.util import hash_data +from bigchaindb.crypto import PrivateKey, PublicKey, generate_key_pair, hash_data from bigchaindb.voter import Voter from bigchaindb.block import Block From 1fa47d4b5f40160389e1cc4c07a5a2a6cbeec923 Mon Sep 17 00:00:00 2001 From: vrde Date: Thu, 3 Mar 2016 03:09:54 +0100 Subject: [PATCH 19/29] Move last methods to util --- bigchaindb/core.py | 25 ------------------------- bigchaindb/util.py | 17 +++++++++++++++-- tests/db/test_bigchain_api.py | 6 +++--- tests/db/test_voter.py | 11 +++++------ 4 files changed, 23 insertions(+), 36 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 54f8f212..3b2a29d1 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -510,28 +510,3 @@ class Bigchain(object): return unvoted - - @staticmethod - def deserialize(data): - """Static method used to deserialize a JSON formatted string into a dict. - - Args: - data (str): JSON formatted string. - - Returns: - dict: dict resulting from the serialization of a JSON formatted string. - """ - - return json.loads(data, encoding="utf-8") - - @staticmethod - def generate_keys(): - """Generates a key pair. - - Returns: - tuple: `(private_key, public_key)`. ECDSA key pair using the secp256k1 curve encoded - in base58. - """ - - # generates and returns the keys serialized in hex - return crypto.generate_key_pair() diff --git a/bigchaindb/util.py b/bigchaindb/util.py index c6ca4d36..d8ddc9ce 100644 --- a/bigchaindb/util.py +++ b/bigchaindb/util.py @@ -32,7 +32,7 @@ class ProcessGroup(object): def serialize(data): - """Function used to serialize a dict into a JSON formatted string. + """Serialize a dict into a JSON formatted string. This function enforces rules like the separator and order of keys. This ensures that all dicts are serialized in the same way. @@ -52,8 +52,21 @@ def serialize(data): separators=(',', ':'), sort_keys=True) +def deserialize(data): + """Deserialize a JSON formatted string into a dict. + + Args: + data (str): JSON formatted string. + + Returns: + dict: dict resulting from the serialization of a JSON formatted string. + """ + + return json.loads(data, encoding="utf-8") + + def timestamp(): - """Function to calculate a UTC timestamp with microsecond precision. + """Calculate a UTC timestamp with microsecond precision. Returns: str: UTC timestamp. diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 747f4a9a..1fed8800 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -52,7 +52,7 @@ class TestBigchainApi(object): # assert tx_hash == tx_calculated_hash def test_transaction_signature(self, b): - sk, vk = b.generate_keys() + sk, vk = generate_key_pair() tx = b.create_transaction(vk, 'b', 'c', 'd') tx_signed = b.sign_transaction(tx, sk) @@ -61,7 +61,7 @@ class TestBigchainApi(object): def test_serializer(self, b): tx = b.create_transaction('a', 'b', 'c', 'd') - assert b.deserialize(util.serialize(tx)) == tx + assert util.deserialize(util.serialize(tx)) == tx @pytest.mark.usefixtures('inputs') def test_write_transaction(self, b, user_public_key, user_private_key): @@ -108,7 +108,7 @@ class TestBigchainApi(object): def test_assign_transaction_multiple_nodes(self, b, user_public_key, user_private_key): # create 5 federation nodes for _ in range(5): - b.federation_nodes.append(b.generate_keys()[1]) + b.federation_nodes.append(generate_key_pair()[1]) # test assignee for several transactions for _ in range(20): diff --git a/tests/db/test_voter.py b/tests/db/test_voter.py index da30b015..d8146829 100644 --- a/tests/db/test_voter.py +++ b/tests/db/test_voter.py @@ -3,11 +3,10 @@ import time import rethinkdb as r import multiprocessing as mp -from bigchaindb import Bigchain from bigchaindb import util from bigchaindb.voter import Voter, BlockStream -from bigchaindb.crypto import PublicKey +from bigchaindb.crypto import PublicKey, generate_key_pair class TestBigchainVoter(object): @@ -54,7 +53,7 @@ class TestBigchainVoter(object): genesis = b.create_genesis_block() # create a `CREATE` transaction - test_user_priv, test_user_pub = b.generate_keys() + test_user_priv, test_user_pub = generate_key_pair() tx = b.create_transaction(b.me, test_user_pub, None, 'CREATE') tx_signed = b.sign_transaction(tx, b.me_private) assert b.is_valid_transaction(tx_signed) @@ -96,7 +95,7 @@ class TestBigchainVoter(object): b.create_genesis_block() # create a `CREATE` transaction - test_user_priv, test_user_pub = b.generate_keys() + test_user_priv, test_user_pub = generate_key_pair() tx = b.create_transaction(b.me, test_user_pub, None, 'CREATE') tx_signed = b.sign_transaction(tx, b.me_private) assert b.is_valid_transaction(tx_signed) @@ -125,7 +124,7 @@ class TestBigchainVoter(object): assert len(blocks[1]['votes']) == 1 # create a `TRANSFER` transaction - test_user2_priv, test_user2_pub = b.generate_keys() + test_user2_priv, test_user2_pub = generate_key_pair() tx2 = b.create_transaction(test_user_pub, test_user2_pub, tx['id'], 'TRANSFER') tx2_signed = b.sign_transaction(tx2, test_user_priv) assert b.is_valid_transaction(tx2_signed) @@ -302,7 +301,7 @@ class TestBlockStream(object): def test_if_federation_size_is_greater_than_one_ignore_past_blocks(self, b): for _ in range(5): - b.federation_nodes.append(b.generate_keys()[1]) + b.federation_nodes.append(generate_key_pair()[1]) new_blocks = mp.Queue() bs = BlockStream(new_blocks) block_1 = b.create_block([]) From 280859651e1d59a6c3f47fd398e46f1d8e225620 Mon Sep 17 00:00:00 2001 From: troymc Date: Thu, 3 Mar 2016 15:50:14 +0100 Subject: [PATCH 20/29] Update ROADMAP.md --- ROADMAP.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index a3c65086..942d20bc 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,20 +2,27 @@ ## BigchainDB Protocols * Validation of other nodes -* Byzantine fault tolerance * Permissions framework -* Benchmarks (e.g. on transactions/second and latency) -* API/Wire protocol exposed by the BigchainDB dameon (HTTP or other). Eventually, the _only_ way for a client to communicate with a BigchainDB database will be via this API. * Protocol audits including security audits ## Implementation/Code * Node validation framework (inspect and agree or not with what the other nodes are doing) -* Federation management and monitoring/dashboard -* Packaging, dockerization, AWS image, etc. (i.e. easy deployment options) -* Drivers/SDKs for common client-side languages (e.g. Python, Ruby, JavaScript, Java) +* Federation management tools +* More tools for benchmarking a cluster +* Descriptions and results of more benchmarking tests +* AWS image and other easy deployment options +* Drivers/SDKs for more client-side languages (e.g. JavaScript, Ruby, Java) * ORM to better-decouple BigchainDB from its data store (will make it easy to try other databases) * Code audits including security audits ## Other/Future -* Multisig +* Byzantine fault tolerance * Better support for smart contract frameworks + +## Done/Past (i.e. was in the Roadmap) +* Packaging for PyPI (setup.py etc.) - [the latest version release can be found on PyPI](https://pypi.python.org/pypi/BigchainDB) +* Dockerization +* Monitoring/dashboard - initial vesion added in [Pull Request #72](https://github.com/bigchaindb/bigchaindb/pull/72) +* API/Wire protocol (RESTful HTTP API) - initial version added in [Pull Request #102](https://github.com/bigchaindb/bigchaindb/pull/102) +* Python driver/SDK - initial version added in [Pull Request #102](https://github.com/bigchaindb/bigchaindb/pull/102) +* Multisig support - initial version added in [Pull Request #107](https://github.com/bigchaindb/bigchaindb/pull/107) From 7d3bca5cb0d49c1eaa85575c6c596e184e4ebce3 Mon Sep 17 00:00:00 2001 From: troymc Date: Fri, 4 Mar 2016 11:15:33 +0100 Subject: [PATCH 21/29] Add Fault Tolerance to ROADMAP.md --- ROADMAP.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ROADMAP.md b/ROADMAP.md index 942d20bc..1c7752ed 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,6 +2,7 @@ ## BigchainDB Protocols * Validation of other nodes +* Fault tolerance * Permissions framework * Protocol audits including security audits From 8ed937aecc3f1f176869d79a55d976b2860093c6 Mon Sep 17 00:00:00 2001 From: vrde Date: Fri, 4 Mar 2016 16:00:30 +0100 Subject: [PATCH 22/29] Simplify code for client --- bigchaindb/client.py | 62 ++++++++++++++++---------------------------- tests/test_client.py | 39 ++-------------------------- 2 files changed, 25 insertions(+), 76 deletions(-) diff --git a/bigchaindb/client.py b/bigchaindb/client.py index 6feca7fc..0a9eed01 100644 --- a/bigchaindb/client.py +++ b/bigchaindb/client.py @@ -41,23 +41,37 @@ class Client: if not self.public_key or not self.private_key: raise exceptions.KeypairNotFoundException() - def make_tx(self, new_owner, tx_input, operation='TRANSFER', payload=None): - """Make a new transaction + def create(self, payload=None): + """Issue a transaction to create an asset. - Refer to the documentation of ``bigchaindb.util.create_tx`` + Args: + payload (dict): the payload for the transaction. + + Return: + The transaction pushed to the Federation. """ - return util.create_tx(self.public_key, new_owner, tx_input, operation, payload) + tx = util.create_tx(self.public_key, self.public_key, None, operation='CREATE', payload=payload) + signed_tx = util.sign_tx(tx, self.private_key) + return self._push(signed_tx) - def sign_tx(self, tx): - """Sign a transaction + def transfer(self, new_owner, tx_input, payload=None): + """Issue a transaction to transfer an asset. - Refer to the documentation of ``bigchaindb.util.sign_tx`` + Args: + new_owner (str): the public key of the new owner + tx_input (str): the id of the transaction to use as input + payload (dict, optional): the payload for the transaction. + + Return: + The transaction pushed to the Federation. """ - return util.sign_tx(tx, self.private_key) + tx = util.create_tx(self.public_key, new_owner, tx_input, operation='TRANSFER', payload=payload) + signed_tx = util.sign_tx(tx, self.private_key) + return self._push(signed_tx) - def push_tx(self, tx): + def _push(self, tx): """Submit a transaction to the Federation. Args: @@ -70,36 +84,6 @@ class Client: res = requests.post(self.api_endpoint + '/transactions/', json=tx) return res.json() - def create(self, payload=None): - """Create a transaction. - - Args: - payload (dict): the payload for the transaction. - - Return: - The transaction pushed to the Federation. - """ - - tx = self.make_tx(self.public_key, None, operation='CREATE', payload=payload) - signed_tx = self.sign_tx(tx) - return self.push_tx(signed_tx) - - def transfer(self, new_owner, tx_input, payload=None): - """Transfer a transaction. - - Args: - new_owner (str): the public key of the new owner - tx_input (str): the id of the transaction to use as input - payload (dict, optional): the payload for the transaction. - - Return: - The transaction pushed to the Federation. - """ - - tx = self.make_tx(new_owner, tx_input, payload=payload) - signed_tx = self.sign_tx(tx) - return self.push_tx(signed_tx) - def temp_client(): """Create a new temporary client. diff --git a/tests/test_client.py b/tests/test_client.py index 5943bb36..f5e15cad 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -29,42 +29,7 @@ def test_temp_client_returns_a_temp_client(): assert client.private_key -def test_client_can_make_transactions(client): - tx = client.make_tx('a', 123) - - assert tx['transaction']['current_owner'] == client.public_key - assert tx['transaction']['new_owner'] == 'a' - assert tx['transaction']['input'] == 123 - - -def test_client_can_sign_transactions(client): - from bigchaindb import util - - tx = client.make_tx('a', 123) - signed_tx = client.sign_tx(tx) - - assert signed_tx['transaction']['current_owner'] == client.public_key - assert signed_tx['transaction']['new_owner'] == 'a' - assert signed_tx['transaction']['input'] == 123 - - assert util.verify_signature(signed_tx) - - -def test_client_can_push_transactions(mock_requests_post, client): - from bigchaindb import util - - tx = client.make_tx('a', 123) - signed_tx = client.sign_tx(tx) - ret_tx = client.push_tx(signed_tx) - - assert ret_tx['transaction']['current_owner'] == client.public_key - assert ret_tx['transaction']['new_owner'] == 'a' - assert ret_tx['transaction']['input'] == 123 - - assert util.verify_signature(ret_tx) - - -def test_client_can_create_transactions_using_shortcut_method(mock_requests_post, client): +def test_client_can_create_assets(mock_requests_post, client): from bigchaindb import util tx = client.create() @@ -81,7 +46,7 @@ def test_client_can_create_transactions_using_shortcut_method(mock_requests_post assert util.verify_signature(tx) -def test_client_can_transfer_transactions_using_shortcut_method(mock_requests_post, client): +def test_client_can_transfer_assets(mock_requests_post, client): from bigchaindb import util tx = client.transfer('a', 123) From e90f8907dc04f733bc84ea1d1cf64f5629f573ab Mon Sep 17 00:00:00 2001 From: troymc Date: Fri, 4 Mar 2016 18:45:14 +0100 Subject: [PATCH 23/29] Remove Done/Past section from ROADMAP.md --- ROADMAP.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 1c7752ed..89ea0971 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -19,11 +19,3 @@ ## Other/Future * Byzantine fault tolerance * Better support for smart contract frameworks - -## Done/Past (i.e. was in the Roadmap) -* Packaging for PyPI (setup.py etc.) - [the latest version release can be found on PyPI](https://pypi.python.org/pypi/BigchainDB) -* Dockerization -* Monitoring/dashboard - initial vesion added in [Pull Request #72](https://github.com/bigchaindb/bigchaindb/pull/72) -* API/Wire protocol (RESTful HTTP API) - initial version added in [Pull Request #102](https://github.com/bigchaindb/bigchaindb/pull/102) -* Python driver/SDK - initial version added in [Pull Request #102](https://github.com/bigchaindb/bigchaindb/pull/102) -* Multisig support - initial version added in [Pull Request #107](https://github.com/bigchaindb/bigchaindb/pull/107) From 11c2a515d89714f4b3556d405978b7784e791ba7 Mon Sep 17 00:00:00 2001 From: troymc Date: Mon, 7 Mar 2016 11:03:09 +0100 Subject: [PATCH 24/29] Mention CHANGELOG.md in docs Release Notes --- docs/source/release-notes.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/release-notes.md b/docs/source/release-notes.md index 54546a89..1a80c120 100644 --- a/docs/source/release-notes.md +++ b/docs/source/release-notes.md @@ -4,4 +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) -We also have [a roadmap document in bigchaindb/ROADMAP.md](https://github.com/bigchaindb/bigchaindb/blob/develop/ROADMAP.md). +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. + +We also have [a roadmap document in ROADMAP.md](https://github.com/bigchaindb/bigchaindb/blob/develop/ROADMAP.md). From 62c47209e6a1721110aa75d40cdd0ef1f04280a0 Mon Sep 17 00:00:00 2001 From: troymc Date: Mon, 7 Mar 2016 11:03:48 +0100 Subject: [PATCH 25/29] First version of CHANGELOG.md --- CHANGELOG.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..f91c852c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,93 @@ +# Change Log (Release Notes) +All _notable_ changes to this project will be documented in this file (`CHANGELOG.md`). +This project adheres to [Semantic Versioning](http://semver.org/) (or at least we try). +Contributors to this file, please follow the guidelines on [keepachangelog.com](http://keepachangelog.com/). +Note that each version (or "release") is the name of a [Git _tag_](https://git-scm.com/book/en/v2/Git-Basics-Tagging) of a particular commit, so the associated date and time are the date and time of that commit (as reported by GitHub), _not_ the "Uploaded on" date listed on PyPI (which may differ). +For reference, the possible headings are: + +* **Added** for new features. +* **Changed** for changes in existing functionality. +* **Deprecated** for once-stable features removed in upcoming releases. +* **Removed** for deprecated features removed in this release. +* **Fixed** for any bug fixes. +* **Security** to invite users to upgrade in case of vulnerabilities. + + +## [Unreleased] - YYYY-MM-DD +Tag name: TBD += commit: TBD +committed: TBD + +### Added +- `CHANGELOG.md` (this file) +- Multisig support: [Pull Request #107](https://github.com/bigchaindb/bigchaindb/pull/107). +- API/Wire protocol (RESTful HTTP API): [Pull Request #102](https://github.com/bigchaindb/bigchaindb/pull/102). +- Python driver/SDK/API: [Pull Request #102](https://github.com/bigchaindb/bigchaindb/pull/102). +- Python Style Guide: [Pull Request #89](https://github.com/bigchaindb/bigchaindb/pull/89) +- Monitoring & dashboard tools: [Pull Request #72](https://github.com/bigchaindb/bigchaindb/pull/72). + +### Changed +- Rewrote [`README.md`](https://github.com/bigchaindb/bigchaindb/blob/develop/README.md) into four sets of links: Pull Requests [#80](https://github.com/bigchaindb/bigchaindb/pull/80) and [#115](https://github.com/bigchaindb/bigchaindb/pull/115) + +### Fixed +- Bug related to config overwrite: [Pull Request #97](https://github.com/bigchaindb/bigchaindb/pull/97) +- [Issue #71](https://github.com/bigchaindb/bigchaindb/issues/71) (Voter is not validating blocks correctly when checking for double spends) in [Pull Request #76](https://github.com/bigchaindb/bigchaindb/pull/76) + + +## [0.1.4] - 2016-02-22 +Tag name: v0.1.4 += commit: c4c850f480bc9ae72df2a54f81c0825b6fb4ed62 +committed: Feb 22, 2016, 11:51 AM GMT+1 + +### Added +- Add classifiers + +### Changed +- Allow running pytest tests in parallel (using [xdist](http://pytest.org/latest/xdist.html)): [Pull Request #65](https://github.com/bigchaindb/bigchaindb/pull/65) +- Allow non-interactive first start: [Pull Request #64](https://github.com/bigchaindb/bigchaindb/pull/64) to resolve [Issue #58](https://github.com/bigchaindb/bigchaindb/issues/58) + + +## [0.1.3] - 2016-02-16 +Tag name: v0.1.3 += commit 8926e3216c1ee39b9bc332e5ef1df2a8901262dd +committed Feb 16, 2016, 11:37 AM GMT+1 + +### Changed +- Changed from using Git Flow to GitHub flow (but with `develop` as the default branch). + + +## [0.1.2] - 2016-02-15 +Tag name: v0.1.2 += commit d2ff24166d69dda68dd7b4a24a88279b1d37e222 +committed Feb 15, 2016, 2:23 PM GMT+1 + +### Added + + +## [0.1.1] - 2016-02-15 +Tag name: v0.1.1 += commit 2a025448b29fe7056760de1039c73bbcfe992461 +committed Feb 15, 2016, 10:48 AM GMT+1 + +### Added +- "release 0.1.1": [Pull Request #37](https://github.com/bigchaindb/bigchaindb/pull/37) + +### Removed +- `tox.ini` [Pull Request #18](https://github.com/bigchaindb/bigchaindb/pull/18) +- `requirements.txt` in the root directory, and the entire `requirements/` directory: [Pull Request #14](https://github.com/bigchaindb/bigchaindb/pull/14) + + +## [0.1.0] - 2016-02-10 +Tag name: v0.1.0 += commit 8539e8dc2d036a4e0a866a3fb9e55889503254d5 +committed Feb 10, 2016, 10:04 PM GMT+1 + +The first public release of BigchainDB, including: + +- Initial BigchainDB Server code, including many tests and some code for benchmarking. +- Initial documentation (in `bigchaindb/docs`). +- Initial `README.md`, `ROADMAP.md`, `CODE_OF_CONDUCT.md`, and `CONTRIBUTING.md`. +- Packaging for PyPI, including `setup.py` and `setup.cfg`. +- Initial `Dockerfile` and `docker-compose.yml` (for deployment using Docker and Docker Compose). +- Initial `.gitignore` (list of things for git to ignore). +- Initial `.travis.yml` (used by Travis CI). From e34dd01d2bf952e7dba0f3a85f6bb6505882e7e5 Mon Sep 17 00:00:00 2001 From: troymc Date: Mon, 7 Mar 2016 13:57:45 +0100 Subject: [PATCH 26/29] In CONTRIBUTING.md, ask contributors to update CHANGELOG.md --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e3a517b..acd281ca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,6 +84,8 @@ git merge upstream/develop 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. + (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 From 6eb2fa463e7e824e46dd0b0d0e99b511d3529964 Mon Sep 17 00:00:00 2001 From: troymc Date: Mon, 7 Mar 2016 14:06:30 +0100 Subject: [PATCH 27/29] First revision of CHANGELOG.md --- CHANGELOG.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f91c852c..ca635f73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,11 +20,11 @@ committed: TBD ### Added - `CHANGELOG.md` (this file) -- Multisig support: [Pull Request #107](https://github.com/bigchaindb/bigchaindb/pull/107). -- API/Wire protocol (RESTful HTTP API): [Pull Request #102](https://github.com/bigchaindb/bigchaindb/pull/102). -- Python driver/SDK/API: [Pull Request #102](https://github.com/bigchaindb/bigchaindb/pull/102). +- Multisig support: [Pull Request #107](https://github.com/bigchaindb/bigchaindb/pull/107) +- API/Wire protocol (RESTful HTTP API): [Pull Request #102](https://github.com/bigchaindb/bigchaindb/pull/102) +- Python driver/SDK/API: [Pull Request #102](https://github.com/bigchaindb/bigchaindb/pull/102) - Python Style Guide: [Pull Request #89](https://github.com/bigchaindb/bigchaindb/pull/89) -- Monitoring & dashboard tools: [Pull Request #72](https://github.com/bigchaindb/bigchaindb/pull/72). +- Monitoring & dashboard tools: [Pull Request #72](https://github.com/bigchaindb/bigchaindb/pull/72) ### Changed - Rewrote [`README.md`](https://github.com/bigchaindb/bigchaindb/blob/develop/README.md) into four sets of links: Pull Requests [#80](https://github.com/bigchaindb/bigchaindb/pull/80) and [#115](https://github.com/bigchaindb/bigchaindb/pull/115) @@ -40,7 +40,7 @@ Tag name: v0.1.4 committed: Feb 22, 2016, 11:51 AM GMT+1 ### Added -- Add classifiers +- Added to `classifiers` to setup.py ### Changed - Allow running pytest tests in parallel (using [xdist](http://pytest.org/latest/xdist.html)): [Pull Request #65](https://github.com/bigchaindb/bigchaindb/pull/65) @@ -62,7 +62,10 @@ Tag name: v0.1.2 committed Feb 15, 2016, 2:23 PM GMT+1 ### Added +- Various tests +### Fixed +- Fix exception when running `start`: [Pull Request #32](https://github.com/bigchaindb/bigchaindb/pull/32) resolved [Issue #35] ## [0.1.1] - 2016-02-15 Tag name: v0.1.1 @@ -76,6 +79,9 @@ committed Feb 15, 2016, 10:48 AM GMT+1 - `tox.ini` [Pull Request #18](https://github.com/bigchaindb/bigchaindb/pull/18) - `requirements.txt` in the root directory, and the entire `requirements/` directory: [Pull Request #14](https://github.com/bigchaindb/bigchaindb/pull/14) +### Fixed +- Hotfix for AttributeError, fixed [Issue #27](https://github.com/bigchaindb/bigchaindb/issues/27) + ## [0.1.0] - 2016-02-10 Tag name: v0.1.0 From 17f7ba3894451f3b34adfadadc20a2fc800ea7fa Mon Sep 17 00:00:00 2001 From: troymc Date: Tue, 8 Mar 2016 10:30:43 +0100 Subject: [PATCH 28/29] Re-add link to licenses in the docs --- LICENSES.md | 2 +- docs/source/_templates/license-template.html | 4 ---- docs/source/_templates/sidebar-links-template.html | 6 ------ docs/source/_templates/sidebar-title-template.html | 1 - docs/source/conf.py | 11 ++++------- docs/source/index.rst | 1 + docs/source/licenses.md | 3 +++ 7 files changed, 9 insertions(+), 19 deletions(-) delete mode 100644 docs/source/_templates/license-template.html delete mode 100644 docs/source/_templates/sidebar-links-template.html delete mode 100644 docs/source/_templates/sidebar-title-template.html create mode 100644 docs/source/licenses.md diff --git a/LICENSES.md b/LICENSES.md index b6e3fa31..3838fedc 100644 --- a/LICENSES.md +++ b/LICENSES.md @@ -2,7 +2,7 @@ All officially-supported BigchainDB _driver code_ is licensed under the Apache License, Version 2.0, the full text of which can be found at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0). -All short code snippets embedded in the official BigchainDB _documentation_ is licensed under the Apache License, Version 2.0, the full text of which can be found at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0). +All short code snippets embedded in the official BigchainDB _documentation_ are licensed under the Apache License, Version 2.0, the full text of which can be found at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0). All _other_ officially-supported BigchainDB code is licensed under the GNU Affero General Public License version 3 (AGPLv3), the full text of which can be found at [http://www.gnu.org/licenses/agpl.html](http://www.gnu.org/licenses/agpl.html). diff --git a/docs/source/_templates/license-template.html b/docs/source/_templates/license-template.html deleted file mode 100644 index 758cbb79..00000000 --- a/docs/source/_templates/license-template.html +++ /dev/null @@ -1,4 +0,0 @@ -

License

-

-This documentation is licensed under a Creative Commons Attribution 4.0 International License. -

\ No newline at end of file diff --git a/docs/source/_templates/sidebar-links-template.html b/docs/source/_templates/sidebar-links-template.html deleted file mode 100644 index 71ead79d..00000000 --- a/docs/source/_templates/sidebar-links-template.html +++ /dev/null @@ -1,6 +0,0 @@ -

Quick links

-

-GitHub repository -
-BigchainDB website -

\ No newline at end of file diff --git a/docs/source/_templates/sidebar-title-template.html b/docs/source/_templates/sidebar-title-template.html deleted file mode 100644 index 519d3dc9..00000000 --- a/docs/source/_templates/sidebar-title-template.html +++ /dev/null @@ -1 +0,0 @@ -

BigchainDB Documentation

\ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 04f749df..766278db 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -168,13 +168,10 @@ html_static_path = ['_static'] #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -html_sidebars = { - '**': ['sidebar-title-template.html', - 'globaltoc.html', - 'sidebar-links-template.html', - 'searchbox.html', - 'license-template.html'], -} +#html_sidebars = { +# '**': ['globaltoc.html', +# 'searchbox.html'], +#} # Additional templates that should be rendered to pages, maps page names to # template names. diff --git a/docs/source/index.rst b/docs/source/index.rst index 6934f335..837b24f2 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -23,6 +23,7 @@ Table of Contents json-serialization developer-interface monitoring + licenses contributing faq release-notes diff --git a/docs/source/licenses.md b/docs/source/licenses.md new file mode 100644 index 00000000..aa60e2bc --- /dev/null +++ b/docs/source/licenses.md @@ -0,0 +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 From 9e1ff01057f4c6fe03684a84ee676c48c92c232d Mon Sep 17 00:00:00 2001 From: Cristian S Date: Tue, 8 Mar 2016 14:06:18 +0200 Subject: [PATCH 29/29] Add missing package form installer Without this running blockchaindb yields: Traceback (most recent call last): File "/usr/lib/python3.4/runpy.py", line 170, in _run_module_as_main "__main__", mod_spec) File "/usr/lib/python3.4/runpy.py", line 85, in _run_code exec(code, run_globals) File "/usr/lib/python3.4/trace.py", line 858, in main() File "/usr/lib/python3.4/trace.py", line 804, in main t.runctx(code, globs, globs) File "/usr/lib/python3.4/trace.py", line 510, in runctx exec(cmd, globals, locals) File "/usr/local/bin/bigchaindb", line 9, in load_entry_point('BigchainDB==0.1.4', 'console_scripts', 'bigchaindb')() File "/usr/local/lib/python3.4/dist-packages/pkg_resources/__init__.py", line 549, in load_entry_point return get_distribution(dist).load_entry_point(group, name) File "/usr/local/lib/python3.4/dist-packages/pkg_resources/__init__.py", line 2542, in load_entry_point return ep.load() File "/usr/local/lib/python3.4/dist-packages/pkg_resources/__init__.py", line 2202, in load return self.resolve() File "/usr/local/lib/python3.4/dist-packages/pkg_resources/__init__.py", line 2208, in resolve module = __import__(self.module_name, fromlist=['__name__'], level=0) File "/usr/local/lib/python3.4/dist-packages/BigchainDB-0.1.4-py3.4.egg/bigchaindb/commands/bigchain.py", line 14, in from bigchaindb.processes import Processes File "/usr/local/lib/python3.4/dist-packages/BigchainDB-0.1.4-py3.4.egg/bigchaindb/processes.py", line 9, in from bigchaindb.web import server ImportError: No module named 'bigchaindb.web' --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0cb4aea3..8d888e6a 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ setup( 'Operating System :: POSIX :: Linux', ], - packages=['bigchaindb', 'bigchaindb.commands', 'bigchaindb.db'], + packages=['bigchaindb', 'bigchaindb.commands', 'bigchaindb.db', 'bigchaindb.web'], entry_points={ 'console_scripts': [