Skip to content

Add filteredFundsRecipient ArbOS state field and precompile#4347

Merged
Tristan-Wilson merged 3 commits intomasterfrom
filtered-funds-recipient
Feb 10, 2026
Merged

Add filteredFundsRecipient ArbOS state field and precompile#4347
Tristan-Wilson merged 3 commits intomasterfrom
filtered-funds-recipient

Conversation

@Tristan-Wilson
Copy link
Copy Markdown
Member

@Tristan-Wilson Tristan-Wilson commented Feb 6, 2026

When transaction filtering redirects funds away from filtered transactions, the chain owner needs to control where those funds go. This adds a configurable filteredFundsRecipient address to ArbOS state, exposed through ArbOwner (setter + getter) and ArbOwnerPublic (getter). The setter emits a FilteredFundsRecipientSet event for transparency.

When not explicitly set, the zero-address default causes the runtime to fall back to networkFeeAccount as the redirect destination. A centralized FilteredFundsRecipientOrDefault() helper encapsulates this resolution.

All three new methods are version-gated at ArbosVersion_TransactionFiltering.

pulls in OffchainLabs/nitro-precompile-interfaces#32

fixes NIT-4482

When transaction filtering redirects funds away from filtered transactions,
the chain owner needs to control where those funds go. This adds a
configurable filteredFundsRecipient address to ArbOS state, exposed through
ArbOwner (setter + getter) and ArbOwnerPublic (getter). The setter emits a
FilteredFundsRecipientSet event for transparency.

When not explicitly set, the zero-address default causes the runtime to
fall back to networkFeeAccount as the redirect destination. A centralized
FilteredFundsRecipientOrDefault() helper encapsulates this resolution.

All three new methods are version-gated at ArbosVersion_TransactionFiltering.
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 6, 2026

Codecov Report

❌ Patch coverage is 17.39130% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 32.95%. Comparing base (be317a1) to head (bd0206d).
⚠️ Report is 89 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4347      +/-   ##
==========================================
- Coverage   33.18%   32.95%   -0.23%     
==========================================
  Files         488      488              
  Lines       57843    57866      +23     
==========================================
- Hits        19196    19072     -124     
- Misses      35302    35470     +168     
+ Partials     3345     3324      -21     

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 6, 2026

❌ 9 Tests Failed:

Tests completed Failed Passed Skipped
4187 9 4178 0
View the top 3 failed tests by shortest run time
TestSetLatestSnapshotUrl
Stack Traces | 0.040s run time
... [CONTENT TRUNCATED: Keeping last 20 lines]
        runtime/debug.Stack()
        	/opt/hostedtoolcache/go/1.25.6/x64/src/runtime/debug/stack.go:26 +0x5e
        github.com/offchainlabs/nitro/util/testhelpers.RequireImpl({0x2a22370, 0xc000103500}, {0x29fb980, 0x3b493d0}, {0xc000892150, 0x1, 0x1})
        	/home/runner/work/nitro/nitro/util/testhelpers/testhelpers.go:29 +0x55
        github.com/offchainlabs/nitro/cmd/nitro/init.Require(0xc000103500, {0x29fb980, 0x3b493d0}, {0xc000892150, 0x1, 0x1})
        	/home/runner/work/nitro/nitro/cmd/nitro/init/init_test.go:1273 +0x5d
        github.com/offchainlabs/nitro/cmd/nitro/init.startFileServer.func2()
        	/home/runner/work/nitro/nitro/cmd/nitro/init/init_test.go:362 +0xa5
        created by github.com/offchainlabs/nitro/cmd/nitro/init.startFileServer in goroutine 68
        	/home/runner/work/nitro/nitro/cmd/nitro/init/init_test.go:359 +0x285
        
    init_test.go:362: �[31;1m [failed to shutdown server] context canceled �[0;0m
INFO [02-10|12:37:10.896] Set latest snapshot url                  url=http://127.0.0.1:39317/arb1/2024/21/archive.tar.gz
    init_test.go:308: running test case latest file with http url
INFO [02-10|12:37:10.896] Set latest snapshot url                  url=http://some.domain.com/arb1/2024/21/archive.tar.gz
    init_test.go:308: running test case latest file with https url
INFO [02-10|12:37:10.898] Set latest snapshot url                  url=https://some.domain.com/arb1/2024/21/archive.tar.gz
    init_test.go:308: running test case chain and contents with upper case
INFO [02-10|12:37:10.899] Set latest snapshot url                  url=http://127.0.0.1:40355/arb1/2024/21/archive.tar.gz
--- FAIL: TestSetLatestSnapshotUrl (0.04s)
TestDataStreaming_PositiveScenario/Many_senders,_long_messages
Stack Traces | 0.120s run time
... [CONTENT TRUNCATED: Keeping last 20 lines]
        github.com/offchainlabs/nitro/daprovider/data_streaming.testBasic.func1()
        	/home/runner/work/nitro/nitro/daprovider/data_streaming/protocol_test.go:230 +0x19b
        created by github.com/offchainlabs/nitro/daprovider/data_streaming.testBasic in goroutine 206
        	/home/runner/work/nitro/nitro/daprovider/data_streaming/protocol_test.go:223 +0x85
        
    protocol_test.go:230: �[31;1m [] too much time has elapsed since request was signed �[0;0m
WARN [02-10|12:38:36.060] Served datastreaming_start               conn=127.0.0.1:55224 reqid=1 duration=1.539289ms err="too much time has elapsed since request was signed"
INFO [02-10|12:38:36.061] rpc response                             method=datastreaming_start logId=1  err="too much time has elapsed since request was signed" result={} attempt=0 args="[\"0x698b26cb\", \"0x2f\", \"0xd9\", \"0x2780\", \"0xa\", \"0xd11669ffa20cb9d31e51d924c384367f176c6b0b2756ace837e85a5b68c7f81e2a4f55700eca79b06a08f92acf2d758dd940cb47e740ebc8612b78dabd93457b00\"]" errorData=null
    protocol_test.go:230: goroutine 284 [running]:
        runtime/debug.Stack()
        	/opt/hostedtoolcache/go/1.25.6/x64/src/runtime/debug/stack.go:26 +0x5e
        github.com/offchainlabs/nitro/util/testhelpers.RequireImpl({0x161f990, 0xc0001368c0}, {0x16061e0, 0xc000cd0420}, {0x0, 0x0, 0x0})
        	/home/runner/work/nitro/nitro/util/testhelpers/testhelpers.go:29 +0x9f
        github.com/offchainlabs/nitro/daprovider/data_streaming.testBasic.func1()
        	/home/runner/work/nitro/nitro/daprovider/data_streaming/protocol_test.go:230 +0x19b
        created by github.com/offchainlabs/nitro/daprovider/data_streaming.testBasic in goroutine 206
        	/home/runner/work/nitro/nitro/daprovider/data_streaming/protocol_test.go:223 +0x85
        
    protocol_test.go:230: �[31;1m [] too much time has elapsed since request was signed �[0;0m
--- FAIL: TestDataStreaming_PositiveScenario/Many_senders,_long_messages (0.12s)
TestDataStreaming_PositiveScenario
Stack Traces | 0.150s run time
=== RUN   TestDataStreaming_PositiveScenario
--- FAIL: TestDataStreaming_PositiveScenario (0.15s)

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

@mahdy-nasr
Copy link
Copy Markdown
Contributor

I have a couple of questions/concerns about the design and the rationale behind it.

Design concern: fallback to networkFeeAccount

Why do we fall back to networkFeeAccount for restricted funds? From a business-logic perspective, that doesn’t feel like a convincing or safe default.

My expectation would be that restricted funds should go to an isolated escrow-like account, ideally with metadata linking the funds to the original restricted address. That way, if the address is later unrestricted, the owner could reclaim their funds.

We don’t need to implement reclaim/withdrawal functionality right now, but preserving the option by keeping funds in an isolated, untouchable place seems important. Conceptually, networkFeeAccount can spend those funds at any time, which makes the restriction effectively irreversible—even though restriction may be temporary.

Precompile-methods guards

Another question i have, I can see the precompile-methods guards, but why we have more check on other method like AddTransactionFilterer which checks TransactionFilteringFromTime? do we need to duplicate same logic on these 3 new methods or we have already enough?

@tsahee
Copy link
Copy Markdown
Contributor

tsahee commented Feb 9, 2026

Why do we fall back to networkFeeAccount for restricted funds?

It's a reasonable default because funds there can be used by the chain owner, so if chain owner wants to refuns someone they can. If we do an isolated escrow-account the funds will be locked and nobody will be able to use them until we also implement a way to use that escrow account, which is really out of scope.

why we have more check on other method like AddTransactionFilterer which checks TransactionFilteringFromTime?

Adding a transaction filterer changes the security properties of the chain, which is why we don't let the chain owner do it immediately but with a 7-day delay. As long as there is no filterer, security of the chain itself does not change so we don't need to put that guard on other configs.

@Tristan-Wilson Tristan-Wilson added this pull request to the merge queue Feb 10, 2026
Merged via the queue into master with commit 8b10b23 Feb 10, 2026
25 checks passed
@Tristan-Wilson Tristan-Wilson deleted the filtered-funds-recipient branch February 10, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants