Skip to content

Commit dd416d9

Browse files
Merge pull request #4591 from OffchainLabs/braga/disable-arbowner-flag
Add flag to disable ArbOwner outside on-chain execution
2 parents 0b031d7 + a77614a commit dd416d9

5 files changed

Lines changed: 125 additions & 3 deletions

File tree

changelog/bragaigor-nit-4744.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
### Configuration
2+
- Add `--execution.disable-arbowner-ethcall` flag to disable ArbOwner precompile calls outside on-chain execution

execution/gethexec/node.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/offchainlabs/nitro/execution/gethexec/addressfilter"
3939
"github.com/offchainlabs/nitro/execution/gethexec/eventfilter"
4040
executionrpcserver "github.com/offchainlabs/nitro/execution/rpcserver"
41+
"github.com/offchainlabs/nitro/gethhook"
4142
"github.com/offchainlabs/nitro/solgen/go/precompilesgen"
4243
"github.com/offchainlabs/nitro/timeboost"
4344
"github.com/offchainlabs/nitro/util"
@@ -181,6 +182,7 @@ type Config struct {
181182
ExposeMultiGas bool `koanf:"expose-multi-gas"`
182183
RPCServer rpcserver.Config `koanf:"rpc-server"`
183184
ConsensusRPCClient rpcclient.ClientConfig `koanf:"consensus-rpc-client" reload:"hot"`
185+
DisableArbOwnerEthCall bool `koanf:"disable-arbowner-ethcall"`
184186

185187
forwardingTarget string
186188
}
@@ -236,6 +238,7 @@ func ConfigAddOptions(prefix string, f *pflag.FlagSet) {
236238
f.Uint64(prefix+".block-metadata-api-cache-size", ConfigDefault.BlockMetadataApiCacheSize, "size (in bytes) of lru cache storing the blockMetadata to service arb_getRawBlockMetadata")
237239
f.Uint64(prefix+".block-metadata-api-blocks-limit", ConfigDefault.BlockMetadataApiBlocksLimit, "maximum number of blocks allowed to be queried for blockMetadata per arb_getRawBlockMetadata query. Enabled by default, set 0 to disable the limit")
238240
f.Bool(prefix+".expose-multi-gas", false, "experimental: expose multi-dimensional gas in transaction receipts")
241+
f.Bool(prefix+".disable-arbowner-ethcall", ConfigDefault.DisableArbOwnerEthCall, "disable ArbOwner precompile calls outside on-chain execution (ethcall, gas estimation)")
239242
LiveTracingConfigAddOptions(prefix+".vmtrace", f)
240243
rpcserver.ConfigAddOptions(prefix+".rpc-server", "execution", f)
241244
rpcclient.RPCClientAddOptions(prefix+".consensus-rpc-client", f, &ConfigDefault.ConsensusRPCClient)
@@ -276,6 +279,7 @@ var ConfigDefault = Config{
276279
BlockMetadataApiBlocksLimit: 100,
277280
VmTrace: DefaultLiveTracingConfig,
278281
ExposeMultiGas: false,
282+
DisableArbOwnerEthCall: false,
279283

280284
RPCServer: rpcserver.DefaultConfig,
281285
ConsensusRPCClient: rpcclient.ClientConfig{
@@ -518,6 +522,13 @@ func (n *ExecutionNode) MarkFeedStart(to arbutil.MessageIndex) containers.Promis
518522

519523
func (n *ExecutionNode) Initialize(ctx context.Context) error {
520524
config := n.configFetcher.Get()
525+
if config.DisableArbOwnerEthCall {
526+
ownerPC := gethhook.GetOwnerPrecompile()
527+
if ownerPC == nil {
528+
return fmt.Errorf("cannot enable disable-arbowner-ethcall: ArbOwner precompile not found")
529+
}
530+
ownerPC.SetDisableEthCall(true)
531+
}
521532
err := n.ExecEngine.Initialize(config.Caching.StylusLRUCacheCapacity, &config.StylusTarget)
522533
if err != nil {
523534
return fmt.Errorf("error initializing execution engine: %w", err)

gethhook/geth-hook.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,22 @@ import (
1010
"github.com/ethereum/go-ethereum/arbitrum/multigas"
1111
"github.com/ethereum/go-ethereum/common"
1212
"github.com/ethereum/go-ethereum/core"
13+
"github.com/ethereum/go-ethereum/core/types"
1314
"github.com/ethereum/go-ethereum/core/vm"
1415
"github.com/ethereum/go-ethereum/log"
1516

1617
"github.com/offchainlabs/nitro/arbos"
1718
"github.com/offchainlabs/nitro/precompiles"
1819
)
1920

21+
// arbOwnerPrecompile holds the *OwnerPrecompile retrieved from the precompile map during init().
22+
// ExecutionNode.Initialize() configures it later when the node config is available.
23+
var arbOwnerPrecompile *precompiles.OwnerPrecompile
24+
25+
func GetOwnerPrecompile() *precompiles.OwnerPrecompile {
26+
return arbOwnerPrecompile
27+
}
28+
2029
type ArbosPrecompileWrapper struct {
2130
inner precompiles.ArbosPrecompile
2231
}
@@ -59,7 +68,13 @@ func init() {
5968

6069
// process arbos precompiles
6170
precompileErrors := make(map[[4]byte]abi.Error)
62-
for addr, precompile := range precompiles.Precompiles() {
71+
arbosPrecompiles := precompiles.Precompiles()
72+
if ownerPC, ok := arbosPrecompiles[types.ArbOwnerAddress].(*precompiles.OwnerPrecompile); ok {
73+
arbOwnerPrecompile = ownerPC
74+
} else {
75+
panic("ArbOwner precompile is not an *OwnerPrecompile, disable-arbowner-ethcall flag will not work")
76+
}
77+
for addr, precompile := range arbosPrecompiles {
6378
for _, errABI := range precompile.Precompile().GetErrorABIs() {
6479
precompileErrors[[4]byte(errABI.ID.Bytes())] = errABI
6580
}

precompiles/wrapper.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ func (wrapper *DebugPrecompile) Name() string {
6161

6262
// OwnerPrecompile is a precompile wrapper for those only chain owners may use
6363
type OwnerPrecompile struct {
64-
precompile ArbosPrecompile
65-
emitSuccess func(mech, bytes4, addr, []byte) error
64+
precompile ArbosPrecompile
65+
emitSuccess func(mech, bytes4, addr, []byte) error
66+
disableEthCall bool
6667
}
6768

6869
func ownerOnly(address addr, impl ArbosPrecompile, emit func(mech, bytes4, addr, []byte) error) (addr, ArbosPrecompile) {
@@ -72,6 +73,10 @@ func ownerOnly(address addr, impl ArbosPrecompile, emit func(mech, bytes4, addr,
7273
}
7374
}
7475

76+
func (wrapper *OwnerPrecompile) SetDisableEthCall(disable bool) {
77+
wrapper.disableEthCall = disable
78+
}
79+
7580
func (wrapper *OwnerPrecompile) Address() common.Address {
7681
return wrapper.precompile.Address()
7782
}
@@ -85,6 +90,10 @@ func (wrapper *OwnerPrecompile) Call(
8590
gasSupplied uint64,
8691
evm *vm.EVM,
8792
) ([]byte, uint64, multigas.MultiGas, error) {
93+
if wrapper.disableEthCall && evm.ProcessingHook.MsgIsNonMutating() {
94+
return nil, gasSupplied, multigas.ZeroGas(), errors.New("ArbOwner precompile is disabled outside on-chain execution")
95+
}
96+
8897
con := wrapper.precompile
8998

9099
burner := &Context{

system_tests/precompile_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import (
1010
"math/big"
1111
"slices"
1212
"sort"
13+
"strings"
1314
"testing"
1415
"time"
1516

1617
"github.com/google/go-cmp/cmp"
1718

19+
"github.com/ethereum/go-ethereum"
1820
"github.com/ethereum/go-ethereum/accounts/abi/bind"
1921
"github.com/ethereum/go-ethereum/common"
2022
"github.com/ethereum/go-ethereum/core/types"
@@ -26,6 +28,7 @@ import (
2628
"github.com/offchainlabs/nitro/arbos/burn"
2729
"github.com/offchainlabs/nitro/arbos/l1pricing"
2830
"github.com/offchainlabs/nitro/cmd/chaininfo"
31+
"github.com/offchainlabs/nitro/gethhook"
2932
"github.com/offchainlabs/nitro/precompiles"
3033
"github.com/offchainlabs/nitro/solgen/go/localgen"
3134
"github.com/offchainlabs/nitro/solgen/go/precompilesgen"
@@ -1357,3 +1360,85 @@ func TestArbDebugOverwriteContractCode(t *testing.T) {
13571360
t.Fatal("expected code B to be", testCodeB, "got", code)
13581361
}
13591362
}
1363+
1364+
func TestDisableArbOwnerEthCall(t *testing.T) {
1365+
ctx, cancel := context.WithCancel(context.Background())
1366+
defer cancel()
1367+
1368+
builder := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise()
1369+
builder.execConfig.DisableArbOwnerEthCall = true
1370+
cleanup := builder.Build(t)
1371+
// The DisableArbOwnerEthCall flag is set on the global OwnerPrecompile singleton,
1372+
// so it persists across tests running in the same process. We must:
1373+
// 1. Not run in parallel (DontParalellise above) to avoid affecting concurrent tests
1374+
// 2. Reset the flag on cleanup to avoid affecting subsequent tests
1375+
defer func() {
1376+
gethhook.GetOwnerPrecompile().SetDisableEthCall(false)
1377+
cleanup()
1378+
}()
1379+
1380+
arbOwnerABI, err := precompilesgen.ArbOwnerMetaData.GetAbi()
1381+
Require(t, err)
1382+
calldata, err := arbOwnerABI.Pack("getAllChainOwners")
1383+
Require(t, err)
1384+
arbOwnerAddr := types.ArbOwnerAddress
1385+
1386+
expectedErrMsg := "ArbOwner precompile is disabled outside on-chain execution"
1387+
1388+
// eth_call should fail
1389+
_, err = builder.L2.Client.CallContract(ctx, ethereum.CallMsg{
1390+
To: &arbOwnerAddr,
1391+
Data: calldata,
1392+
}, nil)
1393+
if err == nil || !strings.Contains(err.Error(), expectedErrMsg) {
1394+
Fatal(t, "eth_call to ArbOwner expected error containing", expectedErrMsg, "got", err)
1395+
}
1396+
1397+
// eth_estimateGas should fail
1398+
_, err = builder.L2.Client.EstimateGas(ctx, ethereum.CallMsg{
1399+
To: &arbOwnerAddr,
1400+
Data: calldata,
1401+
})
1402+
if err == nil || !strings.Contains(err.Error(), expectedErrMsg) {
1403+
Fatal(t, "eth_estimateGas to ArbOwner expected error containing", expectedErrMsg, "got", err)
1404+
}
1405+
1406+
// On-chain transaction should still succeed
1407+
auth := builder.L2Info.GetDefaultTransactOpts("Owner", ctx)
1408+
auth.GasLimit = 32_000_000
1409+
arbOwner, err := precompilesgen.NewArbOwner(arbOwnerAddr, builder.L2.Client)
1410+
Require(t, err)
1411+
tx, err := arbOwner.AddChainOwner(&auth, common.HexToAddress("0xdeadbeef"))
1412+
Require(t, err)
1413+
_, err = builder.L2.EnsureTxSucceeded(tx)
1414+
Require(t, err)
1415+
}
1416+
1417+
func TestArbOwnerEthCallEnabled(t *testing.T) {
1418+
ctx, cancel := context.WithCancel(context.Background())
1419+
defer cancel()
1420+
1421+
builder := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise()
1422+
cleanup := builder.Build(t)
1423+
defer cleanup()
1424+
1425+
arbOwnerABI, err := precompilesgen.ArbOwnerMetaData.GetAbi()
1426+
Require(t, err)
1427+
calldata, err := arbOwnerABI.Pack("getAllChainOwners")
1428+
Require(t, err)
1429+
arbOwnerAddr := types.ArbOwnerAddress
1430+
1431+
// eth_call should succeed when DisableArbOwnerEthCall is false (default)
1432+
_, err = builder.L2.Client.CallContract(ctx, ethereum.CallMsg{
1433+
To: &arbOwnerAddr,
1434+
Data: calldata,
1435+
}, nil)
1436+
Require(t, err)
1437+
1438+
// eth_estimateGas should succeed when DisableArbOwnerEthCall is false (default)
1439+
_, err = builder.L2.Client.EstimateGas(ctx, ethereum.CallMsg{
1440+
To: &arbOwnerAddr,
1441+
Data: calldata,
1442+
})
1443+
Require(t, err)
1444+
}

0 commit comments

Comments
 (0)