Skip to content

Commit 21f4267

Browse files
[MEL] - Implement L2 messages accumulation and introduce MessageReader to extract messages from preimages (#4258)
* begin tx recorder for mel * fix recorder * add unit test for tx recorder * fix tx recorder * add changelog and fix lint * Implement receipt recorder for mel validation * code refactor * refactor * Make tx and receipt fetcher in mel-replay to work with recorded preimages * remove debug statement * code refactor * update impl of GetPreimages * reduce code diff * fix test * address PR comments * code refactor * address PR comments * move mel replay code to its own package * implement typeBasedPreimageResolver * Implement L2 messages accumulation and introduce MessageReader to extract messages from preimages * add changelog * fix typos * remove TODO comment * change hash impl of l2 and delayed messages * delete irrelevant test files * do not include batchPostingReport related fields in Hash impl of DelayedInboxMessage * minor fix * bug fixes * bug fixes * update receipt recorder and fetcher impl * address PR comments * Update arbnode/mel/state.go Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> * merge master * address PR comments * address PR comments --------- Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
1 parent 21e4b11 commit 21f4267

22 files changed

+805
-655
lines changed

arbnode/mel/extraction/batch_lookup.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,6 @@ func ParseBatchesFromBlock(
6060
return nil, nil, fmt.Errorf("error fetching tx by hash: %v in ParseBatchesFromBlock: %w ", log.TxHash, err)
6161
}
6262

63-
// Record this log for MEL validation. This is a very cheap operation in native mode
64-
// and is optimized for recording mode as well.
65-
if _, err := logsFetcher.LogsForTxIndex(ctx, parentChainHeader.Hash(), log.TxIndex); err != nil {
66-
return nil, nil, fmt.Errorf("error recording relevant logs: %w", err)
67-
}
68-
6963
batch := &mel.SequencerInboxBatch{
7064
BlockHash: log.BlockHash,
7165
ParentChainBlockNumber: log.BlockNumber,

arbnode/mel/extraction/delayed_message_lookup.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,6 @@ func parseDelayedMessagesFromBlock(
3939
// On Arbitrum One, this is the bridge contract which emits a MessageDelivered event.
4040
if log.Address == melState.DelayedMessagePostingTargetAddress {
4141
relevantLogs = append(relevantLogs, log)
42-
// Record this log for MEL validation. This is a very cheap operation in native mode
43-
// and is optimized for recording mode as well.
44-
if _, err := logsFetcher.LogsForTxIndex(ctx, parentChainHeader.Hash(), log.TxIndex); err != nil {
45-
return nil, fmt.Errorf("error recording relevant logs: %w", err)
46-
}
4742
}
4843
}
4944
if len(relevantLogs) > 0 {
@@ -83,11 +78,6 @@ func parseDelayedMessagesFromBlock(
8378
return nil, err
8479
}
8580
messageData[common.BigToHash(msgNum)] = msg
86-
// Record this log for MEL validation. This is a very cheap operation in native mode
87-
// and is optimized for recording mode as well.
88-
if _, err := logsFetcher.LogsForTxIndex(ctx, parentChainHeader.Hash(), inboxMsgLog.TxIndex); err != nil {
89-
return nil, fmt.Errorf("error recording relevant logs: %w", err)
90-
}
9181
}
9282
for i, parsedLog := range messageDeliveredEvents {
9383
msgKey := common.BigToHash(parsedLog.MessageIndex)

arbnode/mel/extraction/message_extraction_function.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,12 @@ func extractMessagesImpl(
233233
}
234234
for _, msg := range messagesInBatch {
235235
messages = append(messages, msg)
236-
state.MsgCount += 1
237236
if err = state.AccumulateMessage(msg); err != nil {
238237
return nil, nil, nil, nil, fmt.Errorf("failed to accumulate message: %w", err)
239238
}
239+
// Updating of MsgCount is consistent with how DelayedMessagesSeen is updated
240+
// i.e after the corresponding message has been accumulated
241+
state.MsgCount += 1
240242
}
241243
state.BatchCount += 1
242244
batchMetas = append(batchMetas, &mel.BatchMetadata{
@@ -245,5 +247,11 @@ func extractMessagesImpl(
245247
ParentChainBlock: state.ParentChainBlockNumber,
246248
})
247249
}
250+
if len(messages) > 0 {
251+
// Only need to calculate partials once, after all the messages are extracted
252+
if err := state.GenerateMessageMerklePartialsAndRoot(); err != nil {
253+
return nil, nil, nil, nil, err
254+
}
255+
}
248256
return state, messages, delayedMessages, batchMetas, nil
249257
}

arbnode/mel/messages.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/ethereum/go-ethereum/common"
77
"github.com/ethereum/go-ethereum/core/types"
88
"github.com/ethereum/go-ethereum/crypto"
9+
"github.com/ethereum/go-ethereum/rlp"
910

1011
"github.com/offchainlabs/nitro/arbos/arbostypes"
1112
"github.com/offchainlabs/nitro/arbutil"
@@ -57,18 +58,25 @@ func (m *DelayedInboxMessage) AfterInboxAcc() common.Hash {
5758
return crypto.Keccak256Hash(m.BeforeInboxAcc[:], hash)
5859
}
5960

60-
// Hash will replace AfterInboxAcc
6161
func (m *DelayedInboxMessage) Hash() common.Hash {
62-
hash := crypto.Keccak256(
63-
[]byte{m.Message.Header.Kind},
64-
m.Message.Header.Poster.Bytes(),
65-
arbmath.UintToBytes(m.Message.Header.BlockNumber),
66-
arbmath.UintToBytes(m.Message.Header.Timestamp),
67-
m.Message.Header.RequestId.Bytes(),
68-
arbmath.U256Bytes(m.Message.Header.L1BaseFee),
69-
crypto.Keccak256(m.Message.L2msg),
70-
)
71-
return crypto.Keccak256Hash(hash)
62+
encoded, err := rlp.EncodeToBytes(m.WithOnlyMELConsensusFields())
63+
if err != nil {
64+
panic(err)
65+
}
66+
return crypto.Keccak256Hash(encoded)
67+
}
68+
69+
// WithOnlyMELConsensusFields returns a shallow copy of the DelayedInboxMessage with
70+
// only the fields relevant to MEL consensus being present
71+
func (m *DelayedInboxMessage) WithOnlyMELConsensusFields() *DelayedInboxMessage {
72+
return &DelayedInboxMessage{
73+
BlockHash: m.BlockHash,
74+
Message: &arbostypes.L1IncomingMessage{
75+
Header: m.Message.Header,
76+
L2msg: m.Message.L2msg,
77+
},
78+
ParentChainBlockNumber: m.ParentChainBlockNumber,
79+
}
7280
}
7381

7482
type BatchMetadata struct {

arbnode/mel/recording/delayed_msg_database.go

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,58 @@ func NewDelayedMsgDatabase(db ethdb.KeyValueStore, preimages daprovider.Preimage
4444
}, nil
4545
}
4646

47-
func (r *DelayedMsgDatabase) initialize(ctx context.Context, state *mel.State) error {
47+
// ReadDelayedMessage allows for retrieving a delayed message that has been observed by MEL but not yet consumed in a batch
48+
// at a specific index. Underneath the hood, delayed messages are stored in a binary Merkle tree representation to make
49+
// retrieval possible in WASM mode. In recording mode, reading a delayed message records its access in a preimages mapping
50+
func (r *DelayedMsgDatabase) ReadDelayedMessage(ctx context.Context, state *mel.State, index uint64) (*mel.DelayedInboxMessage, error) {
51+
if index == 0 { // Init message
52+
// This message cannot be found in the database as it is supposed to be seen and read in the same block, so we persist that in DelayedMessageBacklog
53+
return state.GetDelayedMessageBacklog().GetInitMsg(), nil
54+
}
55+
if !r.initialized {
56+
if err := r.initialize(state); err != nil {
57+
return nil, fmt.Errorf("error initializing recording database for MEL validation: %w", err)
58+
}
59+
r.initialized = true
60+
}
61+
// Lightweight operation that is needed as state.Clone() clears the seenDelayedMsgsAcc
62+
if err := r.initSeenDelayedMsgsAccForRecording(state); err != nil {
63+
return nil, fmt.Errorf("error initializing seenDelayedMsgsAcc for recording: %w", err)
64+
}
65+
delayed, err := fetchDelayedMessage(r.db, index)
66+
if err != nil {
67+
return nil, err
68+
}
69+
delayedMsgBytes, err := rlp.EncodeToBytes(delayed.WithOnlyMELConsensusFields())
70+
if err != nil {
71+
return nil, err
72+
}
73+
// Leaves in the Merkle tree are double hashed to prevent against second preimage attacks
74+
// or length extension attacks, which is why we need to add both the preimage of the hash
75+
// and the preimage of the hash of the hash to the mapping
76+
hashDelayedHash := crypto.Keccak256(delayed.Hash().Bytes())
77+
r.preimages[common.BytesToHash(hashDelayedHash)] = delayed.Hash().Bytes()
78+
r.preimages[delayed.Hash()] = delayedMsgBytes
79+
return delayed, nil
80+
}
81+
82+
func fetchDelayedMessage(db ethdb.KeyValueStore, index uint64) (*mel.DelayedInboxMessage, error) {
83+
delayed, err := read.Value[mel.DelayedInboxMessage](db, read.Key(schema.MelDelayedMessagePrefix, index))
84+
if err != nil {
85+
return nil, err
86+
}
87+
return &delayed, nil
88+
}
89+
90+
func getState(db ethdb.KeyValueStore, parentChainBlockNumber uint64) (*mel.State, error) {
91+
state, err := read.Value[mel.State](db, read.Key(schema.MelStatePrefix, parentChainBlockNumber))
92+
if err != nil {
93+
return nil, err
94+
}
95+
return &state, nil
96+
}
97+
98+
func (r *DelayedMsgDatabase) initialize(state *mel.State) error {
4899
var acc *merkleAccumulator.MerkleAccumulator
49100
for i := state.ParentChainBlockNumber; i > 0; i-- {
50101
seenState, err := getState(r.db, i)
@@ -87,6 +138,11 @@ func (r *DelayedMsgDatabase) initialize(ctx context.Context, state *mel.State) e
87138
if err != nil {
88139
return err
89140
}
141+
return nil
142+
}
143+
144+
func (r *DelayedMsgDatabase) initSeenDelayedMsgsAccForRecording(state *mel.State) error {
145+
var err error
90146
seenAcc := state.GetSeenDelayedMsgsAcc()
91147
if seenAcc == nil {
92148
seenAcc, err = merkleAccumulator.NewNonpersistentMerkleAccumulatorFromPartials(mel.ToPtrSlice(state.DelayedMessageMerklePartials))
@@ -98,43 +154,3 @@ func (r *DelayedMsgDatabase) initialize(ctx context.Context, state *mel.State) e
98154
state.SetSeenDelayedMsgsAcc(seenAcc)
99155
return nil
100156
}
101-
102-
func (r *DelayedMsgDatabase) ReadDelayedMessage(ctx context.Context, state *mel.State, index uint64) (*mel.DelayedInboxMessage, error) {
103-
if index == 0 { // Init message
104-
// This message cannot be found in the database as it is supposed to be seen and read in the same block, so we persist that in DelayedMessageBacklog
105-
return state.GetDelayedMessageBacklog().GetInitMsg(), nil
106-
}
107-
if !r.initialized {
108-
if err := r.initialize(ctx, state); err != nil {
109-
return nil, fmt.Errorf("error initializing recording database for MEL validation: %w", err)
110-
}
111-
r.initialized = true
112-
}
113-
delayed, err := fetchDelayedMessage(r.db, index)
114-
if err != nil {
115-
return nil, err
116-
}
117-
delayedMsgBytes, err := rlp.EncodeToBytes(delayed)
118-
if err != nil {
119-
return nil, err
120-
}
121-
hashDelayedHash := crypto.Keccak256(delayed.Hash().Bytes())
122-
r.preimages[common.BytesToHash(hashDelayedHash)] = delayedMsgBytes
123-
return delayed, nil
124-
}
125-
126-
func fetchDelayedMessage(db ethdb.KeyValueStore, index uint64) (*mel.DelayedInboxMessage, error) {
127-
delayed, err := read.Value[mel.DelayedInboxMessage](db, read.Key(schema.MelDelayedMessagePrefix, index))
128-
if err != nil {
129-
return nil, err
130-
}
131-
return &delayed, nil
132-
}
133-
134-
func getState(db ethdb.KeyValueStore, parentChainBlockNumber uint64) (*mel.State, error) {
135-
state, err := read.Value[mel.State](db, read.Key(schema.MelStatePrefix, parentChainBlockNumber))
136-
if err != nil {
137-
return nil, err
138-
}
139-
return &state, nil
140-
}

0 commit comments

Comments
 (0)