Layer 2: Deeper Understanding of Arbitrum

Arbitrum is a scheme for Layer2 Rollup. A challenge mechanism is used to determine the finality of Rollup states. In order to introduce a lightweight challenge mechanism, Arbitrum defines AVM, which simulates the execution environment of EVM on AVM, compatible with EVM.

Arbitrum is a scheme for Layer2 Rollup. Similar to Optimism, the finality of the state is guaranteed by a “challenge” mechanism, where the challenge method is to simulate the execution of a transaction completely in Layer1 and determine whether the state of the transaction is correct after execution. Arbitrum’s challenge is relatively lightweight, in that it executes an operation (AVM) in Layer 1 and determines whether the operation was executed correctly. The advantage of the AVM virtual machine is that the underlying structure facilitates state proofs.

Arbitrum’s developer documentation describes the Arbitrum architecture and design in detail.

Overall Framework

Arbitrum’s developer documentation gives the relationships of the modules.

Layer 2: Deeper Understanding of Arbitrum

Arbitrum’s system consists of three main parts (the right part of the diagram, from bottom to top): EthBridge, the AVM execution environment and ArbOS. EthBridge mainly implements inbox/outbox management and the Rollup protocol. ethBridge is implemented in Layer1. ArbOS executes EVM on an AVM virtual machine. Simply put, Arbitrum implements the AVM virtual machine in Layer2 and then simulates the EVM execution environment on the virtual machine. The reason for using AVM and then emulating EVM is that the state of AVM is better expressed for Layer1 challenges.

The source code corresponding to the EthBridge and AVM execution environments are

Source code for ArbOS:

This diagram of module relationships is too general and is further broken down as follows.

Layer 2: Deeper Understanding of Arbitrum

EthBridge implements three main functions: inbox, outbox and Rollup protocol. inbox “stores” transaction information, which is “synchronized” to ArbOS and executed. outbox “stores” transactions from L2 to L1, mainly withdrawl transactions. The outbox “stores” transactions from L2 to L1, mainly withdrawl transactions. the Rollup protocol is mainly for L2 state storage and challenges. In particular, all transactions in Arbitrum are submitted to L1 first and then executed in ArbOS, which implements the EVM simulator in addition to some external interfaces. The whole simulator is implemented on top of AVM. The entire EVM simulator is implemented in the mini language and Arbitrum implements the mini language compiler on top of AVM. In short, Arbitrum defines a new hardware (machine) and instruction set and implements an upper layer language mini. with the mini language, Arbitrum implements the EVM simulator and can execute the corresponding transactions.

AVM State

Since all transactions are executed in AVM, the execution state of a transaction can be represented by the AVM state. the code for the AVM implementation is available in arbitrum/packages/arb-avm-cpp.

Layer 2: Deeper Understanding of Arbitrum

The state of AVM consists of PC, Stack, Register, etc. The state of AVM is the hash result of stitching the hash values of these states.

AVM is implemented in c++, and the logic of AVM representation is implemented in the machineHash function (machinestate.cpp) of MachineStateKeys class. the special feature of AVM is that it is easier to express (prove) the execution state in addition to execution. In-depth understanding of the basic data structure of AVM, the basic data types of AVM include

using value = std::variant; enum ValueTypes { NUM, CODEPT, HASH_PRE_IMAGE, TUPLE, BUFFER = 12, CODE_POINT_STUB = 13 }
uint256_t – integer type

CodePoint – current code instruction representation

Tuple – tuple, consisting of 8 Values. An element in a tuple can still be a tuple

Buffer – array, up to 2^64

HashPreImage – fixed hash type, hashValue = hash(value, prevHashValue)

Each data type can very easily calculate its hash value as state in addition to its data representation. Take a detailed look at the CodePoint and Tuple basic data types.

The CodePoint type “bundles” multiple operations together, each CodePoint includes the hash information of the previous CodePoint in addition to the current Operation. The CodePoint type is defined in: packages/arb-avm-cpp/avm_values/include/avm _values/codepoint.hpp.

struct CodePoint { Operation op; uint256_t nextHash; CodePoint(Operation op_, uint256_t nextHash_) : op(op_), nextHash(nextHash_) {} bool isError() const { return nextHash == 0 && op == Operation{static_cast(0)}; }
Tuple type is implemented by RawTuple. rawTuple is composed of a set of values. tuple is limited to a maximum of 8 values.

struct RawTuple { HashPreImage cachedPreImage; std::vector data; bool deferredHashing = true; RawTuple() : cachedPreImage({ }, 0), deferredHashing(true) {} };
The type definition of Tuple is in: packages/arb-avm-cpp/avm_values/include/avm_values/tuple.hpp.

With the base types understood, a DataStack can be implemented by a series of Tuple.

Layer 2: Deeper Understanding of Arbitrum

To summarize, the state of PC, Stack, Register, etc. in AVM can be represented by the hash result. the whole state of AVM is represented by the hash of the spliced data of these hash values.

Rollup Challenge

When the states submitted to L1 are divergent, both challenge parties (Asserter and Challenger) first split the states to find the “divergent points”. The challenge processing logic of L1 is implemented in arb-bridge-eth/contracts/challenge/Challenge.sol. The whole challenge mechanism is guaranteed by a timeout mechanism, and the process is simplified to highlight the core process as shown in the following figure.

Layer 2: Deeper Understanding of Arbitrum

The challenger initiates the challenge by using the initializeChallenge function. Next, Challenger and Asserter determine the non-divisible “divergence point” through bisectExecution. After determining the bisect point, the Challenger determines if the Assert’s previously submitted state is correct by using the oneStepProveExecution function.


function initializeChallenge( IOneStepProof[] calldata executors, address _resultReceiver, bytes32 _executionHash, uint256 maxMessageCount, address _asserter, address _challenger, uint256 _asserterTimeLeft, uint256 _challengerTimeLeft, IBridge _bridge ) external override { … asserter = _asserter; challenger = _challenger; … turn = Turn.Challenger; challengeState = _executionHash; … }
initializeChallenge identifies the challenger and asserter and determines the state to be challenged (stored in challengeState). challengeState is the root of a merekle tree consisting of one and multiple bisectionChunk state hashes.

The whole execution process can be partitioned into multiple small processes, each of which (bisection) is represented by a start and end gas and state.

turn is used to record the interaction order. turn = Turn.Challenger indicates that after initializing the challenge, the divergence point partition is first initiated by Challenger.

Layer 2: Deeper Understanding of Arbitrum


bisectExecution picks the previously split fragment and, if possible, splits the fragment again.

The function definition of bisectExecution is as follows.

function bisectExecution( bytes32[] calldata merkleNodes, uint256 _merkleRoute, uint256 _challengedSegmentStart, uint256 challengedSegmentLength, bytes32 _oldEndHash, uint256 _gasUsedBefore, bytes32 _assertionRest, bytes32[] calldata _chainHashes ) external onlyOnTurn {
_chainHashes is the state of the split point again. If it is necessary to split again, the number of split points must be satisfied as follows

uint256 private constant EXECUTION_BISECTION_DEGREE = 400; require( chainHashes.length == bisectionDegree( challengedSegmentLength, EXECUTION_BISECTION_DEGREE) + 1, “CUT_COUNT” );
Simply put, each time it is divided, it must be divided into 400 copies.

The _oldEndHash is used to verify the state that the segmentation fragment of this split is one of the previous split. Need to check the validity of the split.

require(_chainHashes[_chainHashes.length – 1] ! = _oldEndHash, “SAME_END”); require( _chainHashes[0] == ChallengeLib.assertionHash(_gasUsedBefore, _assertionRest), “segment pre fields” ); require(_chainHashes[0] ! = UNREACHABLE_ASSERTION, “UNREACHABLE_START”); require( _gasUsedBefore < _challengedSegmentStart.add(_challengedSegmentLength), ” invalid segment length” );
The start state is correct. This segmentation cannot go beyond the last segmentation, and the last state is not the same as the end state of the previous segmentation.

bytes32 bisectionHash = ChallengeLib.bisectionChunkHash( challengedSegmentStart, _challengedSegmentLength, _chainHashes[0], oldEndHash ); verifySegmentProof(bisectionHash, _merkleNodes, _merkleRoute);
Determine if the start and end states are from some previous segmentation by checking the route of the merkle tree.

updateBisectionRoot(_chainHashes, _challengedSegmentStart, _challengedSegmentLength);
Update the challengeState corresponding to the segmentation.

  • Layer 2: Deeper Understanding of Arbitrum


When the split is not possible, the challenger provides the initial state (proof) and the L1 performs the corresponding computation. The result of the computation should be inconsistent with the provided _oldEndHash. Inconsistency means that the challenger successfully proved that the previous computation was incorrect.

(uint64 gasUsed, uint256 totalMessagesRead, bytes32[4] memory proofFields) = executors[prover].executeStep( bridge, _ initialMessagesRead, [_initialSendAcc, _initialLogAcc], _executionProof, _bufferProof );
The correct end state is calculated via executeStep. executeStep implementation is in packages/arb-bridge-eth/contracts/arch/OneStepProofCommon.sol. The core is the executeOp function, which reads the op for the current context, executes it and updates the status. Interested partners can check it out for themselves.

rootHash = ChallengeLib.bisectionChunkHash( challengedSegmentStart, _challengedSegmentLength, oneStepProofExecutionBefore( initialMessagesRead, _initialSendAcc, _initialLogAcc, _initialState, proofFields ), _oldEndHash ); } verifySegmentProof(rootHash, _merkleNodes, _merkleRoute);
Determines that the initial and ending states are some split from the previous challenge state. The initial state is obtained by the calculation of the proof (proof) provided.

require( oldEndHash ! = oneStepProofExecutionAfter( _initialSendAcc, _initialLogAcc, _initialState, gasUsed, totalMessagesRead, proofFields ), “WRONG END” );
Verify that _oldEndHash is not the same as the calculation to get the end state. It is only if they are not the same that the previously submitted end state is wrong.

After the calculation is complete, determine the winning party.

To summarize.

Arbitrum is a scheme for Layer2 Rollup. A challenge mechanism is used to determine the finality of the Rollup state. To introduce a lightweight challenge mechanism, Arbitrum defines AVM, a virtual machine that can easily prove the execution state, and designs a mini language and compiler. The execution environment of the EVM is simulated on the AVM and is compatible with the EVM. the execution process is partitioned by 400 points during the challenge, and a small number of instructions are executed by L1 to determine whether the state is correct.

Posted by:CoinYuppie,Reprinted with attribution to:
Coinyuppie is an open information publishing platform, all information provided is not related to the views and positions of coinyuppie, and does not constitute any investment and financial advice. Users are expected to carefully screen and prevent risks.

Like (0)
Donate Buy me a coffee Buy me a coffee
Previous 2021-06-23 03:28
Next 2021-06-23 03:35

Related articles