mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge pull request #1377 from bigchaindb/bug/1343/double-spent-critical-on-voting
Bug/1343/double spent critical on voting
This commit is contained in:
commit
b90766f2c5
@ -324,43 +324,57 @@ class Bigchain(object):
|
|||||||
def get_spent(self, txid, output):
|
def get_spent(self, txid, output):
|
||||||
"""Check if a `txid` was already used as an input.
|
"""Check if a `txid` was already used as an input.
|
||||||
|
|
||||||
A transaction can be used as an input for another transaction. Bigchain needs to make sure that a
|
A transaction can be used as an input for another transaction. Bigchain
|
||||||
given `txid` is only used once.
|
needs to make sure that a given `(txid, output)` is only used once.
|
||||||
|
|
||||||
|
This method will check if the `(txid, output)` has already been
|
||||||
|
spent in a transaction that is in either the `VALID`, `UNDECIDED` or
|
||||||
|
`BACKLOG` state.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
txid (str): The id of the transaction
|
txid (str): The id of the transaction
|
||||||
output (num): the index of the output in the respective transaction
|
output (num): the index of the output in the respective transaction
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The transaction (Transaction) that used the `txid` as an input else
|
The transaction (Transaction) that used the `(txid, output)` as an
|
||||||
`None`
|
input else `None`
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
CriticalDoubleSpend: If the given `(txid, output)` was spent in
|
||||||
|
more than one valid transaction.
|
||||||
"""
|
"""
|
||||||
# checks if an input was already spent
|
# checks if an input was already spent
|
||||||
# checks if the bigchain has any transaction with input {'txid': ...,
|
# checks if the bigchain has any transaction with input {'txid': ...,
|
||||||
# 'output': ...}
|
# 'output': ...}
|
||||||
transactions = list(backend.query.get_spent(self.connection, txid, output))
|
transactions = list(backend.query.get_spent(self.connection, txid,
|
||||||
|
output))
|
||||||
|
|
||||||
# a transaction_id should have been spent at most one time
|
# a transaction_id should have been spent at most one time
|
||||||
if transactions:
|
# determine if these valid transactions appear in more than one valid
|
||||||
# determine if these valid transactions appear in more than one valid block
|
# block
|
||||||
num_valid_transactions = 0
|
num_valid_transactions = 0
|
||||||
|
non_invalid_transactions = []
|
||||||
for transaction in transactions:
|
for transaction in transactions:
|
||||||
# ignore invalid blocks
|
# ignore transactions in invalid blocks
|
||||||
# FIXME: Isn't there a faster solution than doing I/O again?
|
# FIXME: Isn't there a faster solution than doing I/O again?
|
||||||
if self.get_transaction(transaction['id']):
|
_, status = self.get_transaction(transaction['id'],
|
||||||
|
include_status=True)
|
||||||
|
if status == self.TX_VALID:
|
||||||
num_valid_transactions += 1
|
num_valid_transactions += 1
|
||||||
|
# `txid` can only have been spent in at most on valid block.
|
||||||
if num_valid_transactions > 1:
|
if num_valid_transactions > 1:
|
||||||
raise core_exceptions.CriticalDoubleSpend(
|
raise core_exceptions.CriticalDoubleSpend(
|
||||||
'`{}` was spent more than once. There is a problem'
|
'`{}` was spent more than once. There is a problem'
|
||||||
' with the chain'.format(txid))
|
' with the chain'.format(txid))
|
||||||
|
# if its not and invalid transaction
|
||||||
|
if status is not None:
|
||||||
|
non_invalid_transactions.append(transaction)
|
||||||
|
|
||||||
if num_valid_transactions:
|
if non_invalid_transactions:
|
||||||
return Transaction.from_dict(transactions[0])
|
return Transaction.from_dict(non_invalid_transactions[0])
|
||||||
else:
|
|
||||||
# all queried transactions were invalid
|
# Either no transaction was returned spending the `(txid, output)` as
|
||||||
return None
|
# input or the returned transactions are not valid.
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_outputs(self, owner):
|
def get_outputs(self, owner):
|
||||||
"""Retrieve a list of links to transaction outputs for a given public
|
"""Retrieve a list of links to transaction outputs for a given public
|
||||||
|
@ -187,6 +187,11 @@ class Block(object):
|
|||||||
if not self.is_signature_valid():
|
if not self.is_signature_valid():
|
||||||
raise InvalidSignature('Invalid block signature')
|
raise InvalidSignature('Invalid block signature')
|
||||||
|
|
||||||
|
# Check that the block contains no duplicated transactions
|
||||||
|
txids = [tx.id for tx in self.transactions]
|
||||||
|
if len(txids) != len(set(txids)):
|
||||||
|
raise DuplicateTransaction('Block has duplicate transaction')
|
||||||
|
|
||||||
def _validate_block_transactions(self, bigchain):
|
def _validate_block_transactions(self, bigchain):
|
||||||
"""Validate Block transactions.
|
"""Validate Block transactions.
|
||||||
|
|
||||||
@ -196,10 +201,6 @@ class Block(object):
|
|||||||
Raises:
|
Raises:
|
||||||
ValidationError: If an invalid transaction is found
|
ValidationError: If an invalid transaction is found
|
||||||
"""
|
"""
|
||||||
txids = [tx.id for tx in self.transactions]
|
|
||||||
if len(txids) != len(set(txids)):
|
|
||||||
raise DuplicateTransaction('Block has duplicate transaction')
|
|
||||||
|
|
||||||
for tx in self.transactions:
|
for tx in self.transactions:
|
||||||
# If a transaction is not valid, `validate_transactions` will
|
# If a transaction is not valid, `validate_transactions` will
|
||||||
# throw an an exception and block validation will be canceled.
|
# throw an an exception and block validation will be canceled.
|
||||||
|
@ -111,6 +111,18 @@ def test_validate_block_with_invalid_id(b):
|
|||||||
assert invalid_dummy_tx == [vote_obj.invalid_dummy_tx]
|
assert invalid_dummy_tx == [vote_obj.invalid_dummy_tx]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.genesis
|
||||||
|
def test_validate_block_with_duplicated_transactions(b):
|
||||||
|
from bigchaindb.pipelines import vote
|
||||||
|
|
||||||
|
tx = dummy_tx(b)
|
||||||
|
block = b.create_block([tx, tx]).to_dict()
|
||||||
|
|
||||||
|
vote_obj = vote.Vote()
|
||||||
|
block_id, invalid_dummy_tx = vote_obj.validate_block(block)
|
||||||
|
assert invalid_dummy_tx == [vote_obj.invalid_dummy_tx]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.genesis
|
@pytest.mark.genesis
|
||||||
def test_validate_block_with_invalid_signature(b):
|
def test_validate_block_with_invalid_signature(b):
|
||||||
from bigchaindb.pipelines import vote
|
from bigchaindb.pipelines import vote
|
||||||
|
@ -152,4 +152,4 @@ class TestBlockModel(object):
|
|||||||
tx = Transaction.create([b.me], [([b.me], 1)])
|
tx = Transaction.create([b.me], [([b.me], 1)])
|
||||||
block = b.create_block([tx, tx])
|
block = b.create_block([tx, tx])
|
||||||
with raises(DuplicateTransaction):
|
with raises(DuplicateTransaction):
|
||||||
block._validate_block_transactions(b)
|
block._validate_block(b)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user