Skip to content
Open
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
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
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
d562843
move mel replay code to its own package
ganeshvanahalli Jan 7, 2026
09c3fb4
implement typeBasedPreimageResolver
ganeshvanahalli Jan 7, 2026
de68c31
Merge branch 'master' into mel-txandreceipt-recorder
ganeshvanahalli Jan 9, 2026
5073fdc
Merge branch 'mel-txandreceipt-recorder' into mel-txandreceipt-fetcher
ganeshvanahalli Jan 9, 2026
7cf3fdd
Implement L2 messages accumulation and introduce MessageReader to ext…
ganeshvanahalli Jan 16, 2026
284104c
add changelog
ganeshvanahalli Jan 16, 2026
5456ce3
fix typos
ganeshvanahalli Jan 16, 2026
ea9875a
Merge branch 'master' into mel-txandreceipt-recorder
ganeshvanahalli Jan 16, 2026
f54d9c3
Merge branch 'mel-txandreceipt-recorder' into mel-txandreceipt-fetcher
ganeshvanahalli Jan 16, 2026
31888a4
remove TODO comment
ganeshvanahalli Jan 16, 2026
10f1b0f
Merge branch 'mel-txandreceipt-fetcher' into implement-l2msg-accumula…
ganeshvanahalli Jan 16, 2026
73a1e94
Merge branch 'master' into mel-txandreceipt-recorder
ganeshvanahalli Jan 20, 2026
48d8387
change hash impl of l2 and delayed messages
ganeshvanahalli Jan 20, 2026
20b838a
merge upstream and resolve conflicts
ganeshvanahalli Jan 20, 2026
bd935ef
Merge branch 'mel-txandreceipt-fetcher' into implement-l2msg-accumula…
ganeshvanahalli Jan 20, 2026
3db7bc4
merge master and resolve conflicts
ganeshvanahalli Jan 20, 2026
85b3e26
Merge branch 'mel-txandreceipt-fetcher' into implement-l2msg-accumula…
ganeshvanahalli Jan 20, 2026
d6a5fae
delete irrelevant test files
ganeshvanahalli Jan 20, 2026
b291653
Merge branch 'mel-txandreceipt-fetcher' into implement-l2msg-accumula…
ganeshvanahalli Jan 20, 2026
70c9b20
Merge branch 'master' into implement-l2msg-accumulation
ganeshvanahalli Jan 21, 2026
b7f136a
do not include batchPostingReport related fields in Hash impl of Dela…
ganeshvanahalli Jan 21, 2026
46fa37c
minor fix
ganeshvanahalli Jan 21, 2026
3bb112e
Merge branch 'master' into implement-l2msg-accumulation
ganeshvanahalli Jan 22, 2026
8b1cabf
bug fixes
ganeshvanahalli Jan 22, 2026
16a60d7
Merge branch 'master' into implement-l2msg-accumulation
ganeshvanahalli Jan 26, 2026
69c3b17
bug fixes
ganeshvanahalli Jan 27, 2026
0596e16
update receipt recorder and fetcher impl
ganeshvanahalli Jan 29, 2026
148db4e
Merge branch 'master' into implement-l2msg-accumulation
rauljordan Jan 29, 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
6 changes: 6 additions & 0 deletions arbnode/mel/extraction/batch_lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ func ParseBatchesFromBlock(
return nil, nil, fmt.Errorf("error fetching tx by hash: %v in ParseBatchesFromBlock: %w ", log.TxHash, err)
}

// Record this log for MEL validation. This is a very cheap operation in native mode
// and is optimized for recording mode as well.
if _, err := logsFetcher.LogsForTxIndex(ctx, parentChainHeader.Hash(), log.TxIndex); err != nil {
return nil, nil, fmt.Errorf("error recording relevant logs: %w", err)
}

batch := &mel.SequencerInboxBatch{
BlockHash: log.BlockHash,
ParentChainBlockNumber: log.BlockNumber,
Expand Down
2 changes: 2 additions & 0 deletions arbnode/mel/extraction/batch_lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ func Test_parseBatchesFromBlock(t *testing.T) {
receipts,
trie.NewStackTrie(nil),
)
receipt.BlockHash = block.Hash()
blockLogsFetcher := newMockBlockLogsFetcher(receipts)
eventUnpacker := &mockEventUnpacker{
events: []*bridgegen.SequencerInboxSequencerBatchDelivered{event},
Expand Down Expand Up @@ -324,6 +325,7 @@ func Test_parseBatchesFromBlock_outOfOrderBatches(t *testing.T) {
receipts,
trie.NewStackTrie(nil),
)
receipt.BlockHash = block.Hash()
blockLogsFetcher := newMockBlockLogsFetcher(receipts)
eventUnpacker := &mockEventUnpacker{
events: []*bridgegen.SequencerInboxSequencerBatchDelivered{
Expand Down
10 changes: 10 additions & 0 deletions arbnode/mel/extraction/delayed_message_lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ func parseDelayedMessagesFromBlock(
// On Arbitrum One, this is the bridge contract which emits a MessageDelivered event.
if log.Address == melState.DelayedMessagePostingTargetAddress {
relevantLogs = append(relevantLogs, log)
// Record this log for MEL validation. This is a very cheap operation in native mode
// and is optimized for recording mode as well.
if _, err := logsFetcher.LogsForTxIndex(ctx, parentChainHeader.Hash(), log.TxIndex); err != nil {
return nil, fmt.Errorf("error recording relevant logs: %w", err)
}
}
}
if len(relevantLogs) > 0 {
Expand Down Expand Up @@ -78,6 +83,11 @@ func parseDelayedMessagesFromBlock(
return nil, err
}
messageData[common.BigToHash(msgNum)] = msg
// Record this log for MEL validation. This is a very cheap operation in native mode
// and is optimized for recording mode as well.
if _, err := logsFetcher.LogsForTxIndex(ctx, parentChainHeader.Hash(), inboxMsgLog.TxIndex); err != nil {
return nil, fmt.Errorf("error recording relevant logs: %w", err)
}
}
for i, parsedLog := range messageDeliveredEvents {
msgKey := common.BigToHash(parsedLog.MessageIndex)
Expand Down
9 changes: 9 additions & 0 deletions arbnode/mel/extraction/delayed_message_lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) {
receipts,
trie.NewStackTrie(nil),
)
receipt.BlockHash = block.Hash()
blockLogsFetcher = newMockBlockLogsFetcher(receipts)
_, err := parseDelayedMessagesFromBlock(
ctx,
Expand Down Expand Up @@ -202,6 +203,8 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) {
receipts,
trie.NewStackTrie(nil),
)
receipt1.BlockHash = block.Hash()
receipt2.BlockHash = block.Hash()
blockLogsFetcher = newMockBlockLogsFetcher(receipts)
_, err = parseDelayedMessagesFromBlock(
ctx,
Expand Down Expand Up @@ -300,6 +303,8 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) {
receipts,
trie.NewStackTrie(nil),
)
receipt1.BlockHash = block.Hash()
receipt2.BlockHash = block.Hash()
blockLogsFetcher = newMockBlockLogsFetcher(receipts)
delayedMessages, err := parseDelayedMessagesFromBlock(
ctx,
Expand Down Expand Up @@ -394,6 +399,8 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) {
receipts,
trie.NewStackTrie(nil),
)
receipt1.BlockHash = block.Hash()
receipt2.BlockHash = block.Hash()
blockLogsFetcher = newMockBlockLogsFetcher(receipts)
_, err = parseDelayedMessagesFromBlock(
ctx,
Expand Down Expand Up @@ -492,6 +499,8 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) {
receipts,
trie.NewStackTrie(nil),
)
receipt1.BlockHash = block.Hash()
receipt2.BlockHash = block.Hash()
blockLogsFetcher = newMockBlockLogsFetcher(receipts)
delayedMessages, err := parseDelayedMessagesFromBlock(
ctx,
Expand Down
6 changes: 6 additions & 0 deletions arbnode/mel/extraction/message_extraction_function.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,5 +245,11 @@ func extractMessagesImpl(
ParentChainBlock: state.ParentChainBlockNumber,
})
}
if len(messages) > 0 {
// Only need to calculate partials once, after all the messages are extracted
if err := state.GenerateMessageMerklePartialsAndRoot(); err != nil {
return nil, nil, nil, nil, err
}
}
return state, messages, delayedMessages, batchMetas, nil
}
16 changes: 6 additions & 10 deletions arbnode/mel/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"

"github.com/offchainlabs/nitro/arbos/arbostypes"
"github.com/offchainlabs/nitro/arbutil"
Expand Down Expand Up @@ -59,16 +60,11 @@ func (m *DelayedInboxMessage) AfterInboxAcc() common.Hash {

// Hash will replace AfterInboxAcc
func (m *DelayedInboxMessage) Hash() common.Hash {
hash := crypto.Keccak256(
[]byte{m.Message.Header.Kind},
m.Message.Header.Poster.Bytes(),
arbmath.UintToBytes(m.Message.Header.BlockNumber),
arbmath.UintToBytes(m.Message.Header.Timestamp),
m.Message.Header.RequestId.Bytes(),
arbmath.U256Bytes(m.Message.Header.L1BaseFee),
crypto.Keccak256(m.Message.L2msg),
)
return crypto.Keccak256Hash(hash)
encoded, err := rlp.EncodeToBytes(m)
if err != nil {
panic(err)
}
return crypto.Keccak256Hash(encoded)
}

type BatchMetadata struct {
Expand Down
6 changes: 3 additions & 3 deletions arbnode/mel/recording/dap_reader_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import (
"github.com/offchainlabs/nitro/validator"
)

// DAPReader implements recording of preimages when melextraction.ExtractMessages function is called by MEL validator for creation
// of validation entry. Since ExtractMessages function would use daprovider.Reader interface to fetch the sequencer batch via RecoverPayload
// we implement collecting of preimages as well in the same method and record it
// DAPReader implements recording of data avaialability preimages when melextraction.ExtractMessages function is called by
// MEL validator for creation of validation entry. Since ExtractMessages function would use daprovider.Reader interface to
// fetch the sequencer batch via RecoverPayload we implement collecting of preimages as well in the same method and record it
type DAPReader struct {
validatorCtx context.Context
reader daprovider.Reader
Expand Down
30 changes: 30 additions & 0 deletions arbnode/mel/recording/msg_recording.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package melrecording

import (
"errors"

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

"github.com/offchainlabs/nitro/arbnode/mel"
"github.com/offchainlabs/nitro/arbos/merkleAccumulator"
"github.com/offchainlabs/nitro/arbutil"
"github.com/offchainlabs/nitro/daprovider"
)

// InitializeRecordingMsgPreimages initializes the given state's msgAcc to record preimages related
// to the extracted messages needed for MEL validation into the given preimages map
func InitializeRecordingMsgPreimages(state *mel.State, preimages daprovider.PreimagesMap) error {
if preimages == nil {
return errors.New("preimages recording destination cannot be nil")
}
if _, ok := preimages[arbutil.Keccak256PreimageType]; !ok {
preimages[arbutil.Keccak256PreimageType] = make(map[common.Hash][]byte)
}
acc, err := merkleAccumulator.NewNonpersistentMerkleAccumulatorFromPartials(mel.ToPtrSlice(state.MessageMerklePartials))
if err != nil {
return err
}
acc.RecordPreimagesTo(preimages[arbutil.Keccak256PreimageType])
state.SetMsgsAcc(acc)
return nil
}
48 changes: 38 additions & 10 deletions arbnode/mel/recording/receipt_recorder.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package melrecording

import (
"bytes"
"context"
"errors"
"fmt"
Expand All @@ -16,19 +17,22 @@ import (

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

// ReceiptRecorder records preimages corresponding to the receipts of a parent chain block
// needed during the message extraction. These preimages are needed for MEL validation and
// are used in creation of the validation entries by the MEL validator
type ReceiptRecorder struct {
parentChainReader BlockReader
parentChainBlockHash common.Hash
recordPreimages daprovider.PreimageRecorder
receipts []*types.Receipt
logs []*types.Log
trieDB *triedb.Database
blockReceiptHash common.Hash
parentChainReader BlockReader
parentChainBlockHash common.Hash
parentChainBlockNumber uint64
recordPreimages daprovider.PreimageRecorder
receipts []*types.Receipt
logs []*types.Log
relevantLogsTxIndexes map[uint]struct{}
trieDB *triedb.Database
blockReceiptHash common.Hash
}

// NewReceiptRecorder returns ReceiptRecorder that records
Expand All @@ -42,9 +46,10 @@ func NewReceiptRecorder(
return nil, errors.New("preimages recording destination cannot be nil")
}
return &ReceiptRecorder{
parentChainReader: parentChainReader,
parentChainBlockHash: parentChainBlockHash,
recordPreimages: daprovider.RecordPreimagesTo(preimages),
parentChainReader: parentChainReader,
parentChainBlockHash: parentChainBlockHash,
recordPreimages: daprovider.RecordPreimagesTo(preimages),
relevantLogsTxIndexes: make(map[uint]struct{}),
}, nil
}

Expand Down Expand Up @@ -98,6 +103,7 @@ func (rr *ReceiptRecorder) Initialize(ctx context.Context) error {
rr.receipts = receipts
rr.trieDB = tdb
rr.blockReceiptHash = root
rr.parentChainBlockNumber = block.NumberU64()
return nil
}

Expand All @@ -108,6 +114,9 @@ func (rr *ReceiptRecorder) LogsForTxIndex(ctx context.Context, parentChainBlockH
if rr.parentChainBlockHash != parentChainBlockHash {
return nil, fmt.Errorf("parentChainBlockHash mismatch. expected: %v got: %v", rr.parentChainBlockHash, parentChainBlockHash)
}
if _, recorded := rr.relevantLogsTxIndexes[txIndex]; recorded {
return rr.receipts[txIndex].Logs, nil
}
// #nosec G115
if int(txIndex) >= len(rr.receipts) {
return nil, fmt.Errorf("index out of range: %d", txIndex)
Expand Down Expand Up @@ -141,7 +150,10 @@ func (rr *ReceiptRecorder) LogsForTxIndex(ctx context.Context, parentChainBlockH
// We use this same trick in validation as well in order to link a tx with its logs
for _, log := range receipt.Logs {
log.TxIndex = txIndex
log.BlockHash = parentChainBlockHash
log.BlockNumber = rr.parentChainBlockNumber
}
rr.relevantLogsTxIndexes[txIndex] = struct{}{}
return receipt.Logs, nil
}

Expand All @@ -154,3 +166,19 @@ func (rr *ReceiptRecorder) LogsForBlockHash(ctx context.Context, parentChainBloc
}
return rr.logs, nil
}

// CollectTxIndicesPreimage adds the array of relevant tx indexes to the preimages map as a value
// to the key represented by parentChainBlockHash.
func (rr *ReceiptRecorder) CollectTxIndicesPreimage() error {
var relevantLogsTxIndexes []uint
for k := range rr.relevantLogsTxIndexes {
relevantLogsTxIndexes = append(relevantLogsTxIndexes, k)
}
var buf bytes.Buffer
if err := rlp.Encode(&buf, relevantLogsTxIndexes); err != nil {
return err
}
relevantTxIndicesKey := melreplay.RelevantTxIndexesKey(rr.parentChainBlockHash)
rr.recordPreimages(relevantTxIndicesKey, buf.Bytes(), arbutil.Keccak256PreimageType)
return nil
}
82 changes: 0 additions & 82 deletions arbnode/mel/recording/receipt_recorder_test.go

This file was deleted.

Loading
Loading