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
14 changes: 5 additions & 9 deletions hathor/consensus/block_consensus.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,9 @@ def execute_nano_contracts(self, block: Block) -> None:
"""Execute the method calls for transactions confirmed by this block handling reorgs."""
# If we reach this point, Nano Contracts must be enabled.
assert self._settings.ENABLE_NANO_CONTRACTS
assert not block.is_genesis

meta = block.get_metadata()

if block.is_genesis:
self._nc_initialize_genesis(block)
return

if meta.voided_by:
# If the block is voided, skip execution.
return
Expand Down Expand Up @@ -248,16 +244,16 @@ def _nc_execute_calls(self, block: Block, *, is_reorg: bool) -> None:
tx=tx.hash.hex(),
execution=tx_meta.nc_execution.value)
match tx_meta.nc_execution:
case NCExecutionState.PENDING:
assert False # should never happen
case NCExecutionState.PENDING: # pragma: no cover
assert False, 'unexpected pending state' # should never happen
case NCExecutionState.SUCCESS:
assert tx_meta.voided_by is None
case NCExecutionState.FAILURE:
assert tx_meta.voided_by == {tx.hash, NC_EXECUTION_FAIL_ID}
case NCExecutionState.SKIPPED:
assert tx_meta.voided_by
assert NC_EXECUTION_FAIL_ID not in tx_meta.voided_by
case _:
case _: # pragma: no cover
assert_never(tx_meta.nc_execution)

def nc_update_metadata(self, tx: Transaction, runner: 'Runner') -> None:
Expand Down Expand Up @@ -423,7 +419,7 @@ def update_voided_info(self, block: Block) -> None:
self.update_voided_by_from_parents(block)

else:
# Either eveyone has the same score or there is a winner.
# Either everyone has the same score or there is a winner.

valid_heads = []
for head in heads:
Expand Down
11 changes: 2 additions & 9 deletions hathor/verification/transaction_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,22 +117,15 @@ def verify_sigops_input(self, tx: Transaction, enable_checkdatasig_count: bool =

def verify_inputs(self, tx: Transaction, *, skip_script: bool = False) -> None:
"""Verify inputs signatures and ownership and all inputs actually exist"""
from hathor.transaction.storage.exceptions import TransactionDoesNotExist

spent_outputs: set[tuple[VertexId, int]] = set()
for input_tx in tx.inputs:
if len(input_tx.data) > self._settings.MAX_INPUT_DATA_SIZE:
raise InvalidInputDataSize('size: {} and max-size: {}'.format(
len(input_tx.data), self._settings.MAX_INPUT_DATA_SIZE
))

try:
spent_tx = tx.get_spent_tx(input_tx)
if input_tx.index >= len(spent_tx.outputs):
raise InexistentInput('Output spent by this input does not exist: {} index {}'.format(
input_tx.tx_id.hex(), input_tx.index))
except TransactionDoesNotExist:
raise InexistentInput('Input tx does not exist: {}'.format(input_tx.tx_id.hex()))
spent_tx = tx.get_spent_tx(input_tx)
assert input_tx.index < len(spent_tx.outputs)

if tx.timestamp <= spent_tx.timestamp:
raise TimestampError('tx={} timestamp={}, spent_tx={} timestamp={}'.format(
Expand Down
7 changes: 3 additions & 4 deletions hathor/verification/verification_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def verify_basic(
assert type(vertex) is OnChainBlueprint
assert self._settings.ENABLE_NANO_CONTRACTS
self._verify_basic_on_chain_blueprint(vertex, params)
case _:
case _: # pragma: no cover
assert_never(vertex.version)

if vertex.is_nano_contract():
Expand Down Expand Up @@ -192,9 +192,8 @@ def verify(self, vertex: BaseTransaction, params: VerificationParams) -> None:
self._verify_token_creation_tx(vertex, params)
case TxVersion.ON_CHAIN_BLUEPRINT:
assert type(vertex) is OnChainBlueprint
# TODO: on-chain blueprint verifications
self._verify_tx(vertex, params)
case _:
case _: # pragma: no cover
assert_never(vertex.version)

if vertex.is_nano_contract():
Expand Down Expand Up @@ -297,7 +296,7 @@ def verify_without_storage(self, vertex: BaseTransaction, params: VerificationPa
case TxVersion.ON_CHAIN_BLUEPRINT:
assert type(vertex) is OnChainBlueprint
self._verify_without_storage_on_chain_blueprint(vertex, params)
case _:
case _: # pragma: no cover
assert_never(vertex.version)

if vertex.is_nano_contract():
Expand Down
4 changes: 2 additions & 2 deletions hathor/verification/vertex_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,9 @@ def get_allowed_headers(self, vertex: BaseTransaction) -> set[type[VertexBaseHea
case NanoContractsSetting.FEATURE_ACTIVATION:
if self._feature_service.is_feature_active(vertex=vertex, feature=Feature.NANO_CONTRACTS):
allowed_headers.add(NanoHeader)
case _ as unreachable:
case _ as unreachable: # pragma: no cover
assert_never(unreachable)
case _:
case _: # pragma: no cover
assert_never(vertex.version)
return allowed_headers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,7 @@ def _create_on_chain_blueprint(self, nc_code: str) -> OnChainBlueprint:
self._ocb_mine(blueprint)
return blueprint

def _test_forbid_syntax(
self,
code: str,
syntax_errors: tuple[str, ...],
) -> None:
def _test_forbid_syntax(self, code: str, *, syntax_errors: tuple[str, ...]) -> None:
blueprint = self._create_on_chain_blueprint(code)
with self.assertRaises(InvalidNewTransaction) as cm:
self.manager.vertex_handler.on_new_relayed_vertex(blueprint)
Expand All @@ -65,6 +61,10 @@ def _test_forbid_syntax(
# The first error is always the one that makes the tx fail
assert cm.exception.__cause__.__cause__.args[0] == syntax_errors[0]

self._test_expected_syntax_errors(code, syntax_errors=syntax_errors)

def _test_expected_syntax_errors(self, code: str, *, syntax_errors: tuple[str, ...],) -> None:
blueprint = self._create_on_chain_blueprint(code)
rules = self.manager.verification_service.verifiers.on_chain_blueprint.blueprint_code_rules()
errors = []
for rule in rules:
Expand Down Expand Up @@ -334,6 +334,44 @@ def test_forbid_match_dunder(self) -> None:
),
)

# These are allowed:

self._test_expected_syntax_errors(
dedent('''
match 123:
case int():
pass
'''),
syntax_errors=(),
)

self._test_expected_syntax_errors(
dedent('''
match 123:
case int(real=real):
pass
'''),
syntax_errors=(),
)

self._test_expected_syntax_errors(
dedent('''
match 123:
case {}:
pass
'''),
syntax_errors=(),
)

self._test_expected_syntax_errors(
dedent('''
match 123:
case {'real': 123}:
pass
'''),
syntax_errors=(),
)

def test_forbid_async_fn(self) -> None:
self._test_forbid_syntax(
'async def foo():\n ...',
Expand Down Expand Up @@ -367,6 +405,15 @@ def test_forbid_await_syntax(self) -> None:
),
)

def test_invalid_python_syntax(self) -> None:
code = 'x ++= 1'
blueprint = self._create_on_chain_blueprint(code)
with self.assertRaises(InvalidNewTransaction) as cm:
self.manager.vertex_handler.on_new_relayed_vertex(blueprint)
assert isinstance(cm.exception.__cause__, OCBInvalidScript)
assert isinstance(cm.exception.__cause__.__cause__, SyntaxError)
assert cm.exception.args[0] == 'full validation failed: Could not correctly parse the script'

def test_blueprint_type_not_a_class(self) -> None:
blueprint = self._create_on_chain_blueprint('''__blueprint__ = "Bet"''')
with self.assertRaises(InvalidNewTransaction) as cm:
Expand Down
56 changes: 56 additions & 0 deletions tests/nanocontracts/test_consensus.py
Original file line number Diff line number Diff line change
Expand Up @@ -1417,3 +1417,59 @@ def test_nc_consensus_voided_tx_propagation_to_blocks(self) -> None:
self.assertIsNone(b33.get_metadata().voided_by)
self.assertIsNone(b34.get_metadata().voided_by)
self.assertIsNone(b50.get_metadata().voided_by)

def test_reorg_nc_with_conflict(self) -> None:
dag_builder = TestDAGBuilder.from_manager(self.manager)
artifacts = dag_builder.build_from_str(f'''
blockchain genesis b[1..33]
blockchain b31 a[32..34]
b30 < dummy

nc1.nc_id = "{self.myblueprint_id.hex()}"
nc1.nc_method = initialize("00")

# nc2 will fail because nc1.counter is 0
nc2.nc_id = nc1
nc2.nc_method = fail_on_zero()

# nc2 has a conflict with tx2
tx1.out[0] <<< nc2
tx1.out[0] <<< tx2

nc1 <-- b31
nc2 <-- b32

# we want to include tx2, but it can't be confirmed by b32
# otherwise that block would be confirming conflicts
tx2 < b32

# a34 will generate a reorg, reexecuting nc2.
b33 < a32
nc2 <-- a33
''')

b31, b32, b33 = artifacts.get_typed_vertices(['b31', 'b32', 'b33'], Block)
a32, a33, a34 = artifacts.get_typed_vertices(['a32', 'a33', 'a34'], Block)
nc2, tx2 = artifacts.get_typed_vertices(['nc2', 'tx2'], Transaction)

artifacts.propagate_with(self.manager, up_to='b33')

assert nc2.get_metadata().nc_execution is NCExecutionState.FAILURE
assert nc2.get_metadata().voided_by == {nc2.hash, NC_EXECUTION_FAIL_ID}
assert nc2.get_metadata().conflict_with == [tx2.hash]
assert nc2.get_metadata().first_block == b32.hash

assert tx2.get_metadata().voided_by == {tx2.hash}
assert tx2.get_metadata().conflict_with == [nc2.hash]
assert tx2.get_metadata().first_block is None

artifacts.propagate_with(self.manager)

assert nc2.get_metadata().nc_execution is NCExecutionState.FAILURE
assert nc2.get_metadata().voided_by == {nc2.hash, NC_EXECUTION_FAIL_ID}
assert nc2.get_metadata().conflict_with == [tx2.hash]
assert nc2.get_metadata().first_block == a33.hash

assert tx2.get_metadata().voided_by == {tx2.hash}
assert tx2.get_metadata().conflict_with == [nc2.hash]
assert tx2.get_metadata().first_block is None
Loading