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.
Transaction 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.