mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-06-28 00:27:45 +02:00
Merge pull request #1208 from bigchaindb/is_new_transaction
Bigchaindb.is_new_transaction, fix double inclusion in Vote pipeline
This commit is contained in:
commit
4a5a5566e7
|
@ -212,13 +212,6 @@ def get_block(conn, block_id):
|
||||||
projection={'_id': False}))
|
projection={'_id': False}))
|
||||||
|
|
||||||
|
|
||||||
@register_query(MongoDBConnection)
|
|
||||||
def has_transaction(conn, transaction_id):
|
|
||||||
return bool(conn.run(
|
|
||||||
conn.collection('bigchain')
|
|
||||||
.find_one({'block.transactions.id': transaction_id})))
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(MongoDBConnection)
|
@register_query(MongoDBConnection)
|
||||||
def count_blocks(conn):
|
def count_blocks(conn):
|
||||||
return conn.run(
|
return conn.run(
|
||||||
|
|
|
@ -211,20 +211,6 @@ def get_block(connection, block_id):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@singledispatch
|
|
||||||
def has_transaction(connection, transaction_id):
|
|
||||||
"""Check if a transaction exists in the bigchain table.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
transaction_id (str): the id of the transaction to check.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
``True`` if the transaction exists, ``False`` otherwise.
|
|
||||||
"""
|
|
||||||
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
@singledispatch
|
@singledispatch
|
||||||
def count_blocks(connection):
|
def count_blocks(connection):
|
||||||
"""Count the number of blocks in the bigchain table.
|
"""Count the number of blocks in the bigchain table.
|
||||||
|
|
|
@ -158,13 +158,6 @@ def get_block(connection, block_id):
|
||||||
return connection.run(r.table('bigchain').get(block_id))
|
return connection.run(r.table('bigchain').get(block_id))
|
||||||
|
|
||||||
|
|
||||||
@register_query(RethinkDBConnection)
|
|
||||||
def has_transaction(connection, transaction_id):
|
|
||||||
return bool(connection.run(
|
|
||||||
r.table('bigchain', read_mode=READ_MODE)
|
|
||||||
.get_all(transaction_id, index='transaction_id').count()))
|
|
||||||
|
|
||||||
|
|
||||||
@register_query(RethinkDBConnection)
|
@register_query(RethinkDBConnection)
|
||||||
def count_blocks(connection):
|
def count_blocks(connection):
|
||||||
return connection.run(
|
return connection.run(
|
||||||
|
|
|
@ -187,6 +187,22 @@ class Bigchain(object):
|
||||||
exceptions.TransactionNotInValidBlock, exceptions.AmountError):
|
exceptions.TransactionNotInValidBlock, exceptions.AmountError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def is_new_transaction(self, txid, exclude_block_id=None):
|
||||||
|
"""
|
||||||
|
Return True if the transaction does not exist in any
|
||||||
|
VALID or UNDECIDED block. Return False otherwise.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
txid (str): Transaction ID
|
||||||
|
exclude_block_id (str): Exclude block from search
|
||||||
|
"""
|
||||||
|
block_statuses = self.get_blocks_status_containing_tx(txid)
|
||||||
|
block_statuses.pop(exclude_block_id, None)
|
||||||
|
for status in block_statuses.values():
|
||||||
|
if status != self.BLOCK_INVALID:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def get_block(self, block_id, include_status=False):
|
def get_block(self, block_id, include_status=False):
|
||||||
"""Get the block with the specified `block_id` (and optionally its status)
|
"""Get the block with the specified `block_id` (and optionally its status)
|
||||||
|
|
||||||
|
@ -534,9 +550,6 @@ class Bigchain(object):
|
||||||
|
|
||||||
return backend.query.write_block(self.connection, block)
|
return backend.query.write_block(self.connection, block)
|
||||||
|
|
||||||
def transaction_exists(self, transaction_id):
|
|
||||||
return backend.query.has_transaction(self.connection, transaction_id)
|
|
||||||
|
|
||||||
def prepare_genesis_block(self):
|
def prepare_genesis_block(self):
|
||||||
"""Prepare a genesis block."""
|
"""Prepare a genesis block."""
|
||||||
|
|
||||||
|
|
|
@ -67,28 +67,19 @@ class BlockPipeline:
|
||||||
AmountError):
|
AmountError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if self.bigchain.transaction_exists(tx.id):
|
# If transaction is in any VALID or UNDECIDED block we
|
||||||
# if the transaction already exists, we must check whether
|
# should not include it again
|
||||||
# it's in a valid or undecided block
|
if not self.bigchain.is_new_transaction(tx.id):
|
||||||
tx, status = self.bigchain.get_transaction(tx.id,
|
|
||||||
include_status=True)
|
|
||||||
if status == self.bigchain.TX_VALID \
|
|
||||||
or status == self.bigchain.TX_UNDECIDED:
|
|
||||||
# if the tx is already in a valid or undecided block,
|
|
||||||
# then it no longer should be in the backlog, or added
|
|
||||||
# to a new block. We can delete and drop it.
|
|
||||||
self.bigchain.delete_transaction(tx.id)
|
|
||||||
return None
|
|
||||||
|
|
||||||
tx_validated = self.bigchain.is_valid_transaction(tx)
|
|
||||||
if tx_validated:
|
|
||||||
return tx
|
|
||||||
else:
|
|
||||||
# if the transaction is not valid, remove it from the
|
|
||||||
# backlog
|
|
||||||
self.bigchain.delete_transaction(tx.id)
|
self.bigchain.delete_transaction(tx.id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# If transaction is not valid it should not be included
|
||||||
|
if not self.bigchain.is_valid_transaction(tx):
|
||||||
|
self.bigchain.delete_transaction(tx.id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return tx
|
||||||
|
|
||||||
def create(self, tx, timeout=False):
|
def create(self, tx, timeout=False):
|
||||||
"""Create a block.
|
"""Create a block.
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,8 @@ class Vote:
|
||||||
yield tx, block_id, num_tx
|
yield tx, block_id, num_tx
|
||||||
|
|
||||||
def validate_tx(self, tx, block_id, num_tx):
|
def validate_tx(self, tx, block_id, num_tx):
|
||||||
"""Validate a transaction.
|
"""Validate a transaction. Transaction must also not be in any VALID
|
||||||
|
block.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tx (dict): the transaction to validate
|
tx (dict): the transaction to validate
|
||||||
|
@ -109,7 +110,12 @@ class Vote:
|
||||||
Three values are returned, the validity of the transaction,
|
Three values are returned, the validity of the transaction,
|
||||||
``block_id``, ``num_tx``.
|
``block_id``, ``num_tx``.
|
||||||
"""
|
"""
|
||||||
return bool(self.bigchain.is_valid_transaction(tx)), block_id, num_tx
|
new = self.bigchain.is_new_transaction(tx.id, exclude_block_id=block_id)
|
||||||
|
if not new:
|
||||||
|
return False, block_id, num_tx
|
||||||
|
|
||||||
|
valid = bool(self.bigchain.is_valid_transaction(tx))
|
||||||
|
return valid, block_id, num_tx
|
||||||
|
|
||||||
def vote(self, tx_validity, block_id, num_tx):
|
def vote(self, tx_validity, block_id, num_tx):
|
||||||
"""Collect the validity of transactions and cast a vote when ready.
|
"""Collect the validity of transactions and cast a vote when ready.
|
||||||
|
|
|
@ -248,19 +248,6 @@ def test_get_block(signed_create_tx):
|
||||||
assert block_db == block.to_dict()
|
assert block_db == block.to_dict()
|
||||||
|
|
||||||
|
|
||||||
def test_has_transaction(signed_create_tx):
|
|
||||||
from bigchaindb.backend import connect, query
|
|
||||||
from bigchaindb.models import Block
|
|
||||||
conn = connect()
|
|
||||||
|
|
||||||
# create and insert block
|
|
||||||
block = Block(transactions=[signed_create_tx])
|
|
||||||
conn.db.bigchain.insert_one(block.to_dict())
|
|
||||||
|
|
||||||
assert query.has_transaction(conn, signed_create_tx.id)
|
|
||||||
assert query.has_transaction(conn, 'aaa') is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_count_blocks(signed_create_tx):
|
def test_count_blocks(signed_create_tx):
|
||||||
from bigchaindb.backend import connect, query
|
from bigchaindb.backend import connect, query
|
||||||
from bigchaindb.models import Block
|
from bigchaindb.models import Block
|
||||||
|
|
|
@ -29,7 +29,6 @@ def test_schema(schema_func_name, args_qty):
|
||||||
('get_votes_by_block_id', 1),
|
('get_votes_by_block_id', 1),
|
||||||
('write_block', 1),
|
('write_block', 1),
|
||||||
('get_block', 1),
|
('get_block', 1),
|
||||||
('has_transaction', 1),
|
|
||||||
('write_vote', 1),
|
('write_vote', 1),
|
||||||
('get_last_voted_block', 1),
|
('get_last_voted_block', 1),
|
||||||
('get_unvoted_blocks', 1),
|
('get_unvoted_blocks', 1),
|
||||||
|
|
|
@ -1240,3 +1240,40 @@ def test_transaction_unicode(b):
|
||||||
assert b.get_block(block.id) == block.to_dict()
|
assert b.get_block(block.id) == block.to_dict()
|
||||||
assert block.validate(b) == block
|
assert block.validate(b) == block
|
||||||
assert beer_json in serialize(block.to_dict())
|
assert beer_json in serialize(block.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.bdb
|
||||||
|
def test_is_new_transaction(b, genesis_block):
|
||||||
|
from bigchaindb.models import Transaction
|
||||||
|
|
||||||
|
def write_tx(n):
|
||||||
|
tx = Transaction.create([b.me], [([b.me], n)])
|
||||||
|
tx = tx.sign([b.me_private])
|
||||||
|
# Tx is new because it's not in any block
|
||||||
|
assert b.is_new_transaction(tx.id)
|
||||||
|
|
||||||
|
block = b.create_block([tx])
|
||||||
|
b.write_block(block)
|
||||||
|
return tx, block
|
||||||
|
|
||||||
|
# test VALID case
|
||||||
|
tx, block = write_tx(1)
|
||||||
|
# Tx is now in undecided block
|
||||||
|
assert not b.is_new_transaction(tx.id)
|
||||||
|
assert b.is_new_transaction(tx.id, exclude_block_id=block.id)
|
||||||
|
# After voting valid, should not be new
|
||||||
|
vote = b.vote(block.id, genesis_block.id, True)
|
||||||
|
b.write_vote(vote)
|
||||||
|
assert not b.is_new_transaction(tx.id)
|
||||||
|
assert b.is_new_transaction(tx.id, exclude_block_id=block.id)
|
||||||
|
|
||||||
|
# test INVALID case
|
||||||
|
tx, block = write_tx(2)
|
||||||
|
# Tx is now in undecided block
|
||||||
|
assert not b.is_new_transaction(tx.id)
|
||||||
|
assert b.is_new_transaction(tx.id, exclude_block_id=block.id)
|
||||||
|
vote = b.vote(block.id, genesis_block.id, False)
|
||||||
|
b.write_vote(vote)
|
||||||
|
# Tx is new because it's only found in an invalid block
|
||||||
|
assert b.is_new_transaction(tx.id)
|
||||||
|
assert b.is_new_transaction(tx.id, exclude_block_id=block.id)
|
||||||
|
|
|
@ -629,3 +629,17 @@ def test_start(mock_start, b):
|
||||||
from bigchaindb.pipelines import vote
|
from bigchaindb.pipelines import vote
|
||||||
vote.start()
|
vote.start()
|
||||||
mock_start.assert_called_with()
|
mock_start.assert_called_with()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.genesis
|
||||||
|
def test_vote_no_double_inclusion(b):
|
||||||
|
from bigchaindb.pipelines import vote
|
||||||
|
|
||||||
|
tx = dummy_tx(b)
|
||||||
|
block = b.create_block([tx])
|
||||||
|
r = vote.Vote().validate_tx(tx, block.id, 1)
|
||||||
|
assert r == (True, block.id, 1)
|
||||||
|
|
||||||
|
b.write_block(block)
|
||||||
|
r = vote.Vote().validate_tx(tx, 'other_block_id', 1)
|
||||||
|
assert r == (False, 'other_block_id', 1)
|
||||||
|
|
|
@ -88,12 +88,3 @@ def test_has_previous_vote(monkeypatch):
|
||||||
block = {'votes': ({'node_pubkey': 'pubkey'},)}
|
block = {'votes': ({'node_pubkey': 'pubkey'},)}
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
bigchain.has_previous_vote(block)
|
bigchain.has_previous_vote(block)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('exists', (True, False))
|
|
||||||
def test_transaction_exists(monkeypatch, exists):
|
|
||||||
from bigchaindb.core import Bigchain
|
|
||||||
monkeypatch.setattr(
|
|
||||||
'bigchaindb.backend.query.has_transaction', lambda x, y: exists)
|
|
||||||
bigchain = Bigchain(public_key='pubkey', private_key='privkey')
|
|
||||||
assert bigchain.transaction_exists('txid') is exists
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user