Geth at the core: modified Geth on Arbitrum Nitro
Nitro makes minimal modifications to Geth in hopes of not 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 inside a Geth statedb
. 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.
Hooks
Arbitrum uses various hooks to modify Geth's behavior when processing
transactions. 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.
core.ApplyTransaction
->core.applyTransaction
->core.ApplyMessage
core.NewStateTransition
ReadyEVMForL2
core.TransitionDb
StartTxHook
core.transitionDbImpl
- if
IsArbitrum()
remove tip GasChargingHook
evm.Call
core.vm.EVMInterpreter.Run
PushCaller
PopCaller
core.StateTransition.refundGas
EndTxHook
- if
- added return parameter:
transactionResult
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, that the user can pay, the amount paid for calldata is set aside for later reimbursement of 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 the callers within the EVM callstack, 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 instead of child chain blocks to accommodate the normal use-case of these opcodes, which often assumes Ethereum-like time passes between different 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 come at the expense of the parent chain aggregators and not the network–the amounts paid for the parent chain calldata should not be refunded. This hook provides Geth access to the equivalent amount of child chain gas the poster's cost equals, ensuring reimbursement for this amount doesn't occur for network-incentivized behaviors like freeing storage slots.
EndTxHook
The EndTxHook
calls after the EVM
has returned a transaction's result, allowing one last opportunity for ArbOS to intervene before the 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
a final time, discarding its state as the system moves on to the next transaction, where its contents will renew.
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 is the main interaction between 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 would always be to assume the last 256 block headers were 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.
Tx Type | Represents | Last Hook Reached | Source |
---|---|---|---|
ArbitrumUnsignedTx | A parent chain to child chain message | EndTxHook | Bridge |
ArbitrumContractTx | A nonce-less parent chain to child chain message | EndTxHook | Bridge |
ArbitrumDepositTx | A user deposit | StartTxHook | Bridge |
ArbitrumSubmitRetryableTx | Creating a retryable | StartTxHook | Bridge |
ArbitrumRetryTx | A retryable redeem attempt | EndTxHook | Child chain |
ArbitrumInternalTx | ArbOS state update | StartTxHook | ArbOS |
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. Fore more info, please see the retryables documentation.
ArbitrumRetryTx
These calls get scheduled by calls using the redeem
method of the ArbRetryableTx
precompile and via retryable auto-redemption. Fore more info, please see the retryables documentation.
ArbitrumInternalTx
Because tracing support requires ArbOS's state changes to happen inside a transaction, ArbOS may create a transaction of this type to update its state between user-generated transactions. Such a transaction has a Type
field signifying 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 generates 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. ArbOS uses this info to decide the transaction 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 Mode | Scope | Carries an Underlying Tx? |
---|---|---|
MessageCommitMode | state transition | Always |
MessageGasEstimationMode | gas estimation | When created via NodeInterface or when scheduled |
MessageEthcallMode | eth_calls | Never |
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 should another arrive just before even slightly changing the transaction's code path. 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 gets created, alerting or panicking should conservation be 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/stakers, to store the send count.
Retryable support
ArbOS primarily implements retryables, while Geth requires some modifications to support them.
- Added
ScheduledTxes
field toExecutionResult
. This process lists transactions scheduled during the execution. To enable this field, we also pass theExecutionResult
to callers ofApplyTransaction
. - Added
gasEstimation
param toDoCall
. When enabled,DoCall
will also execute any retryable activated by the original call, which allows estimating gas to enable 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. This interface is used by Arbitrum precompiles and wraps Geth's standard precompiles.
WASM build support
The WASM Arbitrum executable does not support file operations. We created fileutil.go
to wrap fileutil
calls, stubbing them out when building WASM. 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 computing the forked block. 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.