mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
769 lines
29 KiB
Python
769 lines
29 KiB
Python
import multiprocessing as mp
|
|
import random
|
|
import time
|
|
|
|
import pytest
|
|
import rethinkdb as r
|
|
|
|
import bigchaindb
|
|
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* '
|
|
'interfere with the correctness of the tests. ')
|
|
def test_remove_unclosed_sockets():
|
|
pass
|
|
|
|
class TestBigchainApi(object):
|
|
|
|
def test_create_transaction(self, b):
|
|
tx = b.create_transaction('a', 'b', 'c', 'd')
|
|
|
|
assert sorted(tx) == sorted(['id', 'transaction'])
|
|
assert sorted(tx['transaction']) == sorted(['current_owner', 'new_owner', 'input', 'operation',
|
|
'timestamp', 'data'])
|
|
|
|
def test_create_transaction_with_unsupported_payload_raises(self, b):
|
|
with pytest.raises(TypeError):
|
|
b.create_transaction('a', 'b', 'c', 'd', payload=[])
|
|
|
|
|
|
def test_transaction_hash(self, b):
|
|
payload = {'cats': 'are awesome'}
|
|
tx = b.create_transaction('a', 'b', 'c', 'd', payload)
|
|
tx_calculated = {
|
|
'current_owner': 'a',
|
|
'new_owner': 'b',
|
|
'input': 'c',
|
|
'operation': 'd',
|
|
'timestamp': tx['transaction']['timestamp'],
|
|
'data': {
|
|
'hash': hash_data(b.serialize(payload)),
|
|
'payload': payload
|
|
}
|
|
}
|
|
assert tx['transaction']['data'] == tx_calculated['data']
|
|
# assert tx_hash == tx_calculated_hash
|
|
|
|
def test_transaction_signature(self, b):
|
|
sk, vk = b.generate_keys()
|
|
tx = b.create_transaction(vk, 'b', 'c', 'd')
|
|
tx_signed = b.sign_transaction(tx, sk)
|
|
|
|
assert 'signature' in tx_signed
|
|
assert b.verify_signature(tx_signed)
|
|
|
|
def test_serializer(self, b):
|
|
tx = b.create_transaction('a', 'b', 'c', 'd')
|
|
assert b.deserialize(b.serialize(tx)) == tx
|
|
|
|
@pytest.mark.usefixtures('inputs')
|
|
def test_write_transaction(self, b, user_public_key, user_private_key):
|
|
input_tx = b.get_owned_ids(user_public_key).pop()
|
|
tx = b.create_transaction(user_public_key, 'b', input_tx, 'd')
|
|
tx_signed = b.sign_transaction(tx, user_private_key)
|
|
response = b.write_transaction(tx_signed)
|
|
|
|
assert response['skipped'] == 0
|
|
assert response['deleted'] == 0
|
|
assert response['unchanged'] == 0
|
|
assert response['errors'] == 0
|
|
assert response['replaced'] == 0
|
|
assert response['inserted'] == 1
|
|
|
|
@pytest.mark.usefixtures('inputs')
|
|
def test_read_transaction(self, b, user_public_key, user_private_key):
|
|
input_tx = b.get_owned_ids(user_public_key).pop()
|
|
tx = b.create_transaction(user_public_key, 'b', input_tx, 'd')
|
|
tx_signed = b.sign_transaction(tx, user_private_key)
|
|
b.write_transaction(tx_signed)
|
|
|
|
# create block and write it to the bighcain before retrieving the transaction
|
|
block = b.create_block([tx_signed])
|
|
b.write_block(block, durability='hard')
|
|
|
|
response = b.get_transaction(tx_signed["id"])
|
|
assert b.serialize(tx_signed) == b.serialize(response)
|
|
|
|
@pytest.mark.usefixtures('inputs')
|
|
def test_assign_transaction_one_node(self, b, user_public_key, user_private_key):
|
|
input_tx = b.get_owned_ids(user_public_key).pop()
|
|
tx = b.create_transaction(user_public_key, 'b', input_tx, 'd')
|
|
tx_signed = b.sign_transaction(tx, user_private_key)
|
|
b.write_transaction(tx_signed)
|
|
|
|
# retrieve the transaction
|
|
response = r.table('backlog').get(tx_signed['id']).run(b.conn)
|
|
|
|
# check if the assignee is the current node
|
|
assert response['assignee'] == b.me
|
|
|
|
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):
|
|
input_tx = b.get_owned_ids(user_public_key).pop()
|
|
tx = b.create_transaction(user_public_key, 'b', input_tx, 'd')
|
|
tx_signed = b.sign_transaction(tx, user_private_key)
|
|
b.write_transaction(tx_signed)
|
|
|
|
# retrieve the transaction
|
|
response = r.table('backlog').get(tx_signed['id']).run(b.conn)
|
|
|
|
# check if the assignee is the federation_nodes
|
|
assert response['assignee'] in b.federation_nodes
|
|
|
|
@pytest.mark.usefixtures('inputs')
|
|
def test_genesis_block(self, b):
|
|
response = list(r.table('bigchain')
|
|
.filter(r.row['block_number'] == 0)
|
|
.run(b.conn))[0]
|
|
|
|
assert response['block_number'] == 0
|
|
assert len(response['block']['transactions']) == 1
|
|
assert response['block']['transactions'][0]['transaction']['operation'] == 'GENESIS'
|
|
assert response['block']['transactions'][0]['transaction']['input'] is None
|
|
|
|
def test_create_genesis_block_fails_if_table_not_empty(self, b):
|
|
b.create_genesis_block()
|
|
|
|
with pytest.raises(bigchaindb.core.GenesisBlockAlreadyExistsError):
|
|
b.create_genesis_block()
|
|
|
|
genesis_blocks = list(r.table('bigchain')
|
|
.filter(r.row['block_number'] == 0)
|
|
.run(b.conn))
|
|
|
|
assert len(genesis_blocks) == 1
|
|
|
|
@pytest.mark.skipif(reason='This test may not make sense after changing the chainification mode')
|
|
def test_get_last_block(self, b):
|
|
# get the number of blocks
|
|
num_blocks = r.table('bigchain').count().run(b.conn)
|
|
|
|
# get the last block
|
|
last_block = b.get_last_block()
|
|
|
|
assert last_block['block']['block_number'] == num_blocks - 1
|
|
|
|
@pytest.mark.skipif(reason='This test may not make sense after changing the chainification mode')
|
|
def test_get_last_block_id(self, b):
|
|
last_block = b.get_last_block()
|
|
last_block_id = b.get_last_block_id()
|
|
|
|
assert last_block_id == last_block['id']
|
|
|
|
@pytest.mark.skipif(reason='This test may not make sense after changing the chainification mode')
|
|
def test_get_previous_block(self, b):
|
|
last_block = b.get_last_block()
|
|
new_block = b.create_block([])
|
|
b.write_block(new_block, durability='hard')
|
|
|
|
prev_block = b.get_previous_block(new_block)
|
|
|
|
assert prev_block == last_block
|
|
|
|
@pytest.mark.skipif(reason='This test may not make sense after changing the chainification mode')
|
|
def test_get_previous_block_id(self, b):
|
|
last_block = b.get_last_block()
|
|
new_block = b.create_block([])
|
|
b.write_block(new_block, durability='hard')
|
|
|
|
prev_block_id = b.get_previous_block_id(new_block)
|
|
|
|
assert prev_block_id == last_block['id']
|
|
|
|
def test_create_new_block(self, b):
|
|
new_block = b.create_block([])
|
|
block_hash = hash_data(b.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']) == True
|
|
assert new_block['id'] == block_hash
|
|
assert new_block['votes'] == []
|
|
|
|
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')
|
|
.filter(r.row['block_number'] == 0)
|
|
.run(b.conn))[0]
|
|
assert b.get_last_voted_block() == genesis
|
|
|
|
def test_get_last_voted_block_returns_the_correct_block(self, b):
|
|
genesis = b.create_genesis_block()
|
|
|
|
assert b.get_last_voted_block() == genesis
|
|
|
|
block_1 = b.create_block([])
|
|
block_2 = b.create_block([])
|
|
block_3 = b.create_block([])
|
|
|
|
b.write_block(block_1, durability='hard')
|
|
b.write_block(block_2, durability='hard')
|
|
b.write_block(block_3, durability='hard')
|
|
|
|
b.write_vote(block_1, b.vote(block_1, b.get_last_voted_block(), True), 1)
|
|
assert b.get_last_voted_block()['id'] == block_1['id']
|
|
|
|
b.write_vote(block_2, b.vote(block_2, b.get_last_voted_block(), True), 2)
|
|
assert b.get_last_voted_block()['id'] == block_2['id']
|
|
|
|
b.write_vote(block_3, b.vote(block_3, b.get_last_voted_block(), True), 3)
|
|
assert b.get_last_voted_block()['id'] == block_3['id']
|
|
|
|
|
|
class TestTransactionValidation(object):
|
|
|
|
def test_create_operation_with_inputs(self, b):
|
|
tx = b.create_transaction('a', 'b', 'c', 'CREATE')
|
|
with pytest.raises(ValueError) as excinfo:
|
|
b.validate_transaction(tx)
|
|
|
|
assert excinfo.value.args[0] == 'A CREATE operation has no inputs'
|
|
assert b.is_valid_transaction(tx) == False
|
|
|
|
def test_create_operation_not_federation_node(self, b):
|
|
tx = b.create_transaction('a', 'b', None, 'CREATE')
|
|
with pytest.raises(exceptions.OperationError) as excinfo:
|
|
b.validate_transaction(tx)
|
|
|
|
assert excinfo.value.args[0] == 'Only federation nodes can use the operation `CREATE`'
|
|
assert b.is_valid_transaction(tx) == False
|
|
|
|
def test_non_create_operation_no_inputs(self, b):
|
|
tx = b.create_transaction('a', 'b', None, 'd')
|
|
with pytest.raises(ValueError) as excinfo:
|
|
b.validate_transaction(tx)
|
|
|
|
assert excinfo.value.args[0] == 'Only `CREATE` transactions can have null inputs'
|
|
assert b.is_valid_transaction(tx) == False
|
|
|
|
def test_non_create_input_not_found(self, b):
|
|
tx = b.create_transaction('a', 'b', 'c', 'd')
|
|
with pytest.raises(exceptions.TransactionDoesNotExist) as excinfo:
|
|
b.validate_transaction(tx)
|
|
|
|
assert excinfo.value.args[0] == 'input `c` does not exist in the bigchain'
|
|
assert b.is_valid_transaction(tx) == False
|
|
|
|
@pytest.mark.usefixtures('inputs')
|
|
def test_non_create_valid_input_wrong_owner(self, b, user_public_key):
|
|
valid_input = b.get_owned_ids(user_public_key).pop()
|
|
tx = b.create_transaction('a', 'b', valid_input, 'c')
|
|
with pytest.raises(exceptions.TransactionOwnerError) as excinfo:
|
|
b.validate_transaction(tx)
|
|
|
|
assert excinfo.value.args[0] == 'current_owner `a` does not own the input `{}`'.format(valid_input)
|
|
assert b.is_valid_transaction(tx) == False
|
|
|
|
@pytest.mark.usefixtures('inputs')
|
|
def test_non_create_double_spend(self, b, user_public_key, user_private_key):
|
|
input_valid = b.get_owned_ids(user_public_key).pop()
|
|
tx_valid = b.create_transaction(user_public_key, 'b', input_valid, 'd')
|
|
tx_valid_signed = b.sign_transaction(tx_valid, user_private_key)
|
|
b.write_transaction(tx_valid_signed)
|
|
|
|
# create and write block to bigchain
|
|
block = b.create_block([tx_valid_signed])
|
|
b.write_block(block, durability='hard')
|
|
|
|
# create another transaction with the same input
|
|
tx_double_spend = b.create_transaction(user_public_key, 'd', input_valid, 'd')
|
|
with pytest.raises(exceptions.DoubleSpend) as excinfo:
|
|
b.validate_transaction(tx_double_spend)
|
|
|
|
assert excinfo.value.args[0] == 'input `{}` was already spent'.format(input_valid)
|
|
assert b.is_valid_transaction(tx_double_spend) == False
|
|
|
|
@pytest.mark.usefixtures('inputs')
|
|
def test_wrong_transaction_hash(self, b, user_public_key):
|
|
input_valid = b.get_owned_ids(user_public_key).pop()
|
|
tx_valid = b.create_transaction(user_public_key, 'b', input_valid, 'd')
|
|
|
|
# change the transaction hash
|
|
tx_valid.update({'id': 'abcd'})
|
|
with pytest.raises(exceptions.InvalidHash):
|
|
b.validate_transaction(tx_valid)
|
|
assert b.is_valid_transaction(tx_valid) == False
|
|
|
|
@pytest.mark.usefixtures('inputs')
|
|
def test_wrong_signature(self, b, user_public_key):
|
|
input_valid = b.get_owned_ids(user_public_key).pop()
|
|
tx_valid = b.create_transaction(user_public_key, 'b', input_valid, 'd')
|
|
|
|
wrong_private_key = '4fyvJe1aw2qHZ4UNRYftXK7JU7zy9bCqoU5ps6Ne3xrY'
|
|
|
|
tx_invalid_signed = b.sign_transaction(tx_valid, wrong_private_key)
|
|
with pytest.raises(exceptions.InvalidSignature):
|
|
b.validate_transaction(tx_invalid_signed)
|
|
assert b.is_valid_transaction(tx_invalid_signed) == False
|
|
|
|
def test_valid_create_transaction(self, b, user_public_key):
|
|
tx = b.create_transaction(b.me, user_public_key, None, 'CREATE')
|
|
tx_signed = b.sign_transaction(tx, b.me_private)
|
|
assert tx_signed == b.validate_transaction(tx_signed)
|
|
assert tx_signed == b.is_valid_transaction(tx_signed)
|
|
|
|
@pytest.mark.usefixtures('inputs')
|
|
def test_valid_non_create_transaction(self, b, user_public_key, user_private_key):
|
|
input_valid = b.get_owned_ids(user_public_key).pop()
|
|
tx_valid = b.create_transaction(user_public_key, 'b', input_valid, 'd')
|
|
|
|
tx_valid_signed = b.sign_transaction(tx_valid, user_private_key)
|
|
assert tx_valid_signed == b.validate_transaction(tx_valid_signed)
|
|
assert tx_valid_signed == b.is_valid_transaction(tx_valid_signed)
|
|
|
|
|
|
class TestBlockValidation(object):
|
|
|
|
def test_wrong_block_hash(self, b):
|
|
block = b.create_block([])
|
|
|
|
# change block hash
|
|
block.update({'id': 'abc'})
|
|
with pytest.raises(exceptions.InvalidHash) as excinfo:
|
|
b.validate_block(block)
|
|
|
|
@pytest.mark.skipif(reason='Separated tx validation from block creation.')
|
|
@pytest.mark.usefixtures('inputs')
|
|
def test_invalid_transactions_in_block(self, b, user_public_key, ):
|
|
# invalid transaction
|
|
valid_input = b.get_owned_ids(user_public_key).pop()
|
|
tx_invalid = b.create_transaction('a', 'b', valid_input, 'c')
|
|
|
|
block = b.create_block([tx_invalid])
|
|
assert invalid_transactions == [tx_invalid]
|
|
|
|
# create a block with invalid transactions
|
|
block = {
|
|
'timestamp': b.timestamp(),
|
|
'transactions': [tx_invalid],
|
|
'node_pubkey': b.me,
|
|
'voters': b.federation_nodes
|
|
}
|
|
|
|
block_data = b.serialize(block)
|
|
block_hash = hash_data(block_data)
|
|
block_signature = PrivateKey(b.me_private).sign(block_data)
|
|
|
|
block = {
|
|
'id': block_hash,
|
|
'block': block,
|
|
'signature': block_signature,
|
|
'votes': []
|
|
}
|
|
|
|
with pytest.raises(exceptions.TransactionOwnerError) as excinfo:
|
|
b.validate_block(block)
|
|
|
|
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([])
|
|
|
|
# change block hash
|
|
block.update({'id': 'abc'})
|
|
with pytest.raises(exceptions.InvalidHash):
|
|
b.validate_block(block)
|
|
|
|
@pytest.mark.usefixtures('inputs')
|
|
def test_valid_block(self, b, user_public_key, user_private_key):
|
|
# create valid transaction
|
|
input_valid = b.get_owned_ids(user_public_key).pop()
|
|
tx_valid = b.create_transaction(user_public_key, 'b', input_valid, 'd')
|
|
tx_valid_signed = b.sign_transaction(tx_valid, user_private_key)
|
|
|
|
# create valid block
|
|
block = b.create_block([tx_valid_signed])
|
|
|
|
assert block == b.validate_block(block)
|
|
assert b.is_valid_block(block)
|
|
|
|
|
|
class TestBigchainCrypto(object):
|
|
PRIVATE_VALUE = 64328150571824492670917070117568709277186368319388887463636481841106388379832
|
|
PUBLIC_VALUE_X = 48388170575736684074633245566225141536152842355597159440179742847497614196929
|
|
PUBLIC_VALUE_Y = 65233479152484407841598798165960909560839872511163322973341535484598825150846
|
|
|
|
PRIVATE_VALUE_B58 = 'AaAp4xBavbe6VGeQF2mWdSKNM1r6HfR2Z1tAY6aUkwdq'
|
|
PUBLIC_VALUE_COMPRESSED_B58 = 'ifEi3UuTDT4CqUUKiS5omgeDodhu2aRFHVp6LoahbEVe'
|
|
|
|
def test_private_key_encode(self):
|
|
private_value_base58 = PrivateKey.encode(self.PRIVATE_VALUE)
|
|
assert private_value_base58 == self.PRIVATE_VALUE_B58
|
|
|
|
def test_private_key_decode(self):
|
|
private_value = PrivateKey.decode(self.PRIVATE_VALUE_B58)
|
|
assert private_value == self.PRIVATE_VALUE
|
|
|
|
def test_public_key_encode(self):
|
|
public_value_compressed_base58 = PublicKey.encode(self.PUBLIC_VALUE_X, self.PUBLIC_VALUE_Y)
|
|
assert public_value_compressed_base58 == self.PUBLIC_VALUE_COMPRESSED_B58
|
|
|
|
def test_public_key_decode(self):
|
|
public_value_x, public_value_y = PublicKey.decode(self.PUBLIC_VALUE_COMPRESSED_B58)
|
|
assert public_value_x == self.PUBLIC_VALUE_X
|
|
assert public_value_y == self.PUBLIC_VALUE_Y
|
|
|
|
def test_sign_verify(self):
|
|
message = 'Hello World!'
|
|
public_key = PublicKey(self.PUBLIC_VALUE_COMPRESSED_B58)
|
|
private_key = PrivateKey(self.PRIVATE_VALUE_B58)
|
|
assert public_key.verify(message, private_key.sign(message)) == True
|
|
|
|
def test_generate_key_pair(self):
|
|
private_value_base58, public_value_compressed_base58 = generate_key_pair()
|
|
assert PrivateKey.encode(
|
|
PrivateKey.decode(private_value_base58)) == private_value_base58
|
|
assert PublicKey.encode(
|
|
*PublicKey.decode(public_value_compressed_base58)) == public_value_compressed_base58
|
|
|
|
|
|
class TestBigchainVoter(object):
|
|
|
|
def test_valid_block_voting(self, b):
|
|
# create queue and voter
|
|
q_new_block = mp.Queue()
|
|
voter = Voter(q_new_block)
|
|
|
|
genesis = b.create_genesis_block()
|
|
# create valid block
|
|
block = b.create_block([])
|
|
# assert block is valid
|
|
assert b.is_valid_block(block)
|
|
b.write_block(block, durability='hard')
|
|
|
|
# insert into queue
|
|
# FIXME: we disable this because the voter can currently vote more than one time for a block
|
|
# q_new_block.put(block)
|
|
|
|
# vote
|
|
voter.start()
|
|
# wait for vote to be written
|
|
time.sleep(1)
|
|
voter.kill()
|
|
|
|
# retrive block from bigchain
|
|
bigchain_block = r.table('bigchain').get(block['id']).run(b.conn)
|
|
|
|
# validate vote
|
|
assert len(bigchain_block['votes']) == 1
|
|
vote = bigchain_block['votes'][0]
|
|
|
|
assert vote['vote']['voting_for_block'] == block['id']
|
|
assert vote['vote']['previous_block'] == genesis['id']
|
|
assert vote['vote']['is_block_valid'] == True
|
|
assert vote['vote']['invalid_reason'] == None
|
|
assert vote['node_pubkey'] == b.me
|
|
assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) == True
|
|
|
|
def test_invalid_block_voting(self, b, user_public_key):
|
|
# create queue and voter
|
|
q_new_block = mp.Queue()
|
|
voter = Voter(q_new_block)
|
|
|
|
# create transaction
|
|
transaction = b.create_transaction(b.me, user_public_key, None, 'CREATE')
|
|
transaction_signed = b.sign_transaction(transaction, b.me_private)
|
|
|
|
genesis = b.create_genesis_block()
|
|
# create invalid block
|
|
block = b.create_block([transaction_signed])
|
|
# change transaction id to make it invalid
|
|
block['block']['transactions'][0]['id'] = 'abc'
|
|
assert b.is_valid_block(block) == False
|
|
b.write_block(block, durability='hard')
|
|
|
|
# insert into queue
|
|
# FIXME: we disable this because the voter can currently vote more than one time for a block
|
|
# q_new_block.put(block)
|
|
|
|
# vote
|
|
voter.start()
|
|
# wait for the vote to be written
|
|
time.sleep(1)
|
|
voter.kill()
|
|
|
|
|
|
# retrive block from bigchain
|
|
bigchain_block = r.table('bigchain').get(block['id']).run(b.conn)
|
|
|
|
# validate vote
|
|
assert len(bigchain_block['votes']) == 1
|
|
vote = bigchain_block['votes'][0]
|
|
|
|
assert vote['vote']['voting_for_block'] == block['id']
|
|
assert vote['vote']['previous_block'] == genesis['id']
|
|
assert vote['vote']['is_block_valid'] == False
|
|
assert vote['vote']['invalid_reason'] == None
|
|
assert vote['node_pubkey'] == b.me
|
|
assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) == True
|
|
|
|
def test_vote_creation_valid(self, b):
|
|
# create valid block
|
|
block = b.create_block([])
|
|
# retrieve vote
|
|
vote = b.vote(block, 'abc', True)
|
|
|
|
# assert vote is correct
|
|
assert vote['vote']['voting_for_block'] == block['id']
|
|
assert vote['vote']['previous_block'] == 'abc'
|
|
assert vote['vote']['is_block_valid'] == True
|
|
assert vote['vote']['invalid_reason'] == None
|
|
assert vote['node_pubkey'] == b.me
|
|
assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) == True
|
|
|
|
def test_vote_creation_invalid(self, b):
|
|
# create valid block
|
|
block = b.create_block([])
|
|
# retrieve vote
|
|
vote = b.vote(block, 'abc', False)
|
|
|
|
# assert vote is correct
|
|
assert vote['vote']['voting_for_block'] == block['id']
|
|
assert vote['vote']['previous_block'] == 'abc'
|
|
assert vote['vote']['is_block_valid'] == False
|
|
assert vote['vote']['invalid_reason'] == None
|
|
assert vote['node_pubkey'] == b.me
|
|
assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) == True
|
|
|
|
|
|
class TestBigchainBlock(object):
|
|
|
|
def test_by_assignee(self, b, user_public_key):
|
|
# create transactions and randomly assigne them
|
|
transactions = mp.Queue()
|
|
count_assigned_to_me = 0
|
|
for i in range(100):
|
|
tx = b.create_transaction(b.me, user_public_key, None, 'CREATE')
|
|
assignee = random.choice([b.me, 'aaa', 'bbb', 'ccc'])
|
|
if assignee == b.me:
|
|
count_assigned_to_me += 1
|
|
|
|
tx['assignee'] = assignee
|
|
transactions.put(tx)
|
|
transactions.put('stop')
|
|
|
|
# create a block instance
|
|
block = Block(transactions)
|
|
block.q_new_transaction = transactions
|
|
# filter the transactions
|
|
block.filter_by_assignee()
|
|
|
|
# check if the number of transactions assigned to the node is the same as the number in
|
|
# the queue minus 'stop'
|
|
assert block.q_tx_to_validate.qsize() - 1 == count_assigned_to_me
|
|
|
|
def test_validate_transactions(self, b, user_public_key):
|
|
# create transactions and randomly invalidate some of them by changing the hash
|
|
transactions = mp.Queue()
|
|
count_valid = 0
|
|
for i in range(100):
|
|
valid = random.choice([True, False])
|
|
tx = b.create_transaction(b.me, user_public_key, None, 'CREATE')
|
|
tx = b.sign_transaction(tx, b.me_private)
|
|
if not valid:
|
|
tx['id'] = 'a' * 64
|
|
else:
|
|
count_valid += 1
|
|
transactions.put(tx)
|
|
transactions.put('stop')
|
|
|
|
# create a block instance
|
|
block = Block(transactions)
|
|
block.q_tx_to_validate = transactions
|
|
# validate transactions
|
|
block.validate_transactions()
|
|
|
|
# check if the number of valid transactions
|
|
assert block.q_tx_validated.qsize() - 1 == count_valid
|
|
assert block.q_tx_delete.qsize() - 1 == 100
|
|
|
|
def test_create_block(self, b, user_public_key):
|
|
# create transactions
|
|
transactions = mp.Queue()
|
|
for i in range(100):
|
|
tx = b.create_transaction(b.me, user_public_key, None, 'CREATE')
|
|
tx = b.sign_transaction(tx, b.me_private)
|
|
transactions.put(tx)
|
|
transactions.put('stop')
|
|
|
|
# create a block instance
|
|
block = Block(transactions)
|
|
block.q_tx_validated = transactions
|
|
# create blocks
|
|
block.create_blocks()
|
|
|
|
# check if the number of valid transactions
|
|
assert block.q_block.qsize() - 1 == 1
|
|
|
|
def test_write_block(self, b, user_public_key):
|
|
# create transactions
|
|
transactions = []
|
|
blocks = mp.Queue()
|
|
for i in range(100):
|
|
tx = b.create_transaction(b.me, user_public_key, None, 'CREATE')
|
|
tx = b.sign_transaction(tx, b.me_private)
|
|
transactions.append(tx)
|
|
|
|
# create block
|
|
block = b.create_block(transactions)
|
|
blocks.put(block)
|
|
blocks.put('stop')
|
|
|
|
# create a block instance
|
|
block = Block(transactions)
|
|
block.q_block = blocks
|
|
|
|
# make sure that we only have the genesis block in bigchain
|
|
r.table('bigchain').delete().run(b.conn)
|
|
b.create_genesis_block()
|
|
|
|
# write blocks
|
|
block.write_blocks()
|
|
# lets give it some time for the block to be written
|
|
time.sleep(1)
|
|
|
|
# check if the number of blocks in bigchain increased
|
|
assert r.table('bigchain').count() == 2
|
|
|
|
def test_delete_transactions(self, b, user_public_key):
|
|
# make sure that there are no transactions in the backlog
|
|
r.table('backlog').delete().run(b.conn)
|
|
|
|
# create and write transactions to the backlog
|
|
transactions = mp.Queue()
|
|
for i in range(100):
|
|
tx = b.create_transaction(b.me, user_public_key, None, 'CREATE')
|
|
tx = b.sign_transaction(tx, b.me_private)
|
|
b.write_transaction(tx)
|
|
transactions.put(tx['id'])
|
|
transactions.put('stop')
|
|
|
|
# create a block instance
|
|
block = Block(transactions)
|
|
block.q_tx_delete = transactions
|
|
|
|
# make sure that there are transactions on the backlog
|
|
r.table('backlog').count().run(b.conn) == 100
|
|
|
|
# run the delete process
|
|
block.delete_transactions()
|
|
# give the db time
|
|
time.sleep(1)
|
|
|
|
# check if all transactions were deleted from the backlog
|
|
assert r.table('backlog').count() == 0
|
|
|
|
def test_bootstrap(self, b, user_public_key):
|
|
# make sure that there are no transactions in the backlog
|
|
r.table('backlog').delete().run(b.conn)
|
|
|
|
# create and write transactions to the backlog
|
|
for i in range(100):
|
|
tx = b.create_transaction(b.me, user_public_key, None, 'CREATE')
|
|
tx = b.sign_transaction(tx, b.me_private)
|
|
b.write_transaction(tx)
|
|
|
|
# create a block instance
|
|
block = Block(None)
|
|
|
|
# run bootstrap
|
|
initial_results = block.bootstrap()
|
|
|
|
# we should have gotten a queue with 100 results
|
|
assert initial_results.qsize() - 1 == 100
|
|
|
|
def test_start(self, b, user_public_key):
|
|
# start with 100 transactions in the backlog and 100 in the changefeed
|
|
|
|
# make sure that there are no transactions in the backlog
|
|
r.table('backlog').delete().run(b.conn)
|
|
|
|
# create and write transactions to the backlog
|
|
for i in range(100):
|
|
tx = b.create_transaction(b.me, user_public_key, None, 'CREATE')
|
|
tx = b.sign_transaction(tx, b.me_private)
|
|
b.write_transaction(tx)
|
|
|
|
# create 100 more transactions to emulate the changefeed
|
|
new_transactions = mp.Queue()
|
|
for i in range(100):
|
|
tx = b.create_transaction(b.me, user_public_key, None, 'CREATE')
|
|
tx = b.sign_transaction(tx, b.me_private)
|
|
b.write_transaction(tx)
|
|
new_transactions.put(tx)
|
|
new_transactions.put('stop')
|
|
|
|
# create a block instance
|
|
block = Block(new_transactions)
|
|
|
|
# start the block processes
|
|
block.start()
|
|
|
|
assert new_transactions.qsize() == 0
|
|
assert r.table('backlog').count() == 0
|
|
assert r.table('bigchain').count() == 2
|
|
|
|
def test_empty_queues(self, b):
|
|
# create empty queue
|
|
new_transactions = mp.Queue()
|
|
|
|
# create block instance
|
|
block = Block(new_transactions)
|
|
|
|
# create block_process
|
|
p_block = mp.Process(target=block.start)
|
|
|
|
# start block process
|
|
p_block.start()
|
|
|
|
# wait for 6 seconds to give it time for an empty queue exception to occur
|
|
time.sleep(6)
|
|
|
|
# send the poison pill
|
|
new_transactions.put('stop')
|
|
|
|
# join the process
|
|
p_block.join()
|
|
|
|
def test_duplicated_transactions(self):
|
|
pytest.skip('We may have duplicates in the initial_results and changefeed')
|
|
|