Merge pull request #384 from bigchaindb/feat/380/round-timestamp-add-uuid

Feat/380/round timestamp add uuid
This commit is contained in:
Rodolphe Marques 2016-06-15 13:32:53 +02:00 committed by GitHub
commit df18298cc2
6 changed files with 109 additions and 69 deletions

View File

@ -386,6 +386,10 @@ class Bigchain(object):
dict: created block.
"""
# Prevent the creation of empty blocks
if len(validated_transactions) == 0:
raise exceptions.OperationError('Empty block creation is not allowed')
# Create the new block
block = {
'timestamp': util.timestamp(),

View File

@ -54,7 +54,7 @@ def init():
.run(conn)
# secondary index for payload hash
r.db(dbname).table('bigchain')\
.index_create('payload_hash', r.row['block']['transactions']['transaction']['data']['hash'], multi=True)\
.index_create('payload_uuid', r.row['block']['transactions']['transaction']['data']['uuid'], multi=True)\
.run(conn)
# wait for rethinkdb to finish creating secondary indexes

View File

@ -4,7 +4,7 @@ import contextlib
import threading
import queue
import multiprocessing as mp
from datetime import datetime
import uuid
import rapidjson
@ -127,14 +127,13 @@ def deserialize(data):
def timestamp():
"""Calculate a UTC timestamp with microsecond precision.
"""Calculate a UTC timestamp with second precision.
Returns:
str: UTC timestamp.
"""
dt = datetime.utcnow()
return "{0:.6f}".format(time.mktime(dt.timetuple()) + dt.microsecond / 1e6)
return str(round(time.time()))
# TODO: Consider remove the operation (if there are no inputs CREATE else TRANSFER)
@ -222,15 +221,13 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None):
# handle payload
data = None
if payload is not None:
if isinstance(payload, dict):
hash_payload = crypto.hash_data(serialize(payload))
data = {
'hash': hash_payload,
'payload': payload
}
else:
raise TypeError('`payload` must be an dict instance')
if isinstance(payload, (dict, type(None))):
data = {
'uuid': str(uuid.uuid4()),
'payload': payload
}
else:
raise TypeError('`payload` must be an dict instance or None')
# handle inputs
fulfillments = []

View File

@ -57,7 +57,7 @@ Also, note that timestamps come from clients and nodes. Unless you have some rea
"operation": "<string>",
"timestamp": "<timestamp from client>",
"data": {
"hash": "<hash of payload>",
"uuid": "<uuid4>",
"payload": "<any JSON document>"
}
}
@ -78,7 +78,7 @@ Here's some explanation of the contents of a transaction:
- `operation`: String representation of the operation being performed (currently either "CREATE" or "TRANSFER"). It determines how the transaction should be validated.
- `timestamp`: Time of creation of the transaction in UTC. It's provided by the client.
- `data`:
- `hash`: The hash of the serialized `payload`.
- `uuid`: UUID version 4 (random) converted to a string of hex digits in standard form.
- `payload`: Can be any JSON document. It may be empty in the case of a transfer transaction.
Later, when we get to the models for the block and the vote, we'll see that both include a signature (from the node which created it). You may wonder why transactions don't have signatures... The answer is that they do! They're just hidden inside the `fulfillment` string of each fulfillment. A creation transaction is signed by the node that created it. A transfer transaction is signed by whoever currently controls or owns it.

View File

@ -22,6 +22,20 @@ def test_remove_unclosed_sockets():
pass
# Some util functions
def dummy_tx():
b = bigchaindb.Bigchain()
tx = b.create_transaction(b.me, b.me, None, 'CREATE')
tx_signed = b.sign_transaction(tx, b.me_private)
return tx_signed
def dummy_block():
b = bigchaindb.Bigchain()
block = b.create_block([dummy_tx()])
return block
class TestBigchainApi(object):
def test_create_transaction_create(self, b, user_sk):
tx = b.create_transaction(b.me, user_sk, None, 'CREATE')
@ -33,6 +47,17 @@ class TestBigchainApi(object):
with pytest.raises(TypeError):
b.create_transaction('a', 'b', 'c', 'd', payload=[])
def test_create_transaction_payload_none(self, b, user_vk):
tx = b.create_transaction(b.me, user_vk, None, 'CREATE')
assert len(tx['transaction']['data']['uuid']) == 36
assert tx['transaction']['data']['payload'] is None
def test_create_transaction_payload(self, b, user_vk):
payload = {'msg': 'Hello BigchainDB!'}
tx = b.create_transaction(b.me, user_vk, None, 'CREATE', payload=payload)
assert len(tx['transaction']['data']['uuid']) == 36
assert tx['transaction']['data']['payload'] == payload
@pytest.mark.usefixtures('inputs')
def test_create_transaction_transfer(self, b, user_vk, user_sk):
input_tx = b.get_owned_ids(user_vk).pop()
@ -48,24 +73,6 @@ class TestBigchainApi(object):
assert b.validate_fulfillments(tx) == False
assert b.validate_fulfillments(tx_signed) == True
def test_transaction_hash(self, b, user_vk):
payload = {'cats': 'are awesome'}
tx = b.create_transaction(user_vk, user_vk, None, 'CREATE', payload)
tx_calculated = {
'conditions': [{'cid': 0,
'condition': tx['transaction']['conditions'][0]['condition'],
'new_owners': [user_vk]}],
'data': {'hash': crypto.hash_data(util.serialize(payload)),
'payload': payload},
'fulfillments': [{'current_owners': [user_vk],
'fid': 0,
'fulfillment': None,
'input': None}],
'operation': 'CREATE',
'timestamp': tx['transaction']['timestamp']
}
assert tx['transaction']['data'] == tx_calculated['data']
# assert tx_hash == tx_calculated_hash
def test_transaction_signature(self, b, user_sk, user_vk):
tx = b.create_transaction(user_vk, user_vk, None, 'CREATE')
@ -218,7 +225,8 @@ class TestBigchainApi(object):
assert prev_block_id == last_block['id']
def test_create_new_block(self, b):
new_block = b.create_block([])
tx = dummy_tx()
new_block = b.create_block([tx])
block_hash = crypto.hash_data(util.serialize(new_block['block']))
assert new_block['block']['voters'] == [b.me]
@ -227,6 +235,12 @@ class TestBigchainApi(object):
assert new_block['id'] == block_hash
assert new_block['votes'] == []
def test_create_empty_block(self, b):
with pytest.raises(exceptions.OperationError) as excinfo:
b.create_block([])
assert excinfo.value.args[0] == 'Empty block creation is not allowed'
def test_get_last_voted_block_returns_genesis_if_no_votes_has_been_casted(self, b):
b.create_genesis_block()
genesis = list(r.table('bigchain')
@ -239,9 +253,9 @@ class TestBigchainApi(object):
assert b.get_last_voted_block() == genesis
block_1 = b.create_block([])
block_2 = b.create_block([])
block_3 = b.create_block([])
block_1 = dummy_block()
block_2 = dummy_block()
block_3 = dummy_block()
b.write_block(block_1, durability='hard')
b.write_block(block_2, durability='hard')
@ -259,7 +273,7 @@ class TestBigchainApi(object):
def test_no_vote_written_if_block_already_has_vote(self, b):
b.create_genesis_block()
block_1 = b.create_block([])
block_1 = dummy_block()
b.write_block(block_1, durability='hard')
@ -405,7 +419,7 @@ class TestTransactionValidation(object):
class TestBlockValidation(object):
def test_wrong_block_hash(self, b):
block = b.create_block([])
block = dummy_block()
# change block hash
block.update({'id': 'abc'})
@ -446,7 +460,7 @@ class TestBlockValidation(object):
assert excinfo.value.args[0] == 'current_owner `a` does not own the input `{}`'.format(valid_input)
def test_invalid_block_id(self, b):
block = b.create_block([])
block = dummy_block()
# change block hash
block.update({'id': 'abc'})
@ -468,7 +482,7 @@ class TestBlockValidation(object):
def test_invalid_signature(self, b):
# create a valid block
block = b.create_block([])
block = dummy_block()
# replace the block signature with an invalid one
block['signature'] = crypto.SigningKey(b.me_private).sign(b'wrongdata')
@ -480,7 +494,7 @@ class TestBlockValidation(object):
def test_invalid_node_pubkey(self, b):
# blocks can only be created by a federation node
# create a valid block
block = b.create_block([])
block = dummy_block()
# create some temp keys
tmp_sk, tmp_vk = crypto.generate_key_pair()
@ -506,7 +520,7 @@ class TestBigchainVoter(object):
genesis = b.create_genesis_block()
# create valid block
block = b.create_block([])
block = dummy_block()
# assert block is valid
assert b.is_valid_block(block)
b.write_block(block, durability='hard')
@ -578,7 +592,7 @@ class TestBigchainVoter(object):
def test_vote_creation_valid(self, b):
# create valid block
block = b.create_block([])
block = dummy_block()
# retrieve vote
vote = b.vote(block, 'abc', True)
@ -592,7 +606,7 @@ class TestBigchainVoter(object):
def test_vote_creation_invalid(self, b):
# create valid block
block = b.create_block([])
block = dummy_block()
# retrieve vote
vote = b.vote(block, 'abc', False)
@ -804,9 +818,9 @@ class TestBigchainBlock(object):
def test_revert_delete_block(self, b):
b.create_genesis_block()
block_1 = b.create_block([])
block_2 = b.create_block([])
block_3 = b.create_block([])
block_1 = dummy_block()
block_2 = dummy_block()
block_3 = dummy_block()
b.write_block(block_1, durability='hard')
b.write_block(block_2, durability='hard')
@ -2106,7 +2120,7 @@ class TestCryptoconditions(object):
assert b.is_valid_transaction(escrow_tx_transfer) == escrow_tx_transfer
assert b.validate_transaction(escrow_tx_transfer) == escrow_tx_transfer
time.sleep(time_sleep)
time.sleep(time_sleep + 1)
assert b.is_valid_transaction(escrow_tx_transfer) is False
with pytest.raises(exceptions.InvalidSignature):
@ -2241,7 +2255,7 @@ class TestCryptoconditions(object):
block = b.create_block([escrow_tx_transfer])
b.write_block(block, durability='hard')
time.sleep(time_sleep)
time.sleep(time_sleep + 1)
assert b.is_valid_transaction(escrow_tx_transfer) is False
with pytest.raises(exceptions.InvalidSignature):

View File

@ -9,6 +9,20 @@ from bigchaindb.voter import Voter, Election, BlockStream
from bigchaindb import crypto, Bigchain
# Some util functions
def dummy_tx():
b = Bigchain()
tx = b.create_transaction(b.me, b.me, None, 'CREATE')
tx_signed = b.sign_transaction(tx, b.me_private)
return tx_signed
def dummy_block():
b = Bigchain()
block = b.create_block([dummy_tx()])
return block
class TestBigchainVoter(object):
def test_valid_block_voting(self, b):
@ -17,7 +31,9 @@ class TestBigchainVoter(object):
genesis = b.create_genesis_block()
# create valid block
block = b.create_block([])
# sleep so that `block` as a higher timestamp then genesis
time.sleep(1)
block = dummy_block()
# assert block is valid
assert b.is_valid_block(block)
b.write_block(block, durability='hard')
@ -59,6 +75,8 @@ class TestBigchainVoter(object):
assert b.is_valid_transaction(tx_signed)
# create valid block
# sleep so that block as a higher timestamp then genesis
time.sleep(1)
block = b.create_block([tx_signed])
# assert block is valid
assert b.is_valid_block(block)
@ -172,6 +190,8 @@ class TestBigchainVoter(object):
genesis = b.create_genesis_block()
# create invalid block
# sleep so that `block` as a higher timestamp then `genesis`
time.sleep(1)
block = b.create_block([transaction_signed])
# change transaction id to make it invalid
block['block']['transactions'][0]['id'] = 'abc'
@ -201,7 +221,7 @@ class TestBigchainVoter(object):
def test_vote_creation_valid(self, b):
# create valid block
block = b.create_block([])
block = dummy_block()
# retrieve vote
vote = b.vote(block, 'abc', True)
@ -215,7 +235,7 @@ class TestBigchainVoter(object):
def test_vote_creation_invalid(self, b):
# create valid block
block = b.create_block([])
block = dummy_block()
# retrieve vote
vote = b.vote(block, 'abc', False)
@ -233,9 +253,9 @@ class TestBigchainVoter(object):
# insert blocks in the database while the voter process is not listening
# (these blocks won't appear in the changefeed)
block_1 = b.create_block([])
block_1 = dummy_block()
b.write_block(block_1, durability='hard')
block_2 = b.create_block([])
block_2 = dummy_block()
b.write_block(block_2, durability='hard')
# voter is back online, we simulate that by creating a queue and a Voter instance
@ -243,7 +263,7 @@ class TestBigchainVoter(object):
voter = Voter(q_new_block)
# create a new block that will appear in the changefeed
block_3 = b.create_block([])
block_3 = dummy_block()
b.write_block(block_3, durability='hard')
# put the last block in the queue
@ -266,9 +286,12 @@ class TestBigchainVoter(object):
def test_voter_chains_blocks_with_the_previous_ones(self, b):
b.create_genesis_block()
block_1 = b.create_block([])
# sleep so that `block_*` as a higher timestamp then `genesis`
time.sleep(1)
block_1 = dummy_block()
b.write_block(block_1, durability='hard')
block_2 = b.create_block([])
time.sleep(1)
block_2 = dummy_block()
b.write_block(block_2, durability='hard')
q_new_block = mp.Queue()
@ -294,7 +317,7 @@ class TestBigchainVoter(object):
def test_voter_checks_for_previous_vote(self, b):
b.create_genesis_block()
block_1 = b.create_block([])
block_1 = dummy_block()
b.write_block(block_1, durability='hard')
q_new_block = mp.Queue()
@ -326,7 +349,7 @@ class TestBlockElection(object):
def test_quorum(self, b):
# create a new block
test_block = b.create_block([])
test_block = dummy_block()
# simulate a federation with four voters
key_pairs = [crypto.generate_key_pair() for _ in range(4)]
@ -393,7 +416,7 @@ class TestBlockElection(object):
def test_quorum_odd(self, b):
# test partial quorum situations for odd numbers of voters
# create a new block
test_block = b.create_block([])
test_block = dummy_block()
# simulate a federation with four voters
key_pairs = [crypto.generate_key_pair() for _ in range(5)]
@ -476,7 +499,7 @@ class TestBlockStream(object):
b.federation_nodes.append(crypto.generate_key_pair()[1])
new_blocks = mp.Queue()
bs = BlockStream(new_blocks)
block_1 = b.create_block([])
block_1 = dummy_block()
new_blocks.put(block_1)
assert block_1 == bs.get()
@ -485,8 +508,8 @@ class TestBlockStream(object):
bs = BlockStream(new_blocks)
# create two blocks
block_1 = b.create_block([])
block_2 = b.create_block([])
block_1 = dummy_block()
block_2 = dummy_block()
# write the blocks
b.write_block(block_1, durability='hard')
@ -502,8 +525,10 @@ class TestBlockStream(object):
def test_if_old_blocks_get_should_return_old_block_first(self, b):
# create two blocks
block_1 = b.create_block([])
block_2 = b.create_block([])
block_1 = dummy_block()
# sleep so that block_2 as an higher timestamp then block_1
time.sleep(1)
block_2 = dummy_block()
# write the blocks
b.write_block(block_1, durability='hard')
@ -520,8 +545,8 @@ class TestBlockStream(object):
# pp(block_2)
# create two new blocks that will appear in the changefeed
block_3 = b.create_block([])
block_4 = b.create_block([])
block_3 = dummy_block()
block_4 = dummy_block()
# simulate a changefeed
new_blocks.put(block_3)