Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions hathor/transaction/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ class ConflictWithConfirmedTxError(TxValidationError):
"""Input has a conflict with a confirmed transaction."""


class TooManyWithinConflicts(TxValidationError):
"""Input has too many within conflicts already."""


class TooManyBetweenConflicts(TxValidationError):
"""Input has too many between conflicts already."""


class TooManyOutputs(TxValidationError):
"""More than 256 outputs"""

Expand Down
16 changes: 16 additions & 0 deletions hathor/verification/transaction_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@
ScriptError,
TimestampError,
TooFewInputs,
TooManyBetweenConflicts,
TooManyInputs,
TooManySigOps,
TooManyTokens,
TooManyWithinConflicts,
UnusedTokensError,
WeightError,
)
Expand All @@ -57,6 +59,8 @@
cpu = get_cpu_profiler()

MAX_TOKENS_LENGTH: int = 16
MAX_WITHIN_CONFLICTS: int = 8
MAX_BETWEEN_CONFLICTS: int = 8


class TransactionVerifier:
Expand Down Expand Up @@ -356,12 +360,14 @@ def verify_conflict(self, tx: Transaction, params: VerificationParams) -> None:
if not params.reject_conflicts_with_confirmed_txs:
return

between_counter = 0
for txin in tx.inputs:
spent_tx = tx.get_spent_tx(txin)
spent_tx_meta = spent_tx.get_metadata()
if txin.index not in spent_tx_meta.spent_outputs:
continue
spent_by_list = spent_tx_meta.spent_outputs[txin.index]
within_counter = 0
for h in spent_by_list:
if h == tx.hash:
# Skip tx itself.
Expand All @@ -370,6 +376,16 @@ def verify_conflict(self, tx: Transaction, params: VerificationParams) -> None:
if conflict_tx.get_metadata().first_block is not None:
# only mempool conflicts are allowed
raise ConflictWithConfirmedTxError('transaction has a conflict with a confirmed transaction')
if within_counter == 0:
# Only increment once per input.
between_counter += 1
within_counter += 1

if within_counter >= MAX_WITHIN_CONFLICTS:
raise TooManyWithinConflicts

if between_counter > MAX_BETWEEN_CONFLICTS:
raise TooManyBetweenConflicts


@dataclass(kw_only=True, slots=True)
Expand Down
46 changes: 46 additions & 0 deletions tests/tx/test_verification_mempool.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@
ConflictWithConfirmedTxError,
InvalidToken,
TimestampError,
TooManyBetweenConflicts,
TooManyTokens,
TooManyWithinConflicts,
UnusedTokensError,
)
from hathor.transaction.nc_execution_state import NCExecutionState
from hathor.transaction.token_creation_tx import TokenCreationTransaction
from hathor.verification.nano_header_verifier import MAX_SEQNUM_DIFF_MEMPOOL
from hathor.verification.transaction_verifier import MAX_BETWEEN_CONFLICTS, MAX_WITHIN_CONFLICTS
from hathor.verification.vertex_verifier import MAX_PAST_TIMESTAMP_ALLOWED
from tests import unittest
from tests.dag_builder.builder import TestDAGBuilder
Expand Down Expand Up @@ -176,6 +179,49 @@ def test_conflict_with_confirmed_tx(self) -> None:
self.manager.vertex_handler.on_new_mempool_transaction(tx3)
assert isinstance(e.exception.__cause__, ConflictWithConfirmedTxError)

def test_too_many_between_conflicts(self) -> None:
lines = [f'tx0.out[{i}] <<< txN tx{i + 1}' for i in range(0, MAX_BETWEEN_CONFLICTS + 1)]
orders = [f'tx{i + 1} < txN' for i in range(0, MAX_BETWEEN_CONFLICTS + 1)]
newline = '\n'
artifacts = self.dag_builder.build_from_str(f'''
blockchain genesis b[1..30]
b10 < dummy

{newline.join(lines)}
{newline.join(orders)}
''')
artifacts.propagate_with(self.manager, up_to_before='txN')
txN = artifacts.get_typed_vertex('txN', Transaction)

# need to fix the timestamp to pass the old vertices mempool verification
txN.timestamp = int(self.manager.reactor.seconds())
self.dag_builder._exporter._vertex_resolver(txN)

with self.assertRaises(InvalidNewTransaction) as e:
self.manager.vertex_handler.on_new_mempool_transaction(txN)
assert isinstance(e.exception.__cause__, TooManyBetweenConflicts)

def test_too_many_within_conflicts(self) -> None:
tx_list = [f'tx{i + 1}' for i in range(0, MAX_WITHIN_CONFLICTS + 1)]
artifacts = self.dag_builder.build_from_str(f'''
blockchain genesis b[1..30]
b10 < dummy

tx0.out[0] <<< {' '.join(tx_list)}

{' < '.join(tx_list)}
''')
artifacts.propagate_with(self.manager, up_to_before=tx_list[-1])
txN = artifacts.get_typed_vertex(tx_list[-1], Transaction)

# need to fix the timestamp to pass the old vertices mempool verification
txN.timestamp = int(self.manager.reactor.seconds())
self.dag_builder._exporter._vertex_resolver(txN)

with self.assertRaises(InvalidNewTransaction) as e:
self.manager.vertex_handler.on_new_mempool_transaction(txN)
assert isinstance(e.exception.__cause__, TooManyWithinConflicts)

@inlineCallbacks
def test_checkpoints(self) -> Generator:
artifacts = self.dag_builder.build_from_str('''
Expand Down
Loading