Skip to content

Conversation

@MishkaRogachev
Copy link
Contributor

@MishkaRogachev MishkaRogachev commented Dec 24, 2025

Fixes NIT-4152 and NIT-4253
pulls in OffchainLabs/nitro-precompile-interfaces#25
pulls in OffchainLabs/go-ethereum#604
pulls in OffchainLabs/nitro-testnode#169

Changes:

  • Add new precompile ArbFilteredTransactionsManager to manage filtered transactions
  • Add transaction censors to ArbOs and ArbOwner to limit access to ArbFilteredTransactionsManager
  • Add an account, separated from ArbOs state, to store filtered transactions
  • Limit filtered transactions feature with ArbCensoredTransactionManagerFromTime

@MishkaRogachev MishkaRogachev changed the title Add ArbCensoredTransactionsManager precompile (idle) Add ArbCensoredTransactionsManager precompile Dec 24, 2025
@MishkaRogachev MishkaRogachev force-pushed the create-arbcensoredtransactionsmanager-precompile branch from 1adc79f to 81f4e5e Compare December 24, 2025 14:55
@codecov
Copy link

codecov bot commented Dec 24, 2025

Codecov Report

❌ Patch coverage is 28.04233% with 136 lines in your changes missing coverage. Please review.
✅ Project coverage is 29.48%. Comparing base (8dff9a2) to head (2058741).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4174      +/-   ##
==========================================
- Coverage   33.12%   29.48%   -3.64%     
==========================================
  Files         462      464       +2     
  Lines       55958    56132     +174     
==========================================
- Hits        18534    16549    -1985     
- Misses      34168    36582    +2414     
+ Partials     3256     3001     -255     

@github-actions
Copy link

github-actions bot commented Dec 24, 2025

❌ 6 Tests Failed:

Tests completed Failed Passed Skipped
4469 6 4463 0
View the top 3 failed tests by shortest run time
TestEndToEnd_ManyEvilValidators
Stack Traces | -0.000s run time
... [CONTENT TRUNCATED: Keeping last 20 lines]
created by github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast in goroutine 231189
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:107 +0xca

goroutine 3369051 [select]:
github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast.func1()
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:108 +0xc5
created by github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast in goroutine 231189
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:107 +0xca

goroutine 3369005 [select]:
github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast.func1()
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:108 +0xc5
created by github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast in goroutine 231183
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:107 +0xca

goroutine 3369045 [select]:
github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast.func1()
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:108 +0xc5
created by github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast in goroutine 231189
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:107 +0xca
TestEndToEnd_ManyEvilValidators/honest_essential_edges_confirmed_by_challenge_win
Stack Traces | -0.000s run time
... [CONTENT TRUNCATED: Keeping last 20 lines]
created by github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast in goroutine 231189
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:107 +0xca

goroutine 3369051 [select]:
github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast.func1()
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:108 +0xc5
created by github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast in goroutine 231189
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:107 +0xca

goroutine 3369005 [select]:
github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast.func1()
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:108 +0xc5
created by github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast in goroutine 231183
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:107 +0xca

goroutine 3369045 [select]:
github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast.func1()
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:108 +0xc5
created by github.com/offchainlabs/nitro/bold/containers/events.(*Producer[...]).Broadcast in goroutine 231189
	/home/runner/work/nitro/nitro/bold/containers/events/producer.go:107 +0xca
TestRedisProduceComplex/one_producer,_all_consumers_are_active
Stack Traces | 1.240s run time
... [CONTENT TRUNCATED: Keeping last 20 lines]
�[36mDEBUG�[0m[01-13|19:58:48.563] Redis stream consuming                   �[36mconsumer_id�[0m=9a8e3dc2-a5cc-4158-b780-2d42df45b6bf �[36mmessage_id�[0m=1768334327481-2
�[36mDEBUG�[0m[01-13|19:58:48.563] consumer: xack                           �[36mcid�[0m=7f6d3a90-efbb-40f2-acf3-e9996476e592 �[36mmessageId�[0m=1768334327481-0
�[36mDEBUG�[0m[01-13|19:58:48.564] consumer: setting result                 �[36mcid�[0m=9a8e3dc2-a5cc-4158-b780-2d42df45b6bf �[36mmsgIdInStream�[0m=1768334327481-2  �[36mresultKeyInRedis�[0m=result-key:stream:bd34045a-2767-4d32-82e8-fc6e5fbf0e0f.1768334327481-2
�[36mDEBUG�[0m[01-13|19:58:48.563] consumer: xack                           �[36mcid�[0m=983a2145-d97d-4424-afcd-cb386cff495b �[36mmessageId�[0m=1768334327481-1
�[36mDEBUG�[0m[01-13|19:58:48.564] consumer: xack                           �[36mcid�[0m=9a8e3dc2-a5cc-4158-b780-2d42df45b6bf �[36mmessageId�[0m=1768334327481-2
�[33mWARN �[0m[01-13|19:58:48.564] XClaimJustID returned empty response when indicating heartbeat �[33mmsgID�[0m=1768334327463-1
�[33mWARN �[0m[01-13|19:58:48.564] XClaimJustID returned empty response when indicating heartbeat �[33mmsgID�[0m=1768334327463-0
�[36mDEBUG�[0m[01-13|19:58:48.564] consumer: xdel                           �[36mcid�[0m=9aaf5f30-8b7c-4199-85ce-64c9fa10b92c �[36mmessageId�[0m=1768334327461-5
�[36mDEBUG�[0m[01-13|19:58:48.564] consumer: xdel                           �[36mcid�[0m=9a8e3dc2-a5cc-4158-b780-2d42df45b6bf �[36mmessageId�[0m=1768334327481-2
�[36mDEBUG�[0m[01-13|19:58:48.564] consumer: xdel                           �[36mcid�[0m=983a2145-d97d-4424-afcd-cb386cff495b �[36mmessageId�[0m=1768334327481-1
�[36mDEBUG�[0m[01-13|19:58:48.564] consumer: xdel                           �[36mcid�[0m=7f6d3a90-efbb-40f2-acf3-e9996476e592 �[36mmessageId�[0m=1768334327481-0
�[36mDEBUG�[0m[01-13|19:58:48.565] Redis stream consuming                   �[36mconsumer_id�[0m=46c83632-207a-4d1d-8de1-076c41c0cbd8 �[36mmessage_id�[0m=1768334327481-3
�[36mDEBUG�[0m[01-13|19:58:48.565] consumer: setting result                 �[36mcid�[0m=46c83632-207a-4d1d-8de1-076c41c0cbd8 �[36mmsgIdInStream�[0m=1768334327481-3  �[36mresultKeyInRedis�[0m=result-key:stream:bd34045a-2767-4d32-82e8-fc6e5fbf0e0f.1768334327481-3
�[36mDEBUG�[0m[01-13|19:58:48.565] consumer: xack                           �[36mcid�[0m=46c83632-207a-4d1d-8de1-076c41c0cbd8 �[36mmessageId�[0m=1768334327481-3
�[36mDEBUG�[0m[01-13|19:58:48.566] consumer: xdel                           �[36mcid�[0m=46c83632-207a-4d1d-8de1-076c41c0cbd8 �[36mmessageId�[0m=1768334327481-3
�[36mDEBUG�[0m[01-13|19:58:48.603] checkResponses                           �[36mresponded�[0m=88 �[36merrored�[0m=0 �[36mchecked�[0m=98
�[36mDEBUG�[0m[01-13|19:58:48.609] redis producer: check responses starting
�[36mDEBUG�[0m[01-13|19:58:48.615] checkResponses                           �[36mresponded�[0m=10 �[36merrored�[0m=0 �[36mchecked�[0m=10
�[36mDEBUG�[0m[01-13|19:58:48.680] Error destroying a stream group          �[36merror�[0m="dial tcp 127.0.0.1:40401: connect: connection refused"
--- FAIL: TestRedisProduceComplex/one_producer,_all_consumers_are_active (1.24s)

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

@MishkaRogachev MishkaRogachev force-pushed the create-arbcensoredtransactionsmanager-precompile branch 2 times, most recently from 45fb413 to 67c13f2 Compare December 26, 2025 15:01
@MishkaRogachev MishkaRogachev changed the title Add ArbCensoredTransactionsManager precompile Add ArbFilteredTransactionsManager precompile Dec 26, 2025
@MishkaRogachev MishkaRogachev force-pushed the create-arbcensoredtransactionsmanager-precompile branch 3 times, most recently from 7b7a735 to f56f36c Compare December 29, 2025 12:06
@MishkaRogachev MishkaRogachev marked this pull request as ready for review December 29, 2025 13:48
@Tristan-Wilson Tristan-Wilson self-assigned this Dec 29, 2025
Copy link
Member

@Tristan-Wilson Tristan-Wilson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just a minor comment about consistency of the interface with other precompiles.

return filteredState.IsFiltered(txHash)
}

func (con ArbFilteredTransactionsManager) hasAccess(c *Context) (bool, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This signature is slightly different to ArbNativeTokenManager which has

func (con ArbNativeTokenManager) hasAccess(c ctx) bool {
	manager, err := c.State.NativeTokenOwners().IsMember(c.caller)
	return manager && err == nil
}

and therefore the behavior when there is an error is slightly different in this implementation in that if there is an error, then the gas is not burned out.

Unsure if this difference is intentional or not.

@Tristan-Wilson
Copy link
Member

Tristan-Wilson commented Dec 29, 2025

We should consider making ArbFilteredTransactionsManager calls free for transaction censors, similar to how ArbOwner calls are free for chain owners. Currently, censors pay gas for AddFilteredTransaction/DeleteFilteredTransaction, but since they're trusted actors authorized by chain owners, it would make sense to follow the same pattern. This could be done by creating a CensorPrecompile wrapper (similar to OwnerPrecompile in wrapper.go) that checks TransactionCensors().IsMember(caller) and returns gasSupplied with multigas.ZeroGas() on success.

@MishkaRogachev
Copy link
Contributor Author

This could be done by creating a CensorPrecompile wrapper (similar to OwnerPrecompile in wrapper.go) that checks TransactionCensors().IsMember(caller) and returns gasSupplied with multigas.ZeroGas() on success.

@Tristan-Wilson, wdyt about going further: remove ArbNativeToken-style check and return an error right in CensorPrecompile, like in ArbOwner?

"github.com/offchainlabs/nitro/solgen/go/precompilesgen"
)

func TestManageTransactionCensors(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please also test that the transactions that should be free are free? You can look at TestArbNativeTokenManager and getGasUsed inside it for inspiration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really get how to use getGasUsed in this case, since the transaction will still cost something, only the add/delete call is free. I can suggest to use multigas like this:

	tx, err = arbFilteredTxs.AddFilteredTransaction(&censorTxOpts, txHash)
   require.NoError(t, err)
   receipt, err := builder.L2.EnsureTxSucceeded(tx)
   require.NoError(t, err)

   require.Equal(t, uint64(0), receipt.MultiGasUsed.Get(multigas.ResourceKindStorageAccess))

This somehow proves that tx did not perform any storage operations like set and clear

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you check the censor account's balance is unchanged after adding/deleting filtered txs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This precompile wrapper does not make the transaction completely free, but I still believe that this test is useful because it allows to verify that the call goes through the wrapper

@MishkaRogachev MishkaRogachev force-pushed the create-arbcensoredtransactionsmanager-precompile branch from 2fad658 to 85d7766 Compare January 12, 2026 15:40
@MishkaRogachev MishkaRogachev force-pushed the create-arbcensoredtransactionsmanager-precompile branch from 85d7766 to 9d83f9c Compare January 12, 2026 16:18
evm,
)
if err != nil {
return output, gasSupplied, multigas.ZeroGas(), err
Copy link
Contributor

@diegoximenes diegoximenes Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gas charging decision based on if the caller is a filterer should be done independently of this err.
With current implementation, if the caller is not authorized, then this err will not be nil, and multigas.ZeroGas() will be charged.
This is not the wanted behavior.

Also, how about adding a test, that would have caught this?
Not sure yet if it is straightforward to do this test though, so we can discuss it out of github if you find it will take too much time to implement, and evaluate if it is worth to add it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I overlooked it. I don’t see how this exact issue is reachable from a system test, so I added a precompile-level test


// Initially neither owner nor user can modify filtered transactions,
// but both can read (get) filtered status
_, err = arbFilteredTxs.IsTransactionFiltered(ownerCallOpts, txHash)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: check that returned value is false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}
ev, parseErr := arbOwner.ParseTransactionFiltererAdded(*lg)
if parseErr != nil {
continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: why continuing instead of requiring that parseErr is nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

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