Skip to content

Commit 02566fc

Browse files
ganeshvanahallirauljordaneljobe
authored
[MEL] - Implement recording of preimages related to sequencer batches (DA providers) (#4133)
* [MEL] - Implement delayed message accumulation in native mode * address PR comments * add documentation for checkAgainstAccumulator and a minor fix * undo changes to addressed review comments from other PRs * dont make L2msg rlp optional * make meldb take a KeyValueStore * handle reorg in start step- reducing code diff * Message extraction function works with logs instead of receipts * only keep delayed message accumulation changes * cleanup non related code * Implement preimage recorder for DelayedMessageDatabase interface * Update cmd/mel-replay/delayed_message_db_test.go * fix test name * code refactor * fix lint * Implement recording of preimages related to sequencer batches (DA providers) * address PR comments * add RecoverPayloadAndPreimages method on DACertificatePreimageReader * implement RecoverPayloadAndPreimages method on EvilDAProvider * fix schema.go * bring in merkle partials calculation step in ExtractMessages function * code refactor * add documentation * add changelog * move recording related code to new package, melrecording * reduce conflicts * address PR comments --------- Co-authored-by: Raul Jordan <[email protected]> Co-authored-by: Pepper Lebeck-Jobe <[email protected]>
1 parent 16cdd83 commit 02566fc

File tree

18 files changed

+250
-59
lines changed

18 files changed

+250
-59
lines changed

arbnode/mel/extraction/message_extraction_function.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func ExtractMessages(
5757
ctx context.Context,
5858
inputState *mel.State,
5959
parentChainHeader *types.Header,
60-
dataProviders *daprovider.DAProviderRegistry,
60+
dapReaders arbstate.DapReaderSource,
6161
delayedMsgDatabase DelayedMessageDatabase,
6262
txFetcher TransactionFetcher,
6363
logsFetcher LogsFetcher,
@@ -68,7 +68,7 @@ func ExtractMessages(
6868
inputState,
6969
parentChainHeader,
7070
chainConfig,
71-
dataProviders,
71+
dapReaders,
7272
delayedMsgDatabase,
7373
txFetcher,
7474
logsFetcher,
@@ -90,7 +90,7 @@ func extractMessagesImpl(
9090
inputState *mel.State,
9191
parentChainHeader *types.Header,
9292
chainConfig *params.ChainConfig,
93-
dataProviders *daprovider.DAProviderRegistry,
93+
dapReaders arbstate.DapReaderSource,
9494
delayedMsgDatabase DelayedMessageDatabase,
9595
txFetcher TransactionFetcher,
9696
logsFetcher LogsFetcher,
@@ -154,6 +154,12 @@ func extractMessagesImpl(
154154
}
155155
state.DelayedMessagesSeen += 1
156156
}
157+
if len(delayedMessages) > 0 {
158+
// Only need to calculate partials once, after all the delayed messages are `seen`
159+
if err := state.GenerateDelayedMessagesSeenMerklePartialsAndRoot(); err != nil {
160+
return nil, nil, nil, nil, err
161+
}
162+
}
157163

158164
// Batch posting reports are included in the same transaction as a batch, so there should
159165
// always be the same number of reports as there are batches.
@@ -207,7 +213,7 @@ func extractMessagesImpl(
207213
batch.SequenceNumber,
208214
batch.BlockHash,
209215
serialized,
210-
dataProviders,
216+
dapReaders,
211217
daprovider.KeysetValidate,
212218
chainConfig,
213219
)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package melrecording
2+
3+
import (
4+
"context"
5+
6+
"github.com/ethereum/go-ethereum/common"
7+
8+
"github.com/offchainlabs/nitro/arbstate"
9+
"github.com/offchainlabs/nitro/daprovider"
10+
"github.com/offchainlabs/nitro/util/containers"
11+
"github.com/offchainlabs/nitro/validator"
12+
)
13+
14+
// RecordingDAPReader implements recording of preimages when melextraction.ExtractMessages function is called by MEL validator for creation
15+
// of validation entry. Since ExtractMessages function would use daprovider.Reader interface to fetch the sequencer batch via RecoverPayload
16+
// we implement collecting of preimages as well in the same method and record it
17+
type RecordingDAPReader struct {
18+
validatorCtx context.Context
19+
reader daprovider.Reader
20+
preimages daprovider.PreimagesMap
21+
}
22+
23+
func (r *RecordingDAPReader) RecoverPayload(batchNum uint64, batchBlockHash common.Hash, sequencerMsg []byte) containers.PromiseInterface[daprovider.PayloadResult] {
24+
promise := r.reader.RecoverPayloadAndPreimages(batchNum, batchBlockHash, sequencerMsg)
25+
result, err := promise.Await(r.validatorCtx)
26+
if err != nil {
27+
return containers.NewReadyPromise(daprovider.PayloadResult{}, err)
28+
}
29+
validator.CopyPreimagesInto(r.preimages, result.Preimages)
30+
return containers.NewReadyPromise(daprovider.PayloadResult{Payload: result.Payload}, nil)
31+
}
32+
33+
func (r *RecordingDAPReader) CollectPreimages(batchNum uint64, batchBlockHash common.Hash, sequencerMsg []byte) containers.PromiseInterface[daprovider.PreimagesResult] {
34+
return r.reader.CollectPreimages(batchNum, batchBlockHash, sequencerMsg)
35+
}
36+
37+
func (r *RecordingDAPReader) RecoverPayloadAndPreimages(batchNum uint64, batchBlockHash common.Hash, sequencerMsg []byte) containers.PromiseInterface[daprovider.PayloadAndPreimagesResult] {
38+
return r.reader.RecoverPayloadAndPreimages(batchNum, batchBlockHash, sequencerMsg)
39+
}
40+
41+
// RecordingDAPReaderSource is used for recording preimages related to sequencer batches stored by da providers, given a
42+
// DapReaderSource it implements GetReader method to return a daprovider.Reader interface that records preimgaes. It takes
43+
// in a context variable (corresponding to creation of validation entry) from the MEL validator
44+
type RecordingDAPReaderSource struct {
45+
validatorCtx context.Context
46+
dapReaders arbstate.DapReaderSource
47+
preimages daprovider.PreimagesMap
48+
}
49+
50+
func NewRecordingDAPReaderSource(validatorCtx context.Context, dapReaders arbstate.DapReaderSource) *RecordingDAPReaderSource {
51+
return &RecordingDAPReaderSource{
52+
validatorCtx: validatorCtx,
53+
dapReaders: dapReaders,
54+
preimages: make(daprovider.PreimagesMap),
55+
}
56+
}
57+
58+
func (s *RecordingDAPReaderSource) GetReader(headerByte byte) daprovider.Reader {
59+
reader := s.dapReaders.GetReader(headerByte)
60+
return &RecordingDAPReader{
61+
validatorCtx: s.validatorCtx,
62+
reader: reader,
63+
preimages: s.preimages,
64+
}
65+
}
66+
67+
func (s *RecordingDAPReaderSource) Preimages() daprovider.PreimagesMap { return s.preimages }

arbnode/mel/runner/recording_database.go renamed to arbnode/mel/recording/delayed_msg_database.go

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,34 @@
1-
package melrunner
1+
package melrecording
22

33
import (
44
"context"
55
"errors"
6+
"fmt"
67

78
"github.com/ethereum/go-ethereum/common"
89
"github.com/ethereum/go-ethereum/crypto"
910
"github.com/ethereum/go-ethereum/ethdb"
1011
"github.com/ethereum/go-ethereum/rlp"
1112

13+
"github.com/offchainlabs/nitro/arbnode/db/read"
14+
"github.com/offchainlabs/nitro/arbnode/db/schema"
1215
"github.com/offchainlabs/nitro/arbnode/mel"
1316
"github.com/offchainlabs/nitro/arbos/merkleAccumulator"
1417
)
1518

16-
// RecordingDatabase holds an ethdb.KeyValueStore that contains delayed messages stored by native MEL and implements DelayedMessageDatabase
19+
// DelayedMsgDatabase holds an ethdb.KeyValueStore that contains delayed messages stored by native MEL and implements DelayedMessageDatabase
1720
// interface defined in 'mel'. It is solely used for recording of preimages relating to delayed messages needed for MEL validation
18-
type RecordingDatabase struct {
19-
db ethdb.KeyValueStore
20-
preimages map[common.Hash][]byte
21+
type DelayedMsgDatabase struct {
22+
db ethdb.KeyValueStore
23+
preimages map[common.Hash][]byte
24+
initialized bool
2125
}
2226

23-
func NewRecordingDatabase(db ethdb.KeyValueStore) *RecordingDatabase {
24-
return &RecordingDatabase{db, make(map[common.Hash][]byte)}
27+
func NewDelayedMsgDatabase(db ethdb.KeyValueStore) *DelayedMsgDatabase {
28+
return &DelayedMsgDatabase{db, make(map[common.Hash][]byte), false}
2529
}
2630

27-
func (r *RecordingDatabase) Initialize(ctx context.Context, state *mel.State) error {
31+
func (r *DelayedMsgDatabase) initialize(ctx context.Context, state *mel.State) error {
2832
var acc *merkleAccumulator.MerkleAccumulator
2933
for i := state.ParentChainBlockNumber; i > 0; i-- {
3034
seenState, err := getState(ctx, r.db, i)
@@ -79,13 +83,19 @@ func (r *RecordingDatabase) Initialize(ctx context.Context, state *mel.State) er
7983
return nil
8084
}
8185

82-
func (r *RecordingDatabase) Preimages() map[common.Hash][]byte { return r.preimages }
86+
func (r *DelayedMsgDatabase) Preimages() map[common.Hash][]byte { return r.preimages }
8387

84-
func (r *RecordingDatabase) ReadDelayedMessage(ctx context.Context, state *mel.State, index uint64) (*mel.DelayedInboxMessage, error) {
88+
func (r *DelayedMsgDatabase) ReadDelayedMessage(ctx context.Context, state *mel.State, index uint64) (*mel.DelayedInboxMessage, error) {
8589
if index == 0 { // Init message
8690
// 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
8791
return state.GetDelayedMessageBacklog().GetInitMsg(), nil
8892
}
93+
if !r.initialized {
94+
if err := r.initialize(ctx, state); err != nil {
95+
return nil, fmt.Errorf("error initializing recording database for MEL validation: %w", err)
96+
}
97+
r.initialized = true
98+
}
8999
delayed, err := fetchDelayedMessage(r.db, index)
90100
if err != nil {
91101
return nil, err
@@ -98,3 +108,19 @@ func (r *RecordingDatabase) ReadDelayedMessage(ctx context.Context, state *mel.S
98108
r.preimages[common.BytesToHash(hashDelayedHash)] = delayedMsgBytes
99109
return delayed, nil
100110
}
111+
112+
func fetchDelayedMessage(db ethdb.KeyValueStore, index uint64) (*mel.DelayedInboxMessage, error) {
113+
delayed, err := read.Value[mel.DelayedInboxMessage](db, read.Key(schema.MelDelayedMessagePrefix, index))
114+
if err != nil {
115+
return nil, err
116+
}
117+
return &delayed, nil
118+
}
119+
120+
func getState(ctx context.Context, db ethdb.KeyValueStore, parentChainBlockNumber uint64) (*mel.State, error) {
121+
state, err := read.Value[mel.State](db, read.Key(schema.MelStatePrefix, parentChainBlockNumber))
122+
if err != nil {
123+
return nil, err
124+
}
125+
return &state, nil
126+
}

arbnode/mel/runner/backlog.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import (
88
)
99

1010
// InitializeDelayedMessageBacklog is to be only called by the Start fsm step of MEL. This function fills the backlog based on the seen and read count from the given mel state
11-
func InitializeDelayedMessageBacklog(ctx context.Context, d *mel.DelayedMessageBacklog, db *Database, state *mel.State, finalizedAndReadIndexFetcher func(context.Context) (uint64, error)) error {
11+
func InitializeDelayedMessageBacklog(ctx context.Context, d *mel.DelayedMessageBacklog, db *Database, state *mel.State, finalizedAndReadIndexFetcher func() (uint64, error)) error {
1212
if state.DelayedMessagesSeen == 0 && state.DelayedMessagesRead == 0 { // this is the first mel state so no need to initialize backlog even if the state isn't finalized yet
1313
return nil
1414
}
1515
finalizedDelayedMessagesRead := state.DelayedMessagesRead // Assume to be finalized, then update if needed
1616
var err error
1717
if finalizedAndReadIndexFetcher != nil {
18-
finalizedDelayedMessagesRead, err = finalizedAndReadIndexFetcher(ctx)
18+
finalizedDelayedMessagesRead, err = finalizedAndReadIndexFetcher()
1919
if err != nil {
2020
return err
2121
}

arbnode/mel/runner/backlog_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func TestDelayedMessageBacklogInitialization(t *testing.T) {
110110
require.NoError(t, err)
111111
newDelayedMessageBacklog, err := mel.NewDelayedMessageBacklog(100, func() (uint64, error) { return 0, nil })
112112
require.NoError(t, err)
113-
require.NoError(t, InitializeDelayedMessageBacklog(ctx, newDelayedMessageBacklog, melDB, newState, func(context.Context) (uint64, error) { return 7, nil }))
113+
require.NoError(t, InitializeDelayedMessageBacklog(ctx, newDelayedMessageBacklog, melDB, newState, func() (uint64, error) { return 7, nil }))
114114
// Notice that instead of having seenUnread list from delayed index 13 to 25 inclusive we will have it from 7 to 25 as only till block=7 the chain has finalized and that block has DelayedMessagesRead=7
115115
require.True(t, newDelayedMessageBacklog.Len() == 19)
116116
newState.SetDelayedMessageBacklog(newDelayedMessageBacklog)

arbnode/mel/runner/database.go

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -70,24 +70,11 @@ func (d *Database) setHeadMelStateBlockNum(batch ethdb.KeyValueWriter, parentCha
7070
}
7171

7272
func (d *Database) GetHeadMelStateBlockNum() (uint64, error) {
73-
parentChainBlockNumberBytes, err := d.db.Get(schema.HeadMelStateBlockNumKey)
74-
if err != nil {
75-
return 0, err
76-
}
77-
var parentChainBlockNumber uint64
78-
err = rlp.DecodeBytes(parentChainBlockNumberBytes, &parentChainBlockNumber)
79-
if err != nil {
80-
return 0, err
81-
}
82-
return parentChainBlockNumber, nil
73+
return read.Value[uint64](d.db, schema.HeadMelStateBlockNumKey)
8374
}
8475

8576
func (d *Database) State(ctx context.Context, parentChainBlockNumber uint64) (*mel.State, error) {
86-
return getState(ctx, d.db, parentChainBlockNumber)
87-
}
88-
89-
func getState(ctx context.Context, db ethdb.KeyValueStore, parentChainBlockNumber uint64) (*mel.State, error) {
90-
state, err := read.Value[mel.State](db, read.Key(schema.MelStatePrefix, parentChainBlockNumber))
77+
state, err := read.Value[mel.State](d.db, read.Key(schema.MelStatePrefix, parentChainBlockNumber))
9178
if err != nil {
9279
return nil, err
9380
}
@@ -116,15 +103,10 @@ func (d *Database) SaveBatchMetas(ctx context.Context, state *mel.State, batchMe
116103
}
117104

118105
func (d *Database) fetchBatchMetadata(seqNum uint64) (*mel.BatchMetadata, error) {
119-
key := read.Key(schema.MelSequencerBatchMetaPrefix, seqNum)
120-
batchMetadataBytes, err := d.db.Get(key)
106+
batchMetadata, err := read.Value[mel.BatchMetadata](d.db, read.Key(schema.MelSequencerBatchMetaPrefix, seqNum))
121107
if err != nil {
122108
return nil, err
123109
}
124-
var batchMetadata mel.BatchMetadata
125-
if err = rlp.DecodeBytes(batchMetadataBytes, &batchMetadata); err != nil {
126-
return nil, err
127-
}
128110
return &batchMetadata, nil
129111
}
130112

@@ -167,11 +149,7 @@ func (d *Database) ReadDelayedMessage(ctx context.Context, state *mel.State, ind
167149
}
168150

169151
func (d *Database) fetchDelayedMessage(index uint64) (*mel.DelayedInboxMessage, error) {
170-
return fetchDelayedMessage(d.db, index)
171-
}
172-
173-
func fetchDelayedMessage(db ethdb.KeyValueStore, index uint64) (*mel.DelayedInboxMessage, error) {
174-
delayed, err := read.Value[mel.DelayedInboxMessage](db, read.Key(schema.MelDelayedMessagePrefix, index))
152+
delayed, err := read.Value[mel.DelayedInboxMessage](d.db, read.Key(schema.MelDelayedMessagePrefix, index))
175153
if err != nil {
176154
return nil, err
177155
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
### Added
2+
- Introduces recording of preimages related to posted sequencer batches to DA providers for MEL validation

cmd/mel-replay/delayed_message_db_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/ethereum/go-ethereum/rlp"
1414

1515
"github.com/offchainlabs/nitro/arbnode/mel"
16+
melrecording "github.com/offchainlabs/nitro/arbnode/mel/recording"
1617
"github.com/offchainlabs/nitro/arbnode/mel/runner"
1718
"github.com/offchainlabs/nitro/arbos/arbostypes"
1819
"github.com/offchainlabs/nitro/arbutil"
@@ -69,8 +70,7 @@ func TestRecordingPreimagesForReadDelayedMessage(t *testing.T) {
6970
require.NoError(t, state.GenerateDelayedMessagesSeenMerklePartialsAndRoot())
7071
require.NoError(t, melDB.SaveState(ctx, state))
7172

72-
recordingDB := melrunner.NewRecordingDatabase(db)
73-
require.NoError(t, recordingDB.Initialize(ctx, state))
73+
recordingDB := melrecording.NewDelayedMsgDatabase(db)
7474
for i := startBlockNum; i < numMsgs; i++ {
7575
require.NoError(t, state.AccumulateDelayedMessage(delayedMessages[i]))
7676
state.DelayedMessagesSeen++

cmd/replay/main.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,18 @@ func (r *DACertificatePreimageReader) CollectPreimages(
243243
})
244244
}
245245

246+
func (r *DACertificatePreimageReader) RecoverPayloadAndPreimages(
247+
batchNum uint64,
248+
batchBlockHash common.Hash,
249+
sequencerMsg []byte,
250+
) containers.PromiseInterface[daprovider.PayloadAndPreimagesResult] {
251+
return containers.DoPromise(context.Background(), func(ctx context.Context) (daprovider.PayloadAndPreimagesResult, error) {
252+
// Stub implementation: RecoverPayloadAndPreimages is only called
253+
// by the MEL validator to gather preimages before validation
254+
return daprovider.PayloadAndPreimagesResult{Preimages: make(daprovider.PreimagesMap), Payload: nil}, nil
255+
})
256+
}
257+
246258
// To generate:
247259
// key, _ := crypto.HexToECDSA("0000000000000000000000000000000000000000000000000000000000000001")
248260
// sig, _ := crypto.Sign(make([]byte, 32), key)

daprovider/anytrust/util/util.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,21 @@ func (d *reader) CollectPreimages(
9292
})
9393
}
9494

95+
// RecoverPayloadAndPreimages fetches the underlying payload and collects preimages from the DA provider given the batch header information
96+
func (d *reader) RecoverPayloadAndPreimages(
97+
batchNum uint64,
98+
batchBlockHash common.Hash,
99+
sequencerMsg []byte,
100+
) containers.PromiseInterface[daprovider.PayloadAndPreimagesResult] {
101+
return containers.DoPromise(context.Background(), func(ctx context.Context) (daprovider.PayloadAndPreimagesResult, error) {
102+
payload, preimages, err := d.recoverInternal(ctx, batchNum, sequencerMsg, true, true)
103+
return daprovider.PayloadAndPreimagesResult{
104+
Payload: payload,
105+
Preimages: preimages,
106+
}, err
107+
})
108+
}
109+
95110
// NewWriter is generally meant to be only used by nitro.
96111
// DA Providers should implement methods in the DAProviderWriter interface independently
97112
func NewWriter(anyTrustWriter Writer, maxMessageSize int) *writer {

0 commit comments

Comments
 (0)