Skip to content

Commit 3a9bb93

Browse files
authored
Merge pull request #4464 from OffchainLabs/mel-batchpostingreport-bugfix
MEL fix number of batch posting reports check bug
2 parents ed5d6bd + d3a17ad commit 3a9bb93

File tree

3 files changed

+120
-26
lines changed

3 files changed

+120
-26
lines changed

arbnode/mel/extraction/message_extraction_function.go

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package melextraction
55
import (
66
"bytes"
77
"context"
8-
"errors"
98
"fmt"
109

1110
"github.com/ethereum/go-ethereum/common"
@@ -148,16 +147,16 @@ func extractMessagesImpl(
148147
// If this message is a batch posting report, we save it for later
149148
// use in this function once we extract messages from batches and
150149
// need to fill in their batch posting report.
151-
if delayed.Message.Header.Kind == arbostypes.L1MessageType_BatchPostingReport || delayed.Message.Header.Kind == arbostypes.L1MessageType_Initialize { // Let's consider the init message as a batch posting report, since it is seen as a batch as well, we can later ignore filling its batchGasCost anyway
150+
if delayed.Message.Header.Kind == arbostypes.L1MessageType_BatchPostingReport {
152151
batchPostingReports = append(batchPostingReports, delayed)
153152
}
154153
}
155154

156155
// Batch posting reports are included in the same transaction as a batch, so there should
157-
// always be the same number of reports as there are batches.
158-
if len(batchPostingReports) != len(batches) {
156+
// be the same or lesser number of reports as there are batches.
157+
if len(batchPostingReports) > len(batches) {
159158
return nil, nil, nil, nil, fmt.Errorf(
160-
"batch posting reports %d do not match the number of batches %d",
159+
"number of batch posting reports %d higher than the number of batches %d",
161160
len(batchPostingReports),
162161
len(batches),
163162
)
@@ -166,6 +165,8 @@ func extractMessagesImpl(
166165
var batchMetas []*mel.BatchMetadata
167166
var messages []*arbostypes.MessageWithMetadata
168167
var serializedBatches [][]byte
168+
var batchPostReportIndex int
169+
var batchPostReportBatchHash common.Hash
169170
for i, batch := range batches {
170171
batchTx := batchTxs[i]
171172
serialized, err := serialize(
@@ -178,31 +179,38 @@ func extractMessagesImpl(
178179
return nil, nil, nil, nil, err
179180
}
180181

181-
batchPostReport := batchPostingReports[i]
182-
if batchPostReport.Message.Header.Kind != arbostypes.L1MessageType_Initialize {
183-
_, _, batchHash, _, _, _, err := parseBatchPostingReport(bytes.NewReader(batchPostReport.Message.L2msg))
184-
if err != nil {
185-
return nil, nil, nil, nil, fmt.Errorf("failed to parse batch posting report: %w", err)
182+
if batchPostReportIndex < len(batchPostingReports) {
183+
batchPostReport := batchPostingReports[batchPostReportIndex]
184+
if (batchPostReportBatchHash == common.Hash{}) {
185+
_, _, batchPostReportBatchHash, _, _, _, err = parseBatchPostingReport(bytes.NewReader(batchPostReport.Message.L2msg))
186+
if err != nil {
187+
return nil, nil, nil, nil, fmt.Errorf("failed to parse batch posting report: %w", err)
188+
}
186189
}
187190
gotHash := crypto.Keccak256Hash(serialized)
188-
if gotHash != batchHash {
189-
return nil, nil, nil, nil, fmt.Errorf(
190-
"batch data hash incorrect %v (wanted %v for batch %v)",
191-
gotHash,
192-
batchHash,
193-
batch.SequenceNumber,
194-
)
191+
if gotHash == batchPostReportBatchHash {
192+
// Fill in the batch gas stats into the batch posting report.
193+
batchPostReport.Message.BatchDataStats = arbostypes.GetDataStats(serialized)
194+
legacyCost := arbostypes.LegacyCostForStats(batchPostReport.Message.BatchDataStats)
195+
batchPostReport.Message.LegacyBatchGasCost = &legacyCost
196+
// Process next report
197+
batchPostReportIndex++
198+
batchPostReportBatchHash = common.Hash{}
195199
}
196-
// Fill in the batch gas stats into the batch posting report.
197-
batchPostReport.Message.BatchDataStats = arbostypes.GetDataStats(serialized)
198-
legacyCost := arbostypes.LegacyCostForStats(batchPostReport.Message.BatchDataStats)
199-
batchPostReport.Message.LegacyBatchGasCost = &legacyCost
200-
} else if !(inputState.DelayedMessagesSeen == 0 && i == 0 && delayedMessages[i] == batchPostReport) {
201-
return nil, nil, nil, nil, errors.New("encountered initialize message that is not the first delayed message and the first batch ")
202200
}
203201
serializedBatches = append(serializedBatches, serialized)
204202
}
205203

204+
// Batch posting reports are included in the same transaction as a batch, so all the
205+
// reports should have been filled in with the batch gas stats
206+
if len(batchPostingReports) != batchPostReportIndex {
207+
return nil, nil, nil, nil, fmt.Errorf(
208+
"not all batch posting reports processed. Have: %d, Processed: %d",
209+
len(batchPostingReports),
210+
batchPostReportIndex,
211+
)
212+
}
213+
206214
// Update the delayed message accumulator in the MEL state.
207215
for _, delayed := range delayedMessages {
208216
if err = state.AccumulateDelayedMessage(delayed); err != nil {

arbnode/mel/extraction/message_extraction_function_test.go

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package melextraction
55
import (
66
"context"
77
"errors"
8+
"fmt"
89
"io"
910
"math/big"
1011
"testing"
@@ -64,11 +65,11 @@ func TestExtractMessages(t *testing.T) {
6465
expectedError: "failed to lookup delayed messages",
6566
},
6667
{
67-
name: "mismatched number of batch posting reports vs batches",
68+
name: "number of batch posting reports higher than batches",
6869
melStateParentHash: prevParentBlockHash,
6970
lookupBatches: emptyLookupBatches, // 0 batches
7071
lookupDelayedMsgs: successfulLookupDelayedMsgs, // 1 batch posting report
71-
expectedError: "batch posting reports 1 do not match the number of batches 0",
72+
expectedError: "number of batch posting reports 1 higher than the number of batches 0",
7273
},
7374
{
7475
name: "batch serialization fails",
@@ -94,7 +95,7 @@ func TestExtractMessages(t *testing.T) {
9495
lookupDelayedMsgs: successfulLookupDelayedMsgs,
9596
serializer: emptySerializer, // Returns nil, hash will be different from parseReport
9697
parseReport: emptyParseReport, // Returns empty hash
97-
expectedError: "batch data hash incorrect",
98+
expectedError: "not all batch posting reports processed.",
9899
},
99100
{
100101
name: "parse sequencer message fails",
@@ -131,6 +132,32 @@ func TestExtractMessages(t *testing.T) {
131132
expectedMessages: 2,
132133
expectedDelayedMsgs: 1,
133134
},
135+
{
136+
// 3 batches, 1 batch posting report whose hash matches only the 2nd batch.
137+
// Exercises the continue path where batch 0's hash doesn't match the report,
138+
// batch 1's hash does match, and batch 2 has no report to check.
139+
name: "OK - multiple batches with subset of reports",
140+
melStateParentHash: prevParentBlockHash,
141+
lookupBatches: threeBatchLookup,
142+
lookupDelayedMsgs: delayedMsgsWithOneReportForSecondBatch,
143+
serializer: indexedSerializer(),
144+
parseReport: parseReportForSecondBatch,
145+
parseSequencerMsg: successfulParseSequencerMsg,
146+
extractBatchMessages: func(ctx context.Context, melState *mel.State, seqMsg *arbstate.SequencerMessage, delayedMsgDB DelayedMessageDatabase) ([]*arbostypes.MessageWithMetadata, error) {
147+
return []*arbostypes.MessageWithMetadata{
148+
{
149+
Message: &arbostypes.L1IncomingMessage{
150+
L2msg: []byte("msg"),
151+
Header: &arbostypes.L1IncomingMessageHeader{Kind: arbostypes.L1MessageType_L2Message},
152+
},
153+
},
154+
}, nil
155+
},
156+
expectedMsgCount: 3, // 1 message per batch * 3 batches
157+
expectedDelayedSeen: 1,
158+
expectedMessages: 3,
159+
expectedDelayedMsgs: 1,
160+
},
134161
}
135162

136163
for _, tt := range tests {
@@ -374,3 +401,60 @@ func failingExtractBatchMessages(
374401
) ([]*arbostypes.MessageWithMetadata, error) {
375402
return nil, errors.New("failed to extract batch messages")
376403
}
404+
405+
// threeBatchLookup returns 3 batches and 3 transactions.
406+
func threeBatchLookup(
407+
ctx context.Context,
408+
parentChainBlock *types.Header,
409+
txFetcher TransactionFetcher,
410+
logsFetcher LogsFetcher,
411+
eventUnpacker EventUnpacker,
412+
) ([]*mel.SequencerInboxBatch, []*types.Transaction, error) {
413+
batches := []*mel.SequencerInboxBatch{{}, {}, {}}
414+
txs := []*types.Transaction{{}, {}, {}}
415+
return batches, txs, nil
416+
}
417+
418+
// delayedMsgsWithOneReportForSecondBatch returns 1 delayed message that is a
419+
// batch posting report whose hash will match the 2nd batch (index 1) produced
420+
// by indexedSerializer.
421+
func delayedMsgsWithOneReportForSecondBatch(
422+
ctx context.Context,
423+
melState *mel.State,
424+
parentChainBlock *types.Header,
425+
txFetcher TransactionFetcher,
426+
logsFetcher LogsFetcher,
427+
) ([]*mel.DelayedInboxMessage, error) {
428+
hash := common.MaxHash
429+
return []*mel.DelayedInboxMessage{
430+
{
431+
Message: &arbostypes.L1IncomingMessage{
432+
L2msg: []byte("report-for-batch1"),
433+
Header: &arbostypes.L1IncomingMessageHeader{
434+
Kind: arbostypes.L1MessageType_BatchPostingReport,
435+
RequestId: &hash,
436+
L1BaseFee: common.Big0,
437+
},
438+
},
439+
},
440+
}, nil
441+
}
442+
443+
// indexedSerializer returns a serializer that produces different bytes for each
444+
// successive call: "batch0", "batch1", "batch2", …
445+
func indexedSerializer() func(context.Context, *mel.SequencerInboxBatch, *types.Transaction, LogsFetcher) ([]byte, error) {
446+
callIndex := 0
447+
return func(ctx context.Context, batch *mel.SequencerInboxBatch, tx *types.Transaction, logsFetcher LogsFetcher) ([]byte, error) {
448+
data := []byte(fmt.Sprintf("batch%d", callIndex))
449+
callIndex++
450+
return data, nil
451+
}
452+
}
453+
454+
// parseReportForSecondBatch returns a hash that matches the serialized bytes
455+
// of the 2nd batch ("batch1") from indexedSerializer.
456+
func parseReportForSecondBatch(
457+
rd io.Reader,
458+
) (*big.Int, common.Address, common.Hash, uint64, *big.Int, uint64, error) {
459+
return nil, common.Address{}, crypto.Keccak256Hash([]byte("batch1")), 0, nil, 0, nil
460+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
### Added
2+
- Fix Message Extraction function to handle cases when number of batch posting reports are not equal to the number of batches

0 commit comments

Comments
 (0)