check if fulfillments are in a valid block (#629)

* check if fulfillments are in a valid block

* documentation

* fix almost all tests where tx validity is checked before writing to block

* move import
This commit is contained in:
Ryan Henderson 2016-10-10 09:41:49 +02:00 committed by GitHub
parent 73ed9c4f75
commit 81e70951d4
5 changed files with 150 additions and 76 deletions

View File

@ -188,7 +188,8 @@ class Bigchain(object):
return self.validate_transaction(transaction)
except (ValueError, exceptions.OperationError, exceptions.TransactionDoesNotExist,
exceptions.TransactionOwnerError, exceptions.DoubleSpend,
exceptions.InvalidHash, exceptions.InvalidSignature):
exceptions.InvalidHash, exceptions.InvalidSignature,
exceptions.FulfillmentNotInValidBlock):
return False
def get_transaction(self, txid, include_status=False):

View File

@ -1,7 +1,8 @@
from bigchaindb_common.crypto import hash_data, VerifyingKey, SigningKey
from bigchaindb_common.exceptions import (InvalidHash, InvalidSignature,
OperationError, DoubleSpend,
TransactionDoesNotExist)
TransactionDoesNotExist,
FulfillmentNotInValidBlock)
from bigchaindb_common.transaction import Transaction
from bigchaindb_common.util import gen_timestamp, serialize
@ -45,11 +46,18 @@ class Transaction(Transaction):
for ffill in self.fulfillments:
input_txid = ffill.tx_input.txid
input_cid = ffill.tx_input.cid
input_tx = bigchain.get_transaction(input_txid)
input_tx, status = bigchain.\
get_transaction(input_txid, include_status=True)
if input_tx is None:
raise TransactionDoesNotExist("input `{}` doesn't exist"
.format(input_txid))
if status != bigchain.TX_VALID:
raise FulfillmentNotInValidBlock(
'input `{}` does not exist in a valid block'.format(
input_txid))
spent = bigchain.get_spent(input_txid, ffill.tx_input.cid)
if spent and spent.id != self.id:
raise DoubleSpend('input `{}` was already spent'

View File

@ -11,7 +11,9 @@ import rethinkdb as r
from bigchaindb import Bigchain
from bigchaindb.db import get_conn
from bigchaindb_common import crypto
USER2_SK, USER2_VK = crypto.generate_key_pair()
@pytest.fixture(autouse=True)
def restore_config(request, node_config):
@ -104,11 +106,12 @@ def inputs(user_vk):
# 1. create the genesis block
b = Bigchain()
try:
b.create_genesis_block()
g = b.create_genesis_block()
except GenesisBlockAlreadyExistsError:
pass
# 2. create block with transactions for `USER` to spend
# 2. create blocks with transactions for `USER` to spend
prev_block_id = g.id
for block in range(4):
transactions = [
Transaction.create(
@ -117,3 +120,46 @@ def inputs(user_vk):
]
block = b.create_block(transactions)
b.write_block(block, durability='hard')
# 3. vote the blocks valid, so that the inputs are valid
vote = b.vote(block.id, prev_block_id, True)
prev_block_id = block.id
b.write_vote(vote)
@pytest.fixture
def user2_sk():
return USER2_SK
@pytest.fixture
def user2_vk():
return USER2_VK
@pytest.fixture
def inputs_shared(user_vk, user2_vk):
from bigchaindb.models import Transaction
from bigchaindb_common.exceptions import GenesisBlockAlreadyExistsError
# 1. create the genesis block
b = Bigchain()
try:
g = b.create_genesis_block()
except GenesisBlockAlreadyExistsError:
pass
# 2. create blocks with transactions for `USER` to spend
prev_block_id = g.id
for block in range(4):
transactions = [
Transaction.create(
[b.me], [user_vk, user2_vk], payload={'i': i}).sign([b.me_private])
for i in range(10)
]
block = b.create_block(transactions)
b.write_block(block, durability='hard')
# 3. vote the blocks valid, so that the inputs are valid
vote = b.vote(block.id, prev_block_id, True)
prev_block_id = block.id
b.write_vote(vote)

View File

@ -1,3 +1,5 @@
from time import sleep
import pytest
@ -556,6 +558,7 @@ class TestTransactionValidation(object):
with pytest.raises(InvalidSignature):
b.validate_transaction(tx)
@pytest.mark.usefixtures('inputs')
def test_non_create_double_spend(self, b, signed_create_tx,
signed_transfer_tx):
from bigchaindb_common.exceptions import DoubleSpend
@ -563,10 +566,20 @@ class TestTransactionValidation(object):
block1 = b.create_block([signed_create_tx])
b.write_block(block1)
# vote block valid
vote = b.vote(block1.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
b.write_transaction(signed_transfer_tx)
block = b.create_block([signed_transfer_tx])
b.write_block(block, durability='hard')
# vote block valid
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
sleep(1)
signed_transfer_tx.timestamp = 123
# FIXME: https://github.com/bigchaindb/bigchaindb/issues/592
with pytest.raises(DoubleSpend):
@ -595,6 +608,32 @@ class TestTransactionValidation(object):
# bigchain
assert transfer_tx == b.validate_transaction(transfer_tx)
@pytest.mark.usefixtures('inputs')
def test_fulfillment_not_in_valid_block(self, b, user_vk, user_sk):
from bigchaindb.models import Transaction
from bigchaindb_common.exceptions import FulfillmentNotInValidBlock
input_tx = b.get_owned_ids(user_vk).pop()
input_tx = b.get_transaction(input_tx.txid)
inputs = input_tx.to_inputs()
# create a transaction that's valid but not in a voted valid block
transfer_tx = Transaction.transfer(inputs, [user_vk])
transfer_tx = transfer_tx.sign([user_sk])
assert transfer_tx == b.validate_transaction(transfer_tx)
# create block
block = b.create_block([transfer_tx])
b.write_block(block, durability='hard')
# create transaction with the undecided input
tx_invalid = Transaction.transfer(transfer_tx.to_inputs(), [user_vk])
tx_invalid = tx_invalid.sign([user_sk])
with pytest.raises(FulfillmentNotInValidBlock):
b.validate_transaction(tx_invalid)
class TestBlockValidation(object):
@pytest.mark.skipif(reason='Separated tx validation from block creation.')
@ -695,67 +734,52 @@ class TestMultipleInputs(object):
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
@pytest.mark.usefixtures('inputs')
def test_transfer_single_owners_multiple_inputs(self, b, user_sk, user_vk):
from bigchaindb_common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_vk = crypto.generate_key_pair()
# TODO: Make this a fixture
transactions = []
for i in range(3):
tx = Transaction.create([user_vk], [user_vk])
tx = tx.sign([user_sk])
transactions.append(tx)
b.write_transaction(tx)
block = b.create_block(transactions)
b.write_block(block, durability='hard')
# get inputs
owned_inputs = b.get_owned_ids(user_vk)
input_txs = [b.get_transaction(tx_link.txid) for tx_link
in owned_inputs]
inputs = sum([input_tx.to_inputs() for input_tx in input_txs], [])
tx = Transaction.transfer(inputs, 3 * [[user_vk]])
tx = Transaction.transfer(inputs, len(inputs) * [[user_vk]])
tx = tx.sign([user_sk])
assert b.validate_transaction(tx) == tx
assert len(tx.fulfillments) == 3
assert len(tx.conditions) == 3
assert len(tx.fulfillments) == len(inputs)
assert len(tx.conditions) == len(inputs)
@pytest.mark.usefixtures('inputs')
def test_transfer_single_owners_single_input_from_multiple_outputs(self, b,
user_sk,
user_vk):
import random
from bigchaindb_common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_vk = crypto.generate_key_pair()
transactions = []
for i in range(3):
payload = {'somedata': random.randint(0, 255)}
tx = Transaction.create([user_vk], [user_vk], None, 'CREATE', payload)
tx = tx.sign([user_sk])
transactions.append(tx)
b.write_transaction(tx)
block = b.create_block(transactions)
b.write_block(block, durability='hard')
# get inputs
owned_inputs = b.get_owned_ids(user_vk)
input_txs = [b.get_transaction(tx_link.txid) for tx_link
in owned_inputs]
inputs = sum([input_tx.to_inputs() for input_tx in input_txs], [])
tx = Transaction.transfer(inputs, 3 * [[user2_vk]])
tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk]])
tx = tx.sign([user_sk])
# create block with the transaction
block = b.create_block([tx])
b.write_block(block, durability='hard')
# vote block valid
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
# get inputs from user2
owned_inputs = b.get_owned_ids(user2_vk)
assert len(owned_inputs) == 3
assert len(owned_inputs) == len(inputs)
# create a transaction with a single input from a multiple output transaction
tx_link = owned_inputs.pop()
@ -787,40 +811,38 @@ class TestMultipleInputs(object):
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
@pytest.mark.usefixtures('inputs')
def test_single_owner_before_multiple_owners_after_multiple_inputs(self, b,
user_sk,
user_vk):
import random
from bigchaindb_common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_vk = crypto.generate_key_pair()
user3_sk, user3_vk = crypto.generate_key_pair()
transactions = []
for i in range(3):
payload = {'somedata': random.randint(0, 255)}
tx = Transaction.create([user_vk], [user_vk], None, 'CREATE',
payload)
tx = tx.sign([user_sk])
transactions.append(tx)
b.write_transaction(tx)
block = b.create_block(transactions)
b.write_block(block, durability='hard')
owned_inputs = b.get_owned_ids(user_vk)
input_txs = [b.get_transaction(tx_link.txid) for tx_link
in owned_inputs]
inputs = sum([input_tx.to_inputs() for input_tx in input_txs], [])
tx = Transaction.transfer(inputs, 3 * [[user2_vk, user3_vk]])
tx = Transaction.transfer(inputs, len(inputs) * [[user2_vk, user3_vk]])
tx = tx.sign([user_sk])
# create block with the transaction
block = b.create_block([tx])
b.write_block(block, durability='hard')
# vote block valid
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
# validate transaction
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 3
assert len(tx.conditions) == 3
assert len(tx.fulfillments) == len(inputs)
assert len(tx.conditions) == len(inputs)
@pytest.mark.usefixtures('inputs')
def test_multiple_owners_before_single_owner_after_single_input(self, b,
user_sk,
user_vk):
@ -835,6 +857,10 @@ class TestMultipleInputs(object):
block = b.create_block([tx])
b.write_block(block, durability='hard')
# vote block valid
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
owned_input = b.get_owned_ids(user_vk).pop()
input_tx = b.get_transaction(owned_input.txid)
inputs = input_tx.to_inputs()
@ -847,23 +873,15 @@ class TestMultipleInputs(object):
assert len(transfer_tx.fulfillments) == 1
assert len(transfer_tx.conditions) == 1
@pytest.mark.usefixtures('inputs_shared')
def test_multiple_owners_before_single_owner_after_multiple_inputs(self, b,
user_sk,
user_vk):
user_sk, user_vk, user2_vk, user2_sk):
from bigchaindb_common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_vk = crypto.generate_key_pair()
# create a new users
user3_sk, user3_vk = crypto.generate_key_pair()
transactions = []
for i in range(3):
tx = Transaction.create([b.me], [user_vk, user2_vk])
tx = tx.sign([b.me_private])
transactions.append(tx)
block = b.create_block(transactions)
b.write_block(block, durability='hard')
tx_links = b.get_owned_ids(user_vk)
inputs = sum([b.get_transaction(tx_link.txid).to_inputs() for tx_link
in tx_links], [])
@ -872,9 +890,10 @@ class TestMultipleInputs(object):
tx = tx.sign([user_sk, user2_sk])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 3
assert len(tx.conditions) == 3
assert len(tx.fulfillments) == len(inputs)
assert len(tx.conditions) == len(inputs)
@pytest.mark.usefixtures('inputs')
def test_multiple_owners_before_multiple_owners_after_single_input(self, b,
user_sk,
user_vk):
@ -890,6 +909,11 @@ class TestMultipleInputs(object):
block = b.create_block([tx])
b.write_block(block, durability='hard')
# vote block valid
vote = b.vote(block.id, b.get_last_voted_block().id, True)
b.write_vote(vote)
# get input
tx_link = b.get_owned_ids(user_vk).pop()
tx_input = b.get_transaction(tx_link.txid).to_inputs()
@ -900,25 +924,17 @@ class TestMultipleInputs(object):
assert len(tx.fulfillments) == 1
assert len(tx.conditions) == 1
def test_multiple_owners_before_multiple_owners_after_multiple_inputs(self,
b,
user_sk,
user_vk):
@pytest.mark.usefixtures('inputs_shared')
def test_multiple_owners_before_multiple_owners_after_multiple_inputs(self, b,
user_sk, user_vk,
user2_sk, user2_vk):
from bigchaindb_common import crypto
from bigchaindb.models import Transaction
user2_sk, user2_vk = crypto.generate_key_pair()
# create a new users
user3_sk, user3_vk = crypto.generate_key_pair()
user4_sk, user4_vk = crypto.generate_key_pair()
transactions = []
for i in range(3):
tx = Transaction.create([b.me], [user_vk, user2_vk])
tx = tx.sign([b.me_private])
transactions.append(tx)
block = b.create_block(transactions)
b.write_block(block, durability='hard')
tx_links = b.get_owned_ids(user_vk)
inputs = sum([b.get_transaction(tx_link.txid).to_inputs() for tx_link
in tx_links], [])
@ -927,8 +943,8 @@ class TestMultipleInputs(object):
tx = tx.sign([user_sk, user2_sk])
assert b.is_valid_transaction(tx) == tx
assert len(tx.fulfillments) == 3
assert len(tx.conditions) == 3
assert len(tx.fulfillments) == len(inputs)
assert len(tx.conditions) == len(inputs)
def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_vk):
from bigchaindb_common import crypto

View File

@ -1,3 +1,5 @@
import time
from unittest.mock import patch
import rethinkdb as r
@ -285,9 +287,10 @@ def test_valid_block_voting_with_transfer_transactions(monkeypatch, b):
vote_pipeline = vote.create_pipeline()
vote_pipeline.setup(indata=inpipe, outdata=outpipe)
inpipe.put(block.to_dict())
inpipe.put(block2.to_dict())
vote_pipeline.start()
inpipe.put(block.to_dict())
time.sleep(1)
inpipe.put(block2.to_dict())
vote_out = outpipe.get()
vote2_out = outpipe.get()
vote_pipeline.terminate()