Skip to content

Add prechecker address filtering via dry-run ProduceBlockAdvanced#4382

Open
Tristan-Wilson wants to merge 1 commit intofilter-submit-retryablefrom
prefilter-produceblock-dryrun
Open

Add prechecker address filtering via dry-run ProduceBlockAdvanced#4382
Tristan-Wilson wants to merge 1 commit intofilter-submit-retryablefrom
prefilter-produceblock-dryrun

Conversation

@Tristan-Wilson
Copy link
Member

Add address filtering to the prechecker by running the full block production pipeline on a throwaway statedb. For each RPC-submitted transaction, when filtering is enabled, the prechecker calls ProduceBlockAdvanced(dryRun=true) through a thin PrefiltererSequencingHooks adapter that implements the same TouchAddress/IsAddressFiltered logic the sequencer uses. This catches direct address touches, direct redeems, contract-triggered redeems (where a contract internally calls ArbRetryableTx.redeem), event-filter hits, and cascading redeem chains -- all with zero coupling to retryable internals.

The approach is unified: every tx goes through ProduceBlockAdvanced rather than trying to detect redeem-related transactions statically. A regular contract can call ArbRetryableTx.redeem(ticketId) internally, making static detection impossible. Running the real block processor as a black box sidesteps this entirely.

Key design decisions:

  • No isRedeemTx gate: the unified approach runs ALL txs through ProduceBlockAdvanced when filtering is enabled. A static gate on To == 0x6e misses contract-triggered redeems. The only gate is addressChecker != nil.

  • No statedb.Copy(): each PublishTransaction opens its own statedb via bc.StateAt, never uses it after the dry-run, and ProduceBlockAdvanced doesn't call Commit. The statedb is passed directly and GC'd on return. This eliminates the main cost objection to running the full pipeline.

  • dryRun mode: a new bool parameter on ProduceBlockAdvanced causes an early return after the tx processing loop, skipping FinalizeBlock (which calls IntermediateRoot -- likely the dominant fixed overhead per call), receipt construction, block assembly, and balance delta checks. The prechecker only needs hooks.filtered, not a valid block.

  • Forwarder-only wiring: on the sequencer node, the sequencer already returns ErrArbTxFilter synchronously from real block production so the prechecker dry-run is redundant. The addressChecker/eventFilter are only wired to TxPreChecker on forwarder nodes (which don't run a sequencer and relay to a remote sequencer after prechecking).

  • Config refactor: TransactionFilteringConfig is moved from SequencerConfig to a top-level execution.transaction-filtering.* namespace. Forwarders don't have a Sequencer component, so they couldn't access the filter config when it lived under execution.sequencer. The new location makes it available to any node role.

fixes NIT-4344

Add address filtering to the prechecker by running the full block production
pipeline on a throwaway statedb. For each RPC-submitted transaction, when
filtering is enabled, the prechecker calls ProduceBlockAdvanced(dryRun=true)
through a thin PrefiltererSequencingHooks adapter that implements the same
TouchAddress/IsAddressFiltered logic the sequencer uses. This catches direct
address touches, direct redeems, contract-triggered redeems (where a contract
internally calls ArbRetryableTx.redeem), event-filter hits, and cascading
redeem chains -- all with zero coupling to retryable internals.

The approach is unified: every tx goes through ProduceBlockAdvanced rather than
trying to detect redeem-related transactions statically. A regular contract can
call ArbRetryableTx.redeem(ticketId) internally, making static detection
impossible. Running the real block processor as a black box sidesteps this
entirely.

Key design decisions:

- No isRedeemTx gate: the unified approach runs ALL txs through
  ProduceBlockAdvanced when filtering is enabled. A static gate on
  To == 0x6e misses contract-triggered redeems. The only gate is
  addressChecker != nil.

- No statedb.Copy(): each PublishTransaction opens its own statedb via
  bc.StateAt, never uses it after the dry-run, and ProduceBlockAdvanced
  doesn't call Commit. The statedb is passed directly and GC'd on return.
  This eliminates the main cost objection to running the full pipeline.

- dryRun mode: a new bool parameter on ProduceBlockAdvanced causes an early
  return after the tx processing loop, skipping FinalizeBlock (which calls
  IntermediateRoot -- likely the dominant fixed overhead per call),
  receipt construction, block assembly, and balance delta checks. The
  prechecker only needs hooks.filtered, not a valid block.

- Forwarder-only wiring: on the sequencer node, the sequencer already returns
  ErrArbTxFilter synchronously from real block production so the prechecker
  dry-run is redundant. The addressChecker/eventFilter are only wired to
  TxPreChecker on forwarder nodes (which don't run a sequencer and relay
  to a remote sequencer after prechecking).

- Config refactor: TransactionFilteringConfig is moved from SequencerConfig to
  a top-level execution.transaction-filtering.* namespace. Forwarders don't
  have a Sequencer component, so they couldn't access the filter config when
  it lived under execution.sequencer. The new location makes it available to
  any node role.
@codecov
Copy link

codecov bot commented Feb 13, 2026

Codecov Report

❌ Patch coverage is 5.73770% with 115 lines in your changes missing coverage. Please review.
✅ Project coverage is 32.78%. Comparing base (66d78e5) to head (688a889).

Additional details and impacted files
@@                     Coverage Diff                     @@
##           filter-submit-retryable    #4382      +/-   ##
===========================================================
- Coverage                    33.09%   32.78%   -0.32%     
===========================================================
  Files                          489      490       +1     
  Lines                        58145    58221      +76     
===========================================================
- Hits                         19244    19088     -156     
- Misses                       35530    35789     +259     
+ Partials                      3371     3344      -27     

@github-actions
Copy link
Contributor

❌ 8 Tests Failed:

Tests completed Failed Passed Skipped
2963 8 2955 0
View the top 3 failed tests by shortest run time
TestRedisProduceComplex/one_producer,_all_consumers_are_active
Stack Traces | 1.270s run time
... [CONTENT TRUNCATED: Keeping last 20 lines]
�[36mDEBUG�[0m[02-13|17:51:58.894] consumer: xdel                           �[36mcid�[0m=1be985e1-7a51-4aab-9eeb-9731f4709c24 �[36mmessageId�[0m=1771005117780-0
�[36mDEBUG�[0m[02-13|17:51:58.932] checkResponses                           �[36mresponded�[0m=65 �[36merrored�[0m=0 �[36mchecked�[0m=66
�[36mDEBUG�[0m[02-13|17:51:58.937] redis producer: check responses starting
�[36mDEBUG�[0m[02-13|17:51:58.938] checkResponses                           �[36mresponded�[0m=1  �[36merrored�[0m=0 �[36mchecked�[0m=1
�[31mERROR�[0m[02-13|17:51:58.940] Error from XpendingExt in getting PEL for auto claim �[31merr�[0m="context canceled" �[31mpendingLen�[0m=0
�[31mERROR�[0m[02-13|17:51:58.943] Error from XpendingExt in getting PEL for auto claim �[31merr�[0m="context canceled" �[31mpendingLen�[0m=0
�[36mDEBUG�[0m[02-13|17:51:58.948] redis producer: check responses starting
�[36mDEBUG�[0m[02-13|17:51:58.948] checkResponses                           �[36mresponded�[0m=0  �[36merrored�[0m=0 �[36mchecked�[0m=0
�[31mERROR�[0m[02-13|17:51:58.948] Error from XpendingExt in getting PEL for auto claim �[31merr�[0m="context canceled" �[31mpendingLen�[0m=0
�[31mERROR�[0m[02-13|17:51:58.949] Error from XpendingExt in getting PEL for auto claim �[31merr�[0m="context canceled" �[31mpendingLen�[0m=0
�[31mERROR�[0m[02-13|17:51:58.949] Error from XpendingExt in getting PEL for auto claim �[31merr�[0m="context canceled" �[31mpendingLen�[0m=0
�[31mERROR�[0m[02-13|17:51:58.952] Error from XpendingExt in getting PEL for auto claim �[31merr�[0m="context canceled" �[31mpendingLen�[0m=0
�[36mDEBUG�[0m[02-13|17:51:58.956] redis producer: check responses starting
�[36mDEBUG�[0m[02-13|17:51:58.956] checkResponses                           �[36mresponded�[0m=0  �[36merrored�[0m=0 �[36mchecked�[0m=0
�[31mERROR�[0m[02-13|17:51:58.956] Error from XpendingExt in getting PEL for auto claim �[31merr�[0m="context canceled" �[31mpendingLen�[0m=0
�[31mERROR�[0m[02-13|17:51:58.958] Error from XpendingExt in getting PEL for auto claim �[31merr�[0m="context canceled" �[31mpendingLen�[0m=0
�[31mERROR�[0m[02-13|17:51:58.958] Error from XpendingExt in getting PEL for auto claim �[31merr�[0m="context canceled" �[31mpendingLen�[0m=0
    pubsub_test.go:408: mergeMaps() unexpected error: duplicate key: 1771005117762-0
�[36mDEBUG�[0m[02-13|17:51:59.026] Error destroying a stream group          �[36merror�[0m="dial tcp 127.0.0.1:33323: connect: connection refused"
--- FAIL: TestRedisProduceComplex/one_producer,_all_consumers_are_active (1.27s)
TestBlocksReExecutorCommitState
Stack Traces | 2.130s run time
... [CONTENT TRUNCATED: Keeping last 20 lines]
TRACE[02-13|18:00:14.217] Handled RPC response                     reqid=38638 duration="1.242µs"
DEBUG[02-13|18:00:14.217] Served eth_getBlockByNumber              reqid=1512  duration="60.734µs"
TRACE[02-13|18:00:14.217] Handled RPC response                     reqid=1512  duration=942ns
DEBUG[02-13|18:00:14.218] Pushed sync data from consensus to execution synced=true  maxMessageCount=8   updatedAt=2026-02-13T18:00:14+0000 hasProgressMap=false
DEBUG[02-13|18:00:14.218] Served eth_getBlockByNumber              reqid=1080  duration="59.832µs"
DEBUG[02-13|18:00:14.218] Served eth_getBlockByNumber              reqid=1474  duration="45.736µs"
TRACE[02-13|18:00:14.218] Handled RPC response                     reqid=1080  duration="1.132µs"
DEBUG[02-13|18:00:14.218] Served eth_getBlockByNumber              reqid=16921 duration="41.778µs"
DEBUG[02-13|18:00:14.218] Served eth_getBlockByNumber              reqid=18561 duration="63.058µs"
TRACE[02-13|18:00:14.219] Handled RPC response                     reqid=16921 duration="1.513µs"
DEBUG[02-13|18:00:14.219] Served eth_getBlockByNumber              reqid=1208  duration="44.173µs"
TRACE[02-13|18:00:14.219] Handled RPC response                     reqid=1474  duration="1.212µs"
DEBUG[02-13|18:00:14.218] Served eth_getBlockByNumber              reqid=253   duration="60.874µs"
DEBUG[02-13|18:00:14.219] Served eth_getBlockByNumber              reqid=7392  duration="40.526µs"
TRACE[02-13|18:00:14.219] Handled RPC response                     reqid=1208  duration="1.784µs"
TRACE[02-13|18:00:14.219] Handled RPC response                     reqid=18561 duration=601ns
TRACE[02-13|18:00:14.219] Handled RPC response                     reqid=7392  duration=671ns
TRACE[02-13|18:00:14.219] Handled RPC response                     reqid=253   duration="2.976µs"
--- FAIL: TestBlocksReExecutorCommitState (2.13s)
DEBUG[02-13|18:00:14.220] Pushed sync data from consensus to execution synced=true  maxMessageCount=6   updatedAt=2026-02-13T18:00:14+0000 hasProgressMap=false
TestFilteredRetryableSequencerDoesNotReHalt
Stack Traces | 19.050s run time
... [CONTENT TRUNCATED: Keeping last 20 lines]
    common_test.go:733: BuildL1 deployConfig: DeployBold=true, DeployReferenceDAContracts=false
ERROR[02-13|18:12:40.707] Delayed message filtered - HALTING delayed sequencer txHashes=[0x7089b8015691a643fded6e433730cc04bd8e9d394df7560ab61eb57dead63a1c] delayedMsgIdx=3
    delayed_message_filter_test.go:1639: 
        	Error Trace:	/home/runner/work/nitro/nitro/system_tests/delayed_message_filter_test.go:1639
        	Error:      	Not equal: 
        	            	expected: 1000000000000
        	            	actual  : 0
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -2,4 +2,3 @@
        	            	  neg: (bool) false,
        	            	- abs: (big.nat) (len=1) {
        	            	-  (big.Word) 1000000000000
        	            	+ abs: (big.nat) {
        	            	  }
        	Test:       	TestFilteredRetryableSequencerDoesNotReHalt
        	Messages:   	normal transfer should be processed after filtered retryable
--- FAIL: TestFilteredRetryableSequencerDoesNotReHalt (19.05s)

📣 Thoughts on this report? Let Codecov know! | Powered by Codecov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants