1515from __future__ import annotations
1616
1717from collections import defaultdict
18- from typing import TYPE_CHECKING , Callable
18+ from typing import TYPE_CHECKING , Callable , assert_never
1919
2020from structlog import get_logger
2121
2222from hathor .consensus .block_consensus import BlockConsensusAlgorithmFactory
2323from hathor .consensus .context import ConsensusAlgorithmContext
2424from hathor .consensus .transaction_consensus import TransactionConsensusAlgorithmFactory
2525from hathor .execution_manager import non_critical_code
26- from hathor .feature_activation .utils import Features
26+ from hathor .feature_activation .feature import Feature
2727from hathor .profiler import get_cpu_profiler
2828from hathor .pubsub import HathorEvents , PubSubManager
29- from hathor .transaction import BaseTransaction , Transaction
29+ from hathor .transaction import BaseTransaction , Block , Transaction
3030from hathor .transaction .exceptions import RewardLocked
3131from hathor .util import not_none
3232
@@ -78,6 +78,7 @@ def __init__(
7878 pubsub : PubSubManager ,
7979 * ,
8080 settings : HathorSettings ,
81+ tx_storage : TransactionStorage ,
8182 runner_factory : RunnerFactory ,
8283 nc_calls_sorter : NCSorterCallable ,
8384 nc_log_storage : NCLogStorage ,
@@ -87,6 +88,7 @@ def __init__(
8788 self ._settings = settings
8889 self .log = logger .new ()
8990 self ._pubsub = pubsub
91+ self .tx_storage = tx_storage
9092 self .nc_storage_factory = nc_storage_factory
9193 self .soft_voided_tx_ids = frozenset (soft_voided_tx_ids )
9294 self .block_algorithm_factory = BlockConsensusAlgorithmFactory (
@@ -109,8 +111,7 @@ def unsafe_update(self, base: BaseTransaction) -> None:
109111 if this method throws any exception.
110112 """
111113 from hathor .transaction import Block , Transaction
112- assert base .storage is not None
113- assert base .storage .is_only_valid_allowed ()
114+ assert self .tx_storage .is_only_valid_allowed ()
114115 meta = base .get_metadata ()
115116 assert meta .validation .is_valid ()
116117
@@ -122,12 +123,10 @@ def unsafe_update(self, base: BaseTransaction) -> None:
122123 # this context instance will live only while this update is running
123124 context = self .create_context ()
124125
125- assert base .storage is not None
126- storage = base .storage
127- best_height , best_tip = storage .indexes .height .get_height_tip ()
126+ best_height , best_tip = self .tx_storage .indexes .height .get_height_tip ()
128127
129128 # This has to be called before the removal of vertices, otherwise this call may fail.
130- old_best_block = base . storage . get_transaction (best_tip )
129+ old_best_block = self . tx_storage . get_block (best_tip )
131130
132131 if isinstance (base , Transaction ):
133132 context .transaction_algorithm .update_consensus (base )
@@ -139,10 +138,10 @@ def unsafe_update(self, base: BaseTransaction) -> None:
139138 # signal a mempool tips index update for all affected transactions,
140139 # because that index is used on _compute_vertices_that_became_invalid below.
141140 for tx_affected in _sorted_affected_txs (context .txs_affected ):
142- storage .indexes .mempool_tips .update (tx_affected )
141+ self . tx_storage .indexes .mempool_tips .update (tx_affected )
143142
144143 txs_to_remove : list [BaseTransaction ] = []
145- new_best_height , new_best_tip = storage .indexes .height .get_height_tip ()
144+ new_best_height , new_best_tip = self . tx_storage .indexes .height .get_height_tip ()
146145
147146 if context .reorg_info is not None :
148147 if new_best_height < best_height :
@@ -152,20 +151,22 @@ def unsafe_update(self, base: BaseTransaction) -> None:
152151 )
153152
154153 # XXX: this method will mark as INVALID all transactions in the mempool that became invalid after the reorg
155- txs_to_remove .extend (self ._compute_vertices_that_became_invalid (storage , new_best_height ))
154+ txs_to_remove .extend (
155+ self ._compute_vertices_that_became_invalid (new_best_block = context .reorg_info .new_best_block )
156+ )
156157
157158 if txs_to_remove :
158159 self .log .warn ('some transactions on the mempool became invalid and will be removed' ,
159160 count = len (txs_to_remove ))
160161 # XXX: because transactions in `txs_to_remove` are marked as invalid, we need this context to be
161162 # able to remove them
162- with storage .allow_invalid_context ():
163- self ._remove_transactions (txs_to_remove , storage , context )
163+ with self . tx_storage .allow_invalid_context ():
164+ self ._remove_transactions (txs_to_remove , context )
164165
165166 # emit the reorg started event if needed
166167 if context .reorg_info is not None :
167168 assert isinstance (old_best_block , Block )
168- new_best_block = base . storage .get_transaction (new_best_tip )
169+ new_best_block = self . tx_storage .get_transaction (new_best_tip )
169170 reorg_size = old_best_block .get_height () - context .reorg_info .common_block .get_height ()
170171 # TODO: After we remove block ties, should the assert below be true?
171172 # assert old_best_block.get_metadata().voided_by
@@ -190,10 +191,9 @@ def unsafe_update(self, base: BaseTransaction) -> None:
190191
191192 # finally signal an index update for all affected transactions
192193 for tx_affected in _sorted_affected_txs (context .txs_affected ):
193- assert tx_affected .storage is not None
194- tx_affected .storage .indexes .update_critical_indexes (tx_affected )
194+ self .tx_storage .indexes .update_critical_indexes (tx_affected )
195195 with non_critical_code (self .log ):
196- tx_affected . storage .indexes .update_non_critical_indexes (tx_affected )
196+ self . tx_storage .indexes .update_non_critical_indexes (tx_affected )
197197 context .pubsub .publish (HathorEvents .CONSENSUS_TX_UPDATE , tx = tx_affected )
198198
199199 # signal all transactions of which the execution succeeded
@@ -242,8 +242,7 @@ def _filter_out_soft_voided_entries(self, tx: BaseTransaction, voided_by: set[by
242242 continue
243243 if h in self .soft_voided_tx_ids :
244244 continue
245- assert tx .storage is not None
246- tx3 = tx .storage .get_transaction (h )
245+ tx3 = self .tx_storage .get_transaction (h )
247246 tx3_meta = tx3 .get_metadata ()
248247 tx3_voided_by : set [bytes ] = tx3_meta .voided_by or set ()
249248 if not (self .soft_voided_tx_ids & tx3_voided_by ):
@@ -267,21 +266,15 @@ def _filter_out_nc_fail_entries(self, tx: BaseTransaction, voided_by: set[bytes]
267266 continue
268267 if h == tx .hash :
269268 continue
270- assert tx .storage is not None
271- tx2 = tx .storage .get_transaction (h )
269+ tx2 = self .tx_storage .get_transaction (h )
272270 tx2_meta = tx2 .get_metadata ()
273271 tx2_voided_by : set [bytes ] = tx2_meta .voided_by or set ()
274272 if NC_EXECUTION_FAIL_ID in tx2_voided_by :
275273 ret .discard (h )
276274 assert NC_EXECUTION_FAIL_ID not in ret
277275 return ret
278276
279- def _remove_transactions (
280- self ,
281- txs : list [BaseTransaction ],
282- storage : TransactionStorage ,
283- context : ConsensusAlgorithmContext ,
284- ) -> None :
277+ def _remove_transactions (self , txs : list [BaseTransaction ], context : ConsensusAlgorithmContext ) -> None :
285278 """Will remove all the transactions on the list from the database.
286279
287280 Special notes:
@@ -319,38 +312,32 @@ def _remove_transactions(
319312 spent_tx_meta .spent_outputs [tx_input .index ].remove (tx .hash )
320313 context .save (spent_tx )
321314 for parent_hash , children_to_remove in parents_to_update .items ():
322- parent_tx = storage .get_transaction (parent_hash )
315+ parent_tx = self . tx_storage .get_transaction (parent_hash )
323316 for child in children_to_remove :
324- storage .vertex_children .remove_child (parent_tx , child )
317+ self . tx_storage .vertex_children .remove_child (parent_tx , child )
325318 context .save (parent_tx )
326319 for tx in txs :
327320 self .log .debug ('remove transaction' , tx = tx .hash_hex )
328- storage .remove_transaction (tx )
321+ self . tx_storage .remove_transaction (tx )
329322
330- def _compute_vertices_that_became_invalid (
331- self ,
332- storage : TransactionStorage ,
333- new_best_height : int ,
334- ) -> list [BaseTransaction ]:
323+ def _compute_vertices_that_became_invalid (self , * , new_best_block : Block ) -> list [BaseTransaction ]:
335324 """This method will look for transactions in the mempool that have become invalid after a reorg."""
336325 from hathor .transaction .storage .traversal import BFSTimestampWalk
337326 from hathor .transaction .validation_state import ValidationState
338327
339- mempool_tips = list (storage . indexes .mempool_tips .iter (storage ))
328+ mempool_tips = list (self . tx_storage . indexes .mempool_tips .iter (self . tx_storage ))
340329 if not mempool_tips :
341330 # Mempool is empty, nothing to remove.
342331 return []
343332
344333 mempool_rules : tuple [Callable [[Transaction ], bool ], ...] = (
345- lambda tx : self ._reward_lock_mempool_rule (tx , new_best_height ),
346- lambda tx : self ._unknown_contract_mempool_rule (tx ),
347- lambda tx : self ._nano_activation_rule (storage , tx ),
348- lambda tx : self ._fee_tokens_activation_rule (storage , tx ),
349- self ._checkdatasig_count_rule ,
334+ lambda tx : self ._reward_lock_mempool_rule (tx , new_best_block .get_height ()),
335+ lambda tx : self ._feature_activation_rules (tx , new_best_block ),
336+ self ._unknown_contract_mempool_rule ,
350337 )
351338
352339 find_invalid_bfs = BFSTimestampWalk (
353- storage , is_dag_funds = True , is_dag_verifications = True , is_left_to_right = False
340+ self . tx_storage , is_dag_funds = True , is_dag_verifications = True , is_left_to_right = False
354341 )
355342
356343 invalid_txs : set [BaseTransaction ] = set ()
@@ -373,7 +360,7 @@ def _compute_vertices_that_became_invalid(
373360 # From the invalid txs, mark all vertices to the right as invalid. This includes both txs and blocks.
374361 to_remove : list [BaseTransaction ] = []
375362 find_to_remove_bfs = BFSTimestampWalk (
376- storage , is_dag_funds = True , is_dag_verifications = True , is_left_to_right = True
363+ self . tx_storage , is_dag_funds = True , is_dag_verifications = True , is_left_to_right = True
377364 )
378365 for vertex in find_to_remove_bfs .run (invalid_txs , skip_root = False ):
379366 vertex .set_validation (ValidationState .INVALID )
@@ -416,15 +403,40 @@ def _unknown_contract_mempool_rule(self, tx: Transaction) -> bool:
416403 return False
417404 return True
418405
419- def _nano_activation_rule (self , storage : TransactionStorage , tx : Transaction ) -> bool :
406+ def _feature_activation_rules (self , tx : Transaction , new_best_block : Block ) -> bool :
407+ """Check whether a tx became invalid because of some feature state of the new best block."""
408+ features = self .feature_service .get_feature_states (vertex = new_best_block )
409+
410+ for feature , feature_state in features .items ():
411+ is_active = feature_state .is_active ()
412+ match feature :
413+ case Feature .NANO_CONTRACTS :
414+ if not self ._nano_activation_rule (tx , is_active ):
415+ return False
416+ case Feature .FEE_TOKENS :
417+ if not self ._fee_tokens_activation_rule (tx , is_active ):
418+ return False
419+ case Feature .COUNT_CHECKDATASIG_OP :
420+ if not self ._checkdatasig_count_rule (tx ):
421+ return False
422+ case (
423+ Feature .INCREASE_MAX_MERKLE_PATH_LENGTH
424+ | Feature .NOP_FEATURE_1
425+ | Feature .NOP_FEATURE_2
426+ | Feature .NOP_FEATURE_3
427+ ):
428+ # These features do not affect transactions.
429+ pass
430+ case _:
431+ assert_never (feature )
432+
433+ return True
434+
435+ def _nano_activation_rule (self , tx : Transaction , is_active : bool ) -> bool :
420436 """Check whether a tx became invalid because the reorg changed the nano feature activation state."""
421437 from hathor .nanocontracts import OnChainBlueprint
422438
423- best_block = storage .get_best_block ()
424- features = Features .from_vertex (
425- settings = self ._settings , vertex = best_block , feature_service = self .feature_service
426- )
427- if features .nanocontracts :
439+ if is_active :
428440 # When nano is active, this rule has no effect.
429441 return True
430442
@@ -437,18 +449,14 @@ def _nano_activation_rule(self, storage: TransactionStorage, tx: Transaction) ->
437449
438450 return True
439451
440- def _fee_tokens_activation_rule (self , storage : TransactionStorage , tx : Transaction ) -> bool :
452+ def _fee_tokens_activation_rule (self , tx : Transaction , is_active : bool ) -> bool :
441453 """
442454 Check whether a tx became invalid because the reorg changed the fee-based tokens feature activation state.
443455 """
444456 from hathor .transaction .token_creation_tx import TokenCreationTransaction
445457 from hathor .transaction .token_info import TokenVersion
446458
447- best_block = storage .get_best_block ()
448- features = Features .from_vertex (
449- settings = self ._settings , vertex = best_block , feature_service = self .feature_service
450- )
451- if features .fee_tokens :
459+ if is_active :
452460 # When fee-based tokens feature is active, this rule has no effect.
453461 return True
454462
@@ -462,9 +470,11 @@ def _fee_tokens_activation_rule(self, storage: TransactionStorage, tx: Transacti
462470 return True
463471
464472 def _checkdatasig_count_rule (self , tx : Transaction ) -> bool :
465- """Check whether a tx became invalid because the reorg changed the checkdatasig feature activation state ."""
473+ """Check whether a tx became invalid because of the count checkdatasig feature."""
466474 from hathor .verification .vertex_verifier import VertexVerifier
467475
476+ # We check all txs regardless of the feature state, because this rule
477+ # already prohibited mempool txs before the block feature activation.
468478 # Any exception in the sigops verification will be considered
469479 # a fail and the tx will be removed from the mempool.
470480 try :
0 commit comments