Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
cf064dd
begin tx recorder for mel
rauljordan Dec 11, 2025
d91e37e
fix recorder
rauljordan Dec 12, 2025
20436a8
add unit test for tx recorder
rauljordan Dec 12, 2025
bb28aeb
Merge branch 'raul/mel-inbox-reading' into raul/mel-tx-record
pmikolajczyk41 Dec 19, 2025
0d7e155
Merge branch 'raul/mel-inbox-reading' into raul/mel-tx-record
ganeshvanahalli Dec 23, 2025
c9ab0a0
Merge branch 'raul/mel-inbox-reading' into raul/mel-tx-record
ganeshvanahalli Dec 23, 2025
6f3c0f9
fix tx recorder
ganeshvanahalli Dec 23, 2025
02259cf
add changelog and fix lint
ganeshvanahalli Dec 23, 2025
a4318bd
Implement receipt recorder for mel validation
ganeshvanahalli Dec 30, 2025
b51c239
code refactor
ganeshvanahalli Dec 30, 2025
20ea831
Make tx and receipt fetcher in mel-replay to work with recorded preim…
ganeshvanahalli Jan 2, 2026
27e4d71
Merge branch 'raul/mel-inbox-reading' into raul/mel-tx-record
ganeshvanahalli Jan 5, 2026
e2b6866
Merge branch 'raul/mel-tx-record' into fetching-melobjectsfrom-preimages
ganeshvanahalli Jan 5, 2026
be5fd45
initial mel validator work
ganeshvanahalli Dec 17, 2025
37bce6b
refactor
ganeshvanahalli Dec 18, 2025
8b45fd3
add testing for correctness of preimages
ganeshvanahalli Dec 19, 2025
2cea9a2
add changelog
ganeshvanahalli Jan 5, 2026
2a6119d
minor fixes
ganeshvanahalli Jan 5, 2026
c5ce517
Merge branch 'raul/mel-inbox-reading' into raul/mel-tx-record
ganeshvanahalli Jan 5, 2026
4005e55
Merge branch 'raul/mel-tx-record' into fetching-melobjectsfrom-preimages
ganeshvanahalli Jan 5, 2026
c9c3163
Merge branch 'fetching-melobjectsfrom-preimages' into mel-validator-c…
ganeshvanahalli Jan 5, 2026
9ab227c
remove debug statement
ganeshvanahalli Jan 5, 2026
8fe857e
Merge branch 'fetching-melobjectsfrom-preimages' into mel-validator-c…
ganeshvanahalli Jan 5, 2026
063ce8e
refactor
ganeshvanahalli Jan 5, 2026
985ce53
Merge branch 'raul/mel-tx-record' into fetching-melobjectsfrom-preimages
ganeshvanahalli Jan 5, 2026
c19f416
code refactor
ganeshvanahalli Jan 5, 2026
4247212
Merge branch 'fetching-melobjectsfrom-preimages' into mel-validator-c…
ganeshvanahalli Jan 5, 2026
9457d22
update impl of GetPreimages
ganeshvanahalli Jan 5, 2026
95214e7
Merge branch 'fetching-melobjectsfrom-preimages' into mel-validator-c…
ganeshvanahalli Jan 5, 2026
10aa51d
correctly record preimages for relevant logs
ganeshvanahalli Jan 5, 2026
cec5783
reduce code diff
ganeshvanahalli Jan 5, 2026
1533fe9
Merge branch 'fetching-melobjectsfrom-preimages' into mel-validator-c…
ganeshvanahalli Jan 5, 2026
5aa9a51
begin tx recorder for mel
rauljordan Dec 11, 2025
4065abc
fix recorder
rauljordan Dec 12, 2025
3cbd6a4
add unit test for tx recorder
rauljordan Dec 12, 2025
337bf3c
fix tx recorder
ganeshvanahalli Dec 23, 2025
6f38c63
add changelog and fix lint
ganeshvanahalli Dec 23, 2025
790e83f
Implement receipt recorder for mel validation
ganeshvanahalli Dec 30, 2025
014f267
code refactor
ganeshvanahalli Dec 30, 2025
d7aa9fc
refactor
ganeshvanahalli Jan 5, 2026
59b75ca
Make tx and receipt fetcher in mel-replay to work with recorded preim…
ganeshvanahalli Jan 2, 2026
ccf0e22
remove debug statement
ganeshvanahalli Jan 5, 2026
439c59d
code refactor
ganeshvanahalli Jan 5, 2026
36e255f
update impl of GetPreimages
ganeshvanahalli Jan 5, 2026
472150c
reduce code diff
ganeshvanahalli Jan 5, 2026
ddbd9f4
fix test
ganeshvanahalli Jan 5, 2026
c9c2421
fix test
ganeshvanahalli Jan 5, 2026
af7ef6d
Merge branch 'fetching-melobjectsfrom-preimages' into mel-validator-c…
ganeshvanahalli Jan 5, 2026
a015068
address PR comments
ganeshvanahalli Jan 6, 2026
1658afe
resolve conflicts
ganeshvanahalli Jan 6, 2026
97b40d0
code refactor
ganeshvanahalli Jan 6, 2026
afa84e1
Merge branch 'mel-txandreceipt-recorder' into mel-txandreceipt-fetcher
ganeshvanahalli Jan 6, 2026
95646c2
address PR comments
ganeshvanahalli Jan 6, 2026
5659d42
merge mel-txandreceipt-fetcher
ganeshvanahalli Jan 7, 2026
72e71bd
Merge branch 'mel-txandreceipt-fetcher' into mel-validator-createvali…
ganeshvanahalli Jan 7, 2026
d562843
move mel replay code to its own package
ganeshvanahalli Jan 7, 2026
0346813
Merge branch 'mel-txandreceipt-fetcher' into mel-validator-createvali…
ganeshvanahalli Jan 7, 2026
09c3fb4
implement typeBasedPreimageResolver
ganeshvanahalli Jan 7, 2026
71a8ac5
Merge branch 'mel-txandreceipt-fetcher' into mel-validator-createvali…
ganeshvanahalli Jan 7, 2026
1ab38f9
use typeBasedPreimageResolver in test and update melValidator
ganeshvanahalli Jan 7, 2026
19da777
merge master and resolve conflicts
ganeshvanahalli Jan 9, 2026
9b7a73c
implement more methods of MEL validator and make blockvalidator to on…
ganeshvanahalli Jan 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions arbnode/mel/runner/mel.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ func (m *MessageExtractor) GetHeadState(ctx context.Context) (*mel.State, error)
return m.melDB.GetHeadMelState(ctx)
}

func (m *MessageExtractor) GetState(ctx context.Context, parentchainBlocknumber uint64) (*mel.State, error) {
return m.melDB.State(ctx, parentchainBlocknumber)
}

func (m *MessageExtractor) GetMsgCount(ctx context.Context) (arbutil.MessageIndex, error) {
headState, err := m.melDB.GetHeadMelState(ctx)
if err != nil {
Expand Down
23 changes: 22 additions & 1 deletion arbnode/mel/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"context"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"

"github.com/offchainlabs/nitro/arbos/arbostypes"
"github.com/offchainlabs/nitro/arbos/merkleAccumulator"
"github.com/offchainlabs/nitro/util/arbmath"
)

// State defines the main struct describing the results of processing a single parent
Expand Down Expand Up @@ -77,7 +79,26 @@ type MessageConsumer interface {
}

func (s *State) Hash() common.Hash {
return common.Hash{}
var delayedMerklePartialsBytes []byte
for _, partial := range s.DelayedMessageMerklePartials {
delayedMerklePartialsBytes = append(delayedMerklePartialsBytes, partial.Bytes()...)
}
return crypto.Keccak256Hash(
arbmath.Uint16ToBytes(s.Version),
arbmath.UintToBytes(s.ParentChainId),
arbmath.UintToBytes(s.ParentChainBlockNumber),
s.BatchPostingTargetAddress.Bytes(),
s.DelayedMessagePostingTargetAddress.Bytes(),
s.ParentChainBlockHash.Bytes(),
s.ParentChainPreviousBlockHash.Bytes(),
s.MessageAccumulator.Bytes(),
s.DelayedMessagesSeenRoot.Bytes(),
arbmath.UintToBytes(s.MsgCount),
arbmath.UintToBytes(s.BatchCount),
arbmath.UintToBytes(s.DelayedMessagesRead),
arbmath.UintToBytes(s.DelayedMessagesSeen),
delayedMerklePartialsBytes,
)
}

// Performs a deep clone of the state struct to prevent any unintended
Expand Down
2 changes: 2 additions & 0 deletions changelog/ganeshvanahalli-nit-4142.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### Added
- Introduces MEL validator
10 changes: 0 additions & 10 deletions cmd/mel-replay/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,5 @@

package main

import (
"github.com/ethereum/go-ethereum/common"

"github.com/offchainlabs/nitro/arbutil"
)

type preimageResolver interface {
ResolveTypedPreimage(preimageType arbutil.PreimageType, hash common.Hash) ([]byte, error)
}

func main() {
}
6 changes: 3 additions & 3 deletions cmd/nitro/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import (
"github.com/offchainlabs/nitro/cmd/staterecovery"
"github.com/offchainlabs/nitro/execution/gethexec"
"github.com/offchainlabs/nitro/solgen/go/rollupgen"
"github.com/offchainlabs/nitro/staker/bold"
"github.com/offchainlabs/nitro/staker"
"github.com/offchainlabs/nitro/statetransfer"
"github.com/offchainlabs/nitro/util"
"github.com/offchainlabs/nitro/util/arbmath"
Expand Down Expand Up @@ -957,7 +957,7 @@ func validateGenesisAssertion(ctx context.Context, rollupAddress common.Address,
if err != nil {
return err
}
genesisAssertionCreationInfo, err := bold.ReadBoldAssertionCreationInfo(ctx, userLogic, l1Client, rollupAddress, genesisAssertionHash)
genesisAssertionCreationInfo, err := staker.ReadBoldAssertionCreationInfo(ctx, userLogic, l1Client, rollupAddress, genesisAssertionHash)
if err != nil {
// If we can't find the empty genesis assertion, try to compute the assertion for non-empty genesis
genesisGlobalState := protocol.GoGlobalState{
Expand All @@ -978,7 +978,7 @@ func validateGenesisAssertion(ctx context.Context, rollupAddress common.Address,
if err != nil {
return err
}
genesisAssertionCreationInfo, err = bold.ReadBoldAssertionCreationInfo(ctx, userLogic, l1Client, rollupAddress, genesisAssertionHash)
genesisAssertionCreationInfo, err = staker.ReadBoldAssertionCreationInfo(ctx, userLogic, l1Client, rollupAddress, genesisAssertionHash)
if err != nil {
return err
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/pruning/pruning.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import (
"github.com/offchainlabs/nitro/solgen/go/bridgegen"
"github.com/offchainlabs/nitro/solgen/go/rollupgen"
"github.com/offchainlabs/nitro/staker"
"github.com/offchainlabs/nitro/staker/bold"
legacystaker "github.com/offchainlabs/nitro/staker/legacy"
multiprotocolstaker "github.com/offchainlabs/nitro/staker/multi_protocol"
)
Expand Down Expand Up @@ -258,7 +257,7 @@ func getLatestConfirmedHash(ctx context.Context, rollupAddrs chaininfo.RollupAdd
if err != nil {
return common.Hash{}, err
}
assertion, err := bold.ReadBoldAssertionCreationInfo(
assertion, err := staker.ReadBoldAssertionCreationInfo(
ctx,
rollupUserLogic,
l1Client,
Expand Down
4 changes: 2 additions & 2 deletions cmd/mel-replay/db.go → mel-replay/db.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package melreplay

import (
"errors"
Expand All @@ -14,7 +14,7 @@ import (
var _ ethdb.Database = (*DB)(nil)

type DB struct {
resolver preimageResolver
resolver PreimageResolver
}

func (d *DB) Get(key []byte) ([]byte, error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package melreplay

import (
"bytes"
Expand All @@ -14,7 +14,11 @@ import (
)

type delayedMessageDatabase struct {
preimageResolver preimageResolver
preimageResolver PreimageResolver
}

func NewDelayedMessageDatabase(preimageResolver PreimageResolver) mel.DelayedMessageDatabase {
return &delayedMessageDatabase{preimageResolver}
}

func (d *delayedMessageDatabase) ReadDelayedMessage(
Expand All @@ -27,7 +31,7 @@ func (d *delayedMessageDatabase) ReadDelayedMessage(
if msgIndex >= totalMsgsSeen {
return nil, fmt.Errorf("index %d out of range, total delayed messages seen: %d", msgIndex, totalMsgsSeen)
}
treeSize := nextPowerOfTwo(totalMsgsSeen)
treeSize := NextPowerOfTwo(totalMsgsSeen)
merkleDepth := bits.TrailingZeros64(treeSize)

// Start traversal from root, which is the delayed messages seen root.
Expand Down Expand Up @@ -74,7 +78,7 @@ func (d *delayedMessageDatabase) ReadDelayedMessage(
return delayedMessage, nil
}

func nextPowerOfTwo(n uint64) uint64 {
func NextPowerOfTwo(n uint64) uint64 {
if n == 0 {
return 1
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package melreplay_test

import (
"context"
Expand All @@ -18,24 +18,10 @@ import (
"github.com/offchainlabs/nitro/arbos/arbostypes"
"github.com/offchainlabs/nitro/arbutil"
"github.com/offchainlabs/nitro/daprovider"
"github.com/offchainlabs/nitro/mel-replay"
)

var _ preimageResolver = (*mockPreimageResolver)(nil)
var _ mel.DelayedMessageDatabase = (*delayedMessageDatabase)(nil)

type testPreimageResolver struct {
preimages map[common.Hash][]byte
}

func (r *testPreimageResolver) ResolveTypedPreimage(preimageType arbutil.PreimageType, hash common.Hash) ([]byte, error) {
if preimageType != arbutil.Keccak256PreimageType {
return nil, fmt.Errorf("unsupported preimageType: %d", preimageType)
}
if preimage, ok := r.preimages[hash]; ok {
return preimage, nil
}
return nil, fmt.Errorf("preimage not found for hash: %v", hash)
}
var _ melreplay.PreimageResolver = (*mockPreimageResolver)(nil)

func TestRecordingPreimagesForReadDelayedMessage(t *testing.T) {
ctx := context.Background()
Expand Down Expand Up @@ -89,11 +75,12 @@ func TestRecordingPreimagesForReadDelayedMessage(t *testing.T) {
}

// Test reading in wasm mode
delayedDB := &delayedMessageDatabase{
&testPreimageResolver{
preimages: preimages[arbutil.Keccak256PreimageType],
},
}
delayedDB := melreplay.NewDelayedMessageDatabase(
melreplay.NewTypeBasedPreimageResolver(
arbutil.Keccak256PreimageType,
preimages,
),
)
for i := startBlockNum; i < numMsgsToRead; i++ {
msg, err := delayedDB.ReadDelayedMessage(ctx, state, i)
require.NoError(t, err)
Expand All @@ -104,7 +91,7 @@ func TestRecordingPreimagesForReadDelayedMessage(t *testing.T) {
func TestReadDelayedMessage(t *testing.T) {
ctx := context.Background()
t.Run("message index out of range", func(t *testing.T) {
db := &delayedMessageDatabase{}
db := melreplay.NewDelayedMessageDatabase(nil)
state := &mel.State{
DelayedMessagesSeen: 5,
}
Expand All @@ -122,7 +109,7 @@ func TestReadDelayedMessage(t *testing.T) {
preimages, root := buildMerkleTree(t, messages)

resolver := &mockPreimageResolver{preimages: preimages}
db := &delayedMessageDatabase{preimageResolver: resolver}
db := melreplay.NewDelayedMessageDatabase(resolver)
state := &mel.State{
DelayedMessagesSeen: 1,
DelayedMessagesSeenRoot: root,
Expand Down Expand Up @@ -151,7 +138,7 @@ func TestReadDelayedMessage(t *testing.T) {
preimages, root := buildMerkleTree(t, messages)

resolver := &mockPreimageResolver{preimages: preimages}
db := &delayedMessageDatabase{preimageResolver: resolver}
db := melreplay.NewDelayedMessageDatabase(resolver)
state := &mel.State{
DelayedMessagesSeen: 2,
DelayedMessagesSeenRoot: root,
Expand Down Expand Up @@ -184,7 +171,7 @@ func TestReadDelayedMessage(t *testing.T) {
preimages, root := buildMerkleTree(t, messages)

resolver := &mockPreimageResolver{preimages: preimages}
db := &delayedMessageDatabase{preimageResolver: resolver}
db := melreplay.NewDelayedMessageDatabase(resolver)
state := &mel.State{
DelayedMessagesSeen: 3,
DelayedMessagesSeenRoot: root,
Expand Down Expand Up @@ -218,7 +205,7 @@ func TestNextPowerOfTwo(t *testing.T) {
}

for _, tc := range testCases {
result := nextPowerOfTwo(tc.input)
result := melreplay.NextPowerOfTwo(tc.input)
if result != tc.expected {
t.Errorf("nextPowerOfTwo(%d) = %d, expected %d", tc.input, result, tc.expected)
}
Expand Down
30 changes: 30 additions & 0 deletions mel-replay/mel-replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@
package melreplay

import (
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"

"github.com/offchainlabs/nitro/arbutil"
"github.com/offchainlabs/nitro/daprovider"
)

// RELEVANT_TX_INDEXES_PREFIX represents the prefix appended to a blockHash and the hash of the resulting string
Expand All @@ -15,3 +20,28 @@ const RELEVANT_TX_INDEXES_PREFIX string = "TX_INDEX_DATA"
func RelevantTxIndexesKey(parentChainBlockHash common.Hash) common.Hash {
return crypto.Keccak256Hash([]byte(RELEVANT_TX_INDEXES_PREFIX), parentChainBlockHash.Bytes())
}

type PreimageResolver interface {
ResolveTypedPreimage(preimageType arbutil.PreimageType, hash common.Hash) ([]byte, error)
}

type typeBasedPreimageResolver struct {
ty arbutil.PreimageType
preimagesMap daprovider.PreimagesMap
}

func NewTypeBasedPreimageResolver(ty arbutil.PreimageType, preimagesMap daprovider.PreimagesMap) PreimageResolver {
return &typeBasedPreimageResolver{ty, preimagesMap}
}

func (t *typeBasedPreimageResolver) ResolveTypedPreimage(preimageType arbutil.PreimageType, hash common.Hash) ([]byte, error) {
if preimageType != t.ty {
return nil, fmt.Errorf("unsupported preimageType: %d, want: %d", preimageType, t.ty)
}
if targetMap, ok := t.preimagesMap[preimageType]; ok {
if preimage, ok := targetMap[hash]; ok {
return preimage, nil
}
}
return nil, fmt.Errorf("preimage not found for hash: %v", hash)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2025-2026, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md

package main
package melreplay

import (
"context"
Expand All @@ -15,13 +15,17 @@ import (
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb"

"github.com/offchainlabs/nitro/arbnode/mel/extraction"
"github.com/offchainlabs/nitro/arbutil"
"github.com/offchainlabs/nitro/mel-replay"
)

type receiptFetcherForBlock struct {
header *types.Header
preimageResolver preimageResolver
preimageResolver PreimageResolver
}

func NewLogsFetcher(header *types.Header, preimageResolver PreimageResolver) melextraction.LogsFetcher {
return &receiptFetcherForBlock{header, preimageResolver}
}

// LogsForTxIndex fetches logs for a specific transaction index by walking
Expand Down Expand Up @@ -50,7 +54,7 @@ func (rf *receiptFetcherForBlock) LogsForBlockHash(ctx context.Context, parentCh
if rf.header.Hash() != parentChainBlockHash {
return nil, errors.New("parentChainBlockHash mismatch")
}
relevantTxIndicesKey := melreplay.RelevantTxIndexesKey(rf.header.Hash())
relevantTxIndicesKey := RelevantTxIndexesKey(rf.header.Hash())
txIndexData, err := rf.preimageResolver.ResolveTypedPreimage(arbutil.Keccak256PreimageType, relevantTxIndicesKey)
if err != nil {
return nil, err
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2025-2026, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md

package main
package melreplay_test

import (
"context"
Expand All @@ -19,6 +19,7 @@ import (
"github.com/offchainlabs/nitro/arbnode/mel/recording"
"github.com/offchainlabs/nitro/arbutil"
"github.com/offchainlabs/nitro/daprovider"
"github.com/offchainlabs/nitro/mel-replay"
)

type mockPreimageResolver struct {
Expand Down Expand Up @@ -104,12 +105,13 @@ func TestRecordingOfReceiptPreimagesAndFetchingLogsFromPreimages(t *testing.T) {
// Test reading of logs from the recorded preimages
require.NoError(t, recorder.CollectTxIndicesPreimage())
require.NoError(t, err)
receiptFetcher := &receiptFetcherForBlock{
header: block.Header(),
preimageResolver: &testPreimageResolver{
preimages: preimages[arbutil.Keccak256PreimageType],
},
}
receiptFetcher := melreplay.NewLogsFetcher(
block.Header(),
melreplay.NewTypeBasedPreimageResolver(
arbutil.Keccak256PreimageType,
preimages,
),
)
// Test LogsForBlockHash
logs, err := receiptFetcher.LogsForBlockHash(ctx, block.Hash())
require.NoError(t, err)
Expand Down
4 changes: 2 additions & 2 deletions cmd/mel-replay/trie_fetcher.go → mel-replay/trie_fetcher.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package melreplay

import (
"bytes"
Expand All @@ -14,7 +14,7 @@ import (
// Fetches a specific object at index from a block's Receipt/Tx trie by navigating its
// Merkle Patricia Trie structure. It uses the preimage resolver to fetch preimages
// of trie nodes as needed, and determines how to navigate depending on the structure of the trie nodes.
func fetchObjectFromTrie[T any](root common.Hash, index uint, preimageResolver preimageResolver) (*T, error) {
func fetchObjectFromTrie[T any](root common.Hash, index uint, preimageResolver PreimageResolver) (*T, error) {
var empty *T
currentNodeHash := root
currentPath := []byte{} // Track nibbles consumed so far.
Expand Down
Loading
Loading