mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
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:
parent
73ed9c4f75
commit
81e70951d4
@ -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):
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user