Skip to main content

Geth at the core: modified Geth on Arbitrum Nitro

Arbitrum Nitro makes minimal modifications to Geth to avoid violating its assumptions. This section will explore the relationship between Geth and ArbOS, which consists of a series of hooks, interface implementations, and strategic re-appropriations of Geth’s basic types.

We store ArbOS’s state at an address within a Geth statedb (state database). In doing so, ArbOS inherits the statedb’s statefulness and lifetime properties. For example, a transaction's' direct state changes to ArbOS would get discarded upon a revert.

0xA4B05FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF is a fictional account representing ArbOS.

info

Any links on this page may point to older versions of Nitro or our fork of Geth. While we try to keep this up to date, most of this should be stable. Please check the latest releases of Nitro and Geth for the most recent changes.

Hooks

Arbitrum uses various hooks to modify Geth’s behavior during transaction processing. Each provides an opportunity for ArbOS to update its state and make decisions about the transaction during its lifetime. Transactions are applied using Geth's ApplyTransaction function.

Below is ApplyTransaction's callgraph, with additional info on where the various Arbitrum-specific hooks are. Click on any to go to their section. By default, these hooks do nothing to leave Geth's default behavior unchanged, but for chains configured with EnableArbOS set to true, ReadyEVMForL2 installs the alternative child chain hooks.

What follows is an overview of each hook in chronological order.

ReadyEVMForL2

A call to ReadyEVMForL2 installs the other transaction-specific hooks into each Geth EVM right before it performs a state transition. Without this call, the state transition will instead use the default DefaultTxProcessor and get the same results as vanilla Geth. A TxProcessor object carries these hooks and the associated Arbitrum-specific state during the transaction's lifetime.

StartTxHook

Geth calls the StartTxHook before a transaction executes, which allows ArbOS to handle two Arbitrum-specific transaction types.

If the transaction is ArbitrumDepositTx, ArbOS adds balance to the destination account. This approach is safe because the parent chain bridge submits such a transaction only after collecting the same amount of funds on the parent chain.

If the transaction is an ArbitrumSubmitRetryableTx, ArbOS creates a retryable based on the transaction's fields. ArbOS schedules a retry of the new retryable if the transaction includes sufficient gas.

The hook returns true for both transaction types, signifying that the state transition is complete.

GasChargingHook

This fallible hook ensures the user has enough funds to pay their poster’s parent chain calldata costs. If not, the transaction is reverted, and the EVM does not start. In the common case where the user can pay, the amount paid for calldata is set aside for later reimbursement to the poster. All other fees go to the network account, as they represent the transaction’s burden on validators and nodes more generally.

Suppose the user attempts to purchase compute gas over ArbOS's per-block gas limit. In that case, the difference is set aside and refunded later via ForceRefundGas, so only the gas limit is used. Note that the limit observed may not be the same as that seen at the start of the block if ArbOS's larger gas pool falls below the MaxPerBlockGasLimit while processing the block's previous transactions.

PushCaller

These hooks track callers on the EVM call stack, pushing and popping as calls are made and completed. This hook provides ArbSys with info about the callstack, which is used to implement the methods WasMyCallersAddressAliased and MyCallersAddressWithoutAliasing.

L1BlockHash

In Arbitrum, the BlockHash and Number operations return data that relies on the underlying parent chain blocks rather than child chain blocks to accommodate the normal use case of these opcodes, which often assume Ethereum-like time passing between blocks. The L1BlockHash and L1BlockNumber hooks have the required data for these operations.

ForceRefundGas

This hook allows ArbOS to add additional refunds to the user's transaction. The only usage of this hook is to refund any compute gas purchased in excess of ArbOS's per-block gas limit during the GasChargingHook.

NonRefundableGas

Because poster costs are borne by the parent chain aggregators, not the network, payments toward parent chain calldata should not be refunded. This hook provides Geth access to the equivalent amount of child-chain gas that the poster's cost equals, ensuring that this amount isn't reimbursed for network-incentivized behaviors like freeing storage slots.

EndTxHook

The EndTxHook is called after the EVM returns a transaction's result, providing one last opportunity for ArbOS to intervene before state transition finalization. Final gas amounts are known, enabling ArbOS to credit the network and poster share of the user's gas expenditures and adjust the pools. The hook returns from the TxProcessor for the final time, discarding its state as the system moves on to the next transaction, where its contents will be renewed.

Interfaces and components

APIBackend

APIBackend implements the ethapi.Backend interface, which allows a simple integration of the Arbitrum chain to the existing Geth API. The Backend member answers most calls.

Backend

This struct is an Arbitrum equivalent to the Ethereum struct. It is mostly glue logic, including a pointer to the ArbInterface interface.

ArbInterface

This interface serves as the primary interface between the geth-standard APIs and the Arbitrum chain. Geth APIs either check the status by working on the Blockchain struct retrieved from the Blockchain call or send transactions to Arbitrum using the PublishTransactions call.

RecordingKV

RecordingKV is a read-only key-value store that retrieves values from an internal trie database. All values accessed by a RecordingKV get recorded internally. This value records all preimages accessed during block creation, which will be needed to prove the execution of this particular block. A RecordingChainContext should also be used to record which block headers the block execution reads (another option is always to assume that the last 256 block headers have been accessed). The process is simplified using two functions: PrepareRecording creates a stateDB and chain context objects, running block creation process using these objects records the required preimages, and PreimagesFromRecording function extracts the preimages recorded.

Transaction types

Nitro Geth includes a few child chain-specific transaction types. Click any to jump to their section.

Transaction TypeRepresentsLast Hook ReachedSource
ArbitrumUnsignedTxA parent chain to child chain messageEndTxHookBridge
ArbitrumContractTxA nonce-less parent chain to child chain messageEndTxHookBridge
ArbitrumDepositTxA user depositStartTxHookBridge
ArbitrumSubmitRetryableTxCreating a retryableStartTxHookBridge
ArbitrumRetryTxA retryable redeem attemptEndTxHookChild chain
ArbitrumInternalTxArbOS state updateStartTxHookArbOS

The following reference documents each type.

ArbitrumUnsignedTx

It provides a mechanism for a user on a parent chain to message a contract on a child chain. This mechanism uses the bridge for authentication rather than requiring the user's signature. Address remapping of the user's address will occur on the child chain to distinguish them from a normal child chain caller.

ArbitrumContractTx

These are like ArbitrumUnsignedTx's but intended for smart contracts. These use the bridge's unique, sequential nonce rather than requiring the caller to specify their own. A parent chain contract may still use an ArbitrumUnsignedTx, but doing so may necessitate tracking the nonce in the parent chain state.

ArbitrumDepositTx

It represents a user deposit from a parent chain to a child chain. This representation increases the user's balance by the amount deposited on the parent chain.

ArbitrumSubmitRetryableTx

It represents a retryable submission and may schedule an ArbitrumRetryTx if enough gas is available. For more info, please see the retryables documentation.

ArbitrumRetryTx

These calls are scheduled using the redeem method of the ArbRetryableTx precompile and via retryable auto-redemption. For more info, please see the retryables documentation.

ArbitrumInternalTx

Because tracing support requires state changes to occur within a transaction, ArbOS may create a transaction of this type to update its state between user-generated transactions. Such a transaction has a Type indicating the state it will update, though currently this is just future-proofing, as there's only one value it may have. Below are the internal transaction types.

InternalTxStartBlock

It updates the parent chain block number and the parent chain base fee. This transaction is generated whenever a new block gets created. They are guaranteed to be the first in their child block chain.

Transaction run modes and underlying transactions

A geth message may get processed for various purposes. For example, a message may estimate the gas of a contract call, whereas another may perform the corresponding state transition. Nitro Geth denotes the intent behind a message using TxRunMode, which it sets before processing the message. ArbOS uses this info to decide the transaction that the message ultimately constructs.

A message derived from a transaction will carry that transaction in a field accessible via its UnderlyingTransaction method. While this relates to how a given message is used, they are not one-to-one. The table below shows the various run modes and whether each could have an underlying transaction.

Run ModeScopeCarries an Underlying Transaction?
MessageCommitModestate transitionAlways
MessageGasEstimationModegas estimationWhen created via NodeInterface or when scheduled
MessageEthcallModeeth_callsNever

Arbitrum chain parameters

Nitro's Geth is configurable with the following child chain-specific chain parameters. These allow the rollup creator to customize their rollup at genesis.

EnableArbos

Introduces ArbOS, converting what would otherwise be a vanilla parent chain into a child chain Arbitrum rollup.

AllowDebugPrecompiles

Allows access to debug precompiles. Not enabled for Arbitrum One. When false, calls to debut precompiles will always revert.

DataAvailabilityCommittee

Currently, it does nothing besides indicate that the rollup will access a data availability service for preimage resolution in the future. On Arbitrum One, this indication isn't present, which is a strict state function of its parent chain inbox messages.

Miscellaneous Geth changes

ABI Gas Margin

Vanilla Geth's ABI library submits transactions with the exact estimate the node returns, employing no padding. This process means a transaction may revert if another arrives just before it, even if it changes the transaction's code path by just a little. To account for this, we've added a GasMargin field to bind.TransactOpts that pads estimates by the number of basis points set.

Conservation of child chain ETH

The total amount of the child chain ether in the system should not change except in controlled cases, such as when bridging. As a safety precaution, ArbOS checks Geth's balance delta each time a block is created, alerting or panicking if conservation is violated.

MixDigest and ExtraData

The root hash and leaf count of ArbOS's send Merkle accumulator are stored in each child chain block's MixDigest and ExtraData fields to aid with outbox proof construction. The yellow paper specifies that the ExtraData field may be no larger than 32 bytes, so we use the first 8 bytes of the MixDigest, which has no meaning in a system without miners/bonders, to store the send count.

Retryable support

ArbOS primarily implements retryables, while Geth requires some modifications to support them.

  • Added ScheduledTxes field to ExecutionResult. This process lists transactions scheduled during the execution. To enable this field, we also pass the ExecutionResult to callers of ApplyTransaction.
  • Added gasEstimation param to DoCall. When enabled, DoCall will also execute any retryables activated by the original call, allowing gas to be estimated for retryables.

Added accessors

We added UnderlyingTransaction to the Message interface, and GetCurrentTxLogs to StateDB.

We created the AdvancedPrecompile interface, which executes and charges gas with the same function call. Arbitrum uses this interface to precompile and wrap Geth's standard precompiles.

WASM build support

The WASM executable for Arbitrum does not support file operations. We created fileutil.go to wrap fileutil calls and stub them out during WASM builds. fake_leveldb.go is a similar WASM mock for leveldb. The WASM block-replayer does not require these.

Types

Arbitrum introduces a new signer and multiple new transaction types.

ReorgToOldBlock

Geth natively only allows reorgs to a fork of the currently known network. In Nitro, sometimes reorgs can be detected before the forked block is computed. We added the ReorgToOldBlock function to support re-orging to a block that's an ancestor of the current head.

Genesis block creation

The genesis block in Nitro is not necessarily block #0. Nitro supports importing blocks that take place before genesis. We split out WriteHeadBlock from genesis. Commit and use it to commit non-zero genesis blocks.