Pre-checker tx filtering (using estimate gas call)#4583
Pre-checker tx filtering (using estimate gas call)#4583MishkaRogachev wants to merge 33 commits intomasterfrom
Conversation
91af9cf to
2b91fd1
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #4583 +/- ##
==========================================
- Coverage 34.20% 33.98% -0.23%
==========================================
Files 494 495 +1
Lines 58926 58972 +46
==========================================
- Hits 20156 20041 -115
- Misses 35230 35391 +161
Partials 3540 3540 |
❌ 9 Tests Failed:
View the top 3 failed tests by shortest run time
📣 Thoughts on this report? Let Codecov know! | Powered by Codecov |
There was a problem hiding this comment.
Pull request overview
This PR extends transaction address-filtering to the forwarder path by having TxPreChecker run a gas-estimation dry-run (via gasestimator.Run) to detect filtered addresses before forwarding transactions, covering direct sender/recipient touches, retryables, and event-based matches (including cascading redeems).
Changes:
- Add pre-checker filtering using a gas-estimation dry-run and a new
txFiltererimplementation for the backend. - Refactor transaction-filtering configuration wiring so filters/services are initialized at the execution-node level (and adjust tests accordingly).
- Add system tests validating prechecker-based filtering in a two-node (sequencer + forwarder) setup, plus a Solidity helper for retryable redemption.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| system_tests/tx_address_filter_test.go | Updates filtering-readiness test to inject a filter service directly. |
| system_tests/retryable_tickets_filtering_test.go | Forces manual gas limits to bypass eth_estimateGas pre-rejection in filtering tests. |
| system_tests/prechecker_filter_test.go | New end-to-end system tests for prechecker filtering via forwarder dry-runs. |
| system_tests/delayed_message_filter_test.go | Updates config field paths for transaction filterer RPC client / delayed sequencing filter toggle. |
| system_tests/common_test.go | Updates helper to set event filter rules via the new config location. |
| execution/gethexec/tx_pre_checker.go | Adds backend wiring + dry-run filtering check before publishing/forwarding. |
| execution/gethexec/tx_filterer.go | New tx filterer hook used by gas estimation to detect filtered touches. |
| execution/gethexec/sequencer.go | Moves filtering/service setup out of SequencerConfig; adjusts constructor and test helper. |
| execution/gethexec/node.go | Introduces top-level TransactionFiltering config; initializes services and wires backend + prechecker. |
| execution/gethexec/executionengine.go | Refactors address-touching helper to be reusable by the new tx filterer. |
| contracts-local/src/mocks/AddressFilterTest.sol | Adds a wrapper method to trigger retryable redemption from a contract. |
| changelog/mrogachev-nit-4344.md | Adds changelog entry for prechecker filtering behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| t.Fatal("addressFilterService is nil") | ||
| } | ||
| s.addressFilterService.GetHashStore().Store(salt, hashes, digest) | ||
| s.addressFilterService = service |
There was a problem hiding this comment.
SetAddressFilterServiceForTest only swaps the addressFilterService pointer; it doesn't mirror the production wiring done in ExecutionNode.Start (starting the service and setting ExecEngine's address checker). This can leave tests in an inconsistent state where FilteringReady() reflects the injected service but actual filtering behavior still uses the previous checker (or none). Consider also setting s.execEngine.SetAddressChecker(service.GetAddressChecker()) (and optionally starting/stopping the service), or rename/document the helper to clarify it only affects readiness checks.
| s.addressFilterService = service | |
| s.addressFilterService = service | |
| if s.execEngine != nil && service != nil { | |
| s.execEngine.SetAddressChecker(service.GetAddressChecker()) | |
| } |
There was a problem hiding this comment.
pre-existing, i believe it should be handled separately with some tests refactor
diegoximenes
left a comment
There was a problem hiding this comment.
There should also be tests covering eth_estimateGas filtering.
| } | ||
|
|
||
| func (f *txFilterer) TouchAddresses(statedb *state.StateDB, tx *types.Transaction, sender common.Address) { | ||
| touchAddresses(statedb, nil, tx, sender) |
There was a problem hiding this comment.
Using nil here is weird.
touchAddresses implementation in this PR was originally used only for the post tx filter for the delayed sequencing.
This txFilterer.TouchAddresses function is used for a pre tx filter for estimateGas/prechecker.
For regular tx sequencing, sequencer applies other code for pre tx filtering, of only touching from/to.
Why in this case txFilterer use delayed sequencing pre tx filtering policy instead of regular sequencing pre tx filtering policy?
There was a problem hiding this comment.
TouchAddresses is called only for scheduled txes (retryable redeems) from RunScheduledTxes: not for the main tx. These are retryables, so we need the full set: aliased sender and retryable-specific fields. The regular preTxFilter's from/to-only approach would miss those.
For the main tx, gasestimator touches only from/to directly, matching the regular preTxFilter policy. We can't reuse TouchAddresses there because gasestimator uses *core.Message not *types.Transaction.
So, the nil event filter is because event-based filtering happens once at the end in CheckFiltered, across all accumulated logs.
There was a problem hiding this comment.
These are retryables, so we need the full set: aliased sender and retryable-specific fields. The regular preTxFilter's from/to-only approach would miss those.
Current scenario:
- For delayed sequencing, the post tx filter calls touchRetryableAddresses, to handle ArbitrumSubmitRetryableTx in a special way. It also handles "TxTypeAlias" in a special way.
- For regular transaction sequencing, touchRetryableAddresses is not called, ArbitrumSubmitRetryableTx is not handled in a special way in any step of the filtering process.
- For pre-checker/eth_estimateGas we are enforcing to use the same policy as the delayed sequencing.
All those scenarios handle scheduled transactions.
At this point this is confusing to me.
I don't understand if regular transaction sequencing has an issue right now, and it should actually handle ArbitrumSubmitRetryableTx in a special way.
Or if we are enforcing this ArbitrumSubmitRetryableTx handling in eth_estimateGas unnecessarily, which doesn't seem create wrong results, but the whole inconsistency is causing me confusion.
I will let the way it is right now, but depending on the reason we should revisit it in another task to clean up this inconsistency.
@tsahee am I missing something?
There was a problem hiding this comment.
So the event filter will be added in checkFiltered and de-facto will enforce the same policy as normal transactions. But it deserves more thought and possibly renaming
execution/gethexec/tx_pre_checker.go
Outdated
| if err != nil { | ||
| return err | ||
| } | ||
| if err := c.checkFilteredAddresses(tx, block); err != nil { |
There was a problem hiding this comment.
This call can be made inside PreCheckTx, so you can avoid calling it twice, here and PublishExpressLaneTransaction.
There was a problem hiding this comment.
I think we discussed it on previous attempt with dry-run, these two paths are not the same. PreCheckTx has an early return at Strictness < LikelyCompatible that exits before the end, while checkFilteredAddresses only needs Strictness >= AlwaysCompatible. Restructuring the early returns to accommodate this messes things up.
There was a problem hiding this comment.
checkFilteredAddresses could be done in the beginning of PreCheckTx to avoid that.
But fully running current PreCheckTx can avoid unnecessary gas estimation anyway.
|
|
||
| type TransactionFilteringConfig struct { | ||
| DisableDelayedSequencingFilter bool `koanf:"disable-delayed-sequencing-filter"` | ||
| EnableRPCFilter bool `koanf:"enable-rpc-filter"` |
There was a problem hiding this comment.
EnableRPCFilter is too generic to me, since publishing a transaction also happens through RPC.
I am inclined on using EnableNonMutatingRPCFilter, but I think Tsahi doesn't like it.
I will let @tsahee decide that.
| EnableRPCFilter bool `koanf:"enable-rpc-filter"` | |
| EnableNonMutatingRPCFilter bool `koanf:"enable-non-mutating-rpc-filter"` |
There was a problem hiding this comment.
I indeed don't like non-mutating. enable-ethcall-filter? I know it's not exact but much more self-explanatory.
It should be identical to: https://github.com/OffchainLabs/nitro/pull/4591/changes
execution/gethexec/tx_pre_checker.go
Outdated
| if err != nil { | ||
| return err | ||
| } | ||
| if err := c.checkFilteredAddresses(tx, block); err != nil { |
There was a problem hiding this comment.
checkFilteredAddresses could be done in the beginning of PreCheckTx to avoid that.
But fully running current PreCheckTx can avoid unnecessary gas estimation anyway.
| } | ||
|
|
||
| func (f *txFilterer) TouchAddresses(statedb *state.StateDB, tx *types.Transaction, sender common.Address) { | ||
| touchAddresses(statedb, nil, tx, sender) |
There was a problem hiding this comment.
These are retryables, so we need the full set: aliased sender and retryable-specific fields. The regular preTxFilter's from/to-only approach would miss those.
Current scenario:
- For delayed sequencing, the post tx filter calls touchRetryableAddresses, to handle ArbitrumSubmitRetryableTx in a special way. It also handles "TxTypeAlias" in a special way.
- For regular transaction sequencing, touchRetryableAddresses is not called, ArbitrumSubmitRetryableTx is not handled in a special way in any step of the filtering process.
- For pre-checker/eth_estimateGas we are enforcing to use the same policy as the delayed sequencing.
All those scenarios handle scheduled transactions.
At this point this is confusing to me.
I don't understand if regular transaction sequencing has an issue right now, and it should actually handle ArbitrumSubmitRetryableTx in a special way.
Or if we are enforcing this ArbitrumSubmitRetryableTx handling in eth_estimateGas unnecessarily, which doesn't seem create wrong results, but the whole inconsistency is causing me confusion.
I will let the way it is right now, but depending on the reason we should revisit it in another task to clean up this inconsistency.
@tsahee am I missing something?
| if n.AddressFilterService != nil { | ||
| n.AddressFilterService.Start(ctx) | ||
| checker := n.AddressFilterService.GetAddressChecker() | ||
| n.ExecEngine.SetAddressChecker(checker) |
There was a problem hiding this comment.
I think we need to move get/set address checker to (n *ExecutionNode) Initialize, and then ExecEngine, when started, will know it has an addresschecker (so it could do things like refuse transactions while the filter is not initialized)
| } | ||
|
|
||
| func (f *txFilterer) TouchAddresses(statedb *state.StateDB, tx *types.Transaction, sender common.Address) { | ||
| touchAddresses(statedb, nil, tx, sender) |
There was a problem hiding this comment.
So the event filter will be added in checkFiltered and de-facto will enforce the same policy as normal transactions. But it deserves more thought and possibly renaming
| if tx.Type() == types.ArbitrumInternalTxType { | ||
| return nil | ||
| } | ||
| func touchAddresses(db *state.StateDB, ef *eventfilter.EventFilter, tx *types.Transaction, sender common.Address) { |
There was a problem hiding this comment.
I think Diego's (justified) confusion comes from the fact that this function tries to do two things:
both "touch addresses" and apply event filter, while the function with the same name in core.TxFilterer only does one of those things, and eventFilter is applied later.
Either remove eventFilter from touchAddresses, or make the two funcs have different names.
| if config.Sequencer.Enable { | ||
| seqConfigFetcher := func() *SequencerConfig { return &configFetcher.Get().Sequencer } | ||
| sequencer, err = NewSequencer(execEngine, parentChainReader, seqConfigFetcher, seqParentChain) | ||
| if config.TransactionFiltering.TransactionFiltererRPCClient.URL != "" { |
There was a problem hiding this comment.
not critical for this PR - but reporting will also move and will also cover (at least) prechecker
| if n.AddressFilterService != nil { | ||
| n.AddressFilterService.Start(ctx) | ||
| checker := n.AddressFilterService.GetAddressChecker() | ||
| n.ExecEngine.SetAddressChecker(checker) |
There was a problem hiding this comment.
I think we need to move get/set address checker to (n *ExecutionNode) Initialize, and then ExecEngine, when started, will know it has an addresschecker (so it could do things like refuse transactions while the filter is not initialized)
fixes NIT-4344
Pulls in OffchainLabs/go-ethereum#643
Add address filtering to TxPreChecker via gas estimation dry-run. Pre-checker now runs transactions through gasestimator.Run to detect filtered addresses before forwarding, catching sender/recipient, aliased, retryable, and event-based address matches including cascading redeems.