mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge pull request #384 from bigchaindb/feat/380/round-timestamp-add-uuid
Feat/380/round timestamp add uuid
This commit is contained in:
commit
df18298cc2
@ -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(),
|
||||
|
@ -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
|
||||
|
@ -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 = []
|
||||
|
@ -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.
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user