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()