forked from ethereum/go-ethereum
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(rollup): sync finalized batches from L1 (ethereum#515)
* feat(rollup): sync finalized batches from L1 * tweak * address comments * address comments * tweak * address comments * Update rollup/eventwatcher/eventwatcher.go Co-authored-by: Péter Garamvölgyi <peter@scroll.io> * address comments * address comments * address comments * add s.rollupSyncService.Stop() * add block chunk batch tests * fix goimports-lint * address comments * address comments * remove * add TestUnpackLog test * add decodeChunkRanges tests * add commit batch version check * fix goimport & golint * tweak * fixes * add TestRollupSyncServiceStartAndStop * add TestGetChunkRanges * address comments * add TestValidateBatch * add TestCalculateFinalizedBatchMeta * address comments * address comments * fix * address comments * address comments * fix * tweak log * fix * tweak logs * increase wait time * add --rollup.verify flag * add flag check in s.rollupSyncService.Stop() * bump version * refactor * tweak configs * tweak * refactor getLocalInfoForBatch * use syscall.Kill(os.Getpid(), syscall.SIGTERM) to shutdown * refactor decodeChunkBlockRanges * nit * address comments * address comments * add WithdrawRoot and StateRoot in FinalizedBatchMeta * address comments * address comments * add ctx.Err() check in retry * add configs in gen_config.go * bump version * fix goimport & golint * try to fix goimport * revert change * fix goimport & golint * tweak * bump version * add l2 block -> batch index mapping * bump version * add mainnet config * bump version * address comments * rename some vars * feat: add more detailed logs in crash case (ethereum#547) add more detailed logs in crash case * trigger ci * re-add batch finalization progress log * remove block number -> batch index mapping --------- Co-authored-by: Péter Garamvölgyi <peter@scroll.io> Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
- Loading branch information
1 parent
bbf4204
commit 4412122
Showing
28 changed files
with
19,845 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package rawdb | ||
|
||
import ( | ||
"bytes" | ||
"math/big" | ||
|
||
"github.com/scroll-tech/go-ethereum/common" | ||
"github.com/scroll-tech/go-ethereum/ethdb" | ||
"github.com/scroll-tech/go-ethereum/log" | ||
"github.com/scroll-tech/go-ethereum/rlp" | ||
) | ||
|
||
// ChunkBlockRange represents the range of blocks within a chunk. | ||
type ChunkBlockRange struct { | ||
StartBlockNumber uint64 | ||
EndBlockNumber uint64 | ||
} | ||
|
||
// FinalizedBatchMeta holds metadata for finalized batches. | ||
type FinalizedBatchMeta struct { | ||
BatchHash common.Hash | ||
TotalL1MessagePopped uint64 // total number of L1 messages popped before and in this batch. | ||
StateRoot common.Hash | ||
WithdrawRoot common.Hash | ||
} | ||
|
||
// WriteRollupEventSyncedL1BlockNumber stores the latest synced L1 block number related to rollup events in the database. | ||
func WriteRollupEventSyncedL1BlockNumber(db ethdb.KeyValueWriter, l1BlockNumber uint64) { | ||
value := big.NewInt(0).SetUint64(l1BlockNumber).Bytes() | ||
if err := db.Put(rollupEventSyncedL1BlockNumberKey, value); err != nil { | ||
log.Crit("failed to store rollup event synced L1 block number for rollup event", "err", err) | ||
} | ||
} | ||
|
||
// ReadRollupEventSyncedL1BlockNumber fetches the highest synced L1 block number associated with rollup events from the database. | ||
func ReadRollupEventSyncedL1BlockNumber(db ethdb.Reader) *uint64 { | ||
data, err := db.Get(rollupEventSyncedL1BlockNumberKey) | ||
if err != nil && isNotFoundErr(err) { | ||
return nil | ||
} | ||
if err != nil { | ||
log.Crit("failed to read rollup event synced L1 block number from database", "err", err) | ||
} | ||
|
||
number := new(big.Int).SetBytes(data) | ||
if !number.IsUint64() { | ||
log.Crit("unexpected rollup event synced L1 block number in database", "number", number) | ||
} | ||
|
||
rollupEventSyncedL1BlockNumber := number.Uint64() | ||
return &rollupEventSyncedL1BlockNumber | ||
} | ||
|
||
// WriteBatchChunkRanges writes the block ranges for each chunk within a batch to the database. | ||
// It serializes the chunk ranges using RLP and stores them under a key derived from the batch index. | ||
func WriteBatchChunkRanges(db ethdb.KeyValueWriter, batchIndex uint64, chunkBlockRanges []*ChunkBlockRange) { | ||
bytes, err := rlp.EncodeToBytes(chunkBlockRanges) | ||
if err != nil { | ||
log.Crit("failed to RLP encode batch chunk ranges", "batch index", batchIndex, "err", err) | ||
} | ||
if err := db.Put(batchChunkRangesKey(batchIndex), bytes); err != nil { | ||
log.Crit("failed to store batch chunk ranges", "batch index", batchIndex, "err", err) | ||
} | ||
} | ||
|
||
// DeleteBatchChunkRanges removes the block ranges of all chunks associated with a specific batch from the database. | ||
// Note: Only non-finalized batches can be reverted. | ||
func DeleteBatchChunkRanges(db ethdb.KeyValueWriter, batchIndex uint64) { | ||
if err := db.Delete(batchChunkRangesKey(batchIndex)); err != nil { | ||
log.Crit("failed to delete batch chunk ranges", "batch index", batchIndex, "err", err) | ||
} | ||
} | ||
|
||
// ReadBatchChunkRanges retrieves the block ranges of all chunks associated with a specific batch from the database. | ||
// It returns a list of ChunkBlockRange pointers, or nil if no chunk ranges are found for the given batch index. | ||
func ReadBatchChunkRanges(db ethdb.Reader, batchIndex uint64) []*ChunkBlockRange { | ||
data, err := db.Get(batchChunkRangesKey(batchIndex)) | ||
if err != nil && isNotFoundErr(err) { | ||
return nil | ||
} | ||
if err != nil { | ||
log.Crit("failed to read batch chunk ranges from database", "err", err) | ||
} | ||
|
||
cr := new([]*ChunkBlockRange) | ||
if err := rlp.Decode(bytes.NewReader(data), cr); err != nil { | ||
log.Crit("Invalid ChunkBlockRange RLP", "batch index", batchIndex, "data", data, "err", err) | ||
} | ||
return *cr | ||
} | ||
|
||
// WriteFinalizedBatchMeta stores the metadata of a finalized batch in the database. | ||
func WriteFinalizedBatchMeta(db ethdb.KeyValueWriter, batchIndex uint64, finalizedBatchMeta *FinalizedBatchMeta) { | ||
var err error | ||
bytes, err := rlp.EncodeToBytes(finalizedBatchMeta) | ||
if err != nil { | ||
log.Crit("failed to RLP encode batch metadata", "batch index", batchIndex, "err", err) | ||
} | ||
if err := db.Put(batchMetaKey(batchIndex), bytes); err != nil { | ||
log.Crit("failed to store batch metadata", "batch index", batchIndex, "err", err) | ||
} | ||
} | ||
|
||
// ReadFinalizedBatchMeta fetches the metadata of a finalized batch from the database. | ||
func ReadFinalizedBatchMeta(db ethdb.Reader, batchIndex uint64) *FinalizedBatchMeta { | ||
data, err := db.Get(batchMetaKey(batchIndex)) | ||
if err != nil && isNotFoundErr(err) { | ||
return nil | ||
} | ||
if err != nil { | ||
log.Crit("failed to read finalized batch metadata from database", "err", err) | ||
} | ||
|
||
fbm := new(FinalizedBatchMeta) | ||
if err := rlp.Decode(bytes.NewReader(data), fbm); err != nil { | ||
log.Crit("Invalid FinalizedBatchMeta RLP", "batch index", batchIndex, "data", data, "err", err) | ||
} | ||
return fbm | ||
} | ||
|
||
// WriteFinalizedL2BlockNumber stores the highest finalized L2 block number in the database. | ||
func WriteFinalizedL2BlockNumber(db ethdb.KeyValueWriter, l2BlockNumber uint64) { | ||
value := big.NewInt(0).SetUint64(l2BlockNumber).Bytes() | ||
if err := db.Put(finalizedL2BlockNumberKey, value); err != nil { | ||
log.Crit("failed to store finalized L2 block number for rollup event", "err", err) | ||
} | ||
} | ||
|
||
// ReadFinalizedL2BlockNumber fetches the highest finalized L2 block number from the database. | ||
func ReadFinalizedL2BlockNumber(db ethdb.Reader) *uint64 { | ||
data, err := db.Get(finalizedL2BlockNumberKey) | ||
if err != nil && isNotFoundErr(err) { | ||
return nil | ||
} | ||
if err != nil { | ||
log.Crit("failed to read finalized L2 block number from database", "err", err) | ||
} | ||
|
||
number := new(big.Int).SetBytes(data) | ||
if !number.IsUint64() { | ||
log.Crit("unexpected finalized L2 block number in database", "number", number) | ||
} | ||
|
||
finalizedL2BlockNumber := number.Uint64() | ||
return &finalizedL2BlockNumber | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
package rawdb | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/scroll-tech/go-ethereum/common" | ||
) | ||
|
||
func TestWriteRollupEventSyncedL1BlockNumber(t *testing.T) { | ||
blockNumbers := []uint64{ | ||
1, | ||
1 << 2, | ||
1 << 8, | ||
1 << 16, | ||
1 << 32, | ||
} | ||
|
||
db := NewMemoryDatabase() | ||
|
||
// read non-existing value | ||
if got := ReadRollupEventSyncedL1BlockNumber(db); got != nil { | ||
t.Fatal("Expected 0 for non-existing value", "got", *got) | ||
} | ||
|
||
for _, num := range blockNumbers { | ||
WriteRollupEventSyncedL1BlockNumber(db, num) | ||
got := ReadRollupEventSyncedL1BlockNumber(db) | ||
|
||
if *got != num { | ||
t.Fatal("Block number mismatch", "expected", num, "got", got) | ||
} | ||
} | ||
} | ||
|
||
func TestFinalizedL2BlockNumber(t *testing.T) { | ||
blockNumbers := []uint64{ | ||
1, | ||
1 << 2, | ||
1 << 8, | ||
1 << 16, | ||
1 << 32, | ||
} | ||
|
||
db := NewMemoryDatabase() | ||
|
||
// read non-existing value | ||
if got := ReadFinalizedL2BlockNumber(db); got != nil { | ||
t.Fatal("Expected 0 for non-existing value", "got", *got) | ||
} | ||
|
||
for _, num := range blockNumbers { | ||
WriteFinalizedL2BlockNumber(db, num) | ||
got := ReadFinalizedL2BlockNumber(db) | ||
|
||
if *got != num { | ||
t.Fatal("Block number mismatch", "expected", num, "got", got) | ||
} | ||
} | ||
} | ||
|
||
func TestFinalizedBatchMeta(t *testing.T) { | ||
batches := []*FinalizedBatchMeta{ | ||
{ | ||
BatchHash: common.BytesToHash([]byte("batch1")), | ||
TotalL1MessagePopped: 123, | ||
StateRoot: common.BytesToHash([]byte("stateRoot1")), | ||
WithdrawRoot: common.BytesToHash([]byte("withdrawRoot1")), | ||
}, | ||
{ | ||
BatchHash: common.BytesToHash([]byte("batch2")), | ||
TotalL1MessagePopped: 456, | ||
StateRoot: common.BytesToHash([]byte("stateRoot2")), | ||
WithdrawRoot: common.BytesToHash([]byte("withdrawRoot2")), | ||
}, | ||
{ | ||
BatchHash: common.BytesToHash([]byte("batch3")), | ||
TotalL1MessagePopped: 789, | ||
StateRoot: common.BytesToHash([]byte("stateRoot3")), | ||
WithdrawRoot: common.BytesToHash([]byte("withdrawRoot3")), | ||
}, | ||
} | ||
|
||
db := NewMemoryDatabase() | ||
|
||
for i, batch := range batches { | ||
batchIndex := uint64(i) | ||
WriteFinalizedBatchMeta(db, batchIndex, batch) | ||
} | ||
|
||
for i, batch := range batches { | ||
batchIndex := uint64(i) | ||
readBatch := ReadFinalizedBatchMeta(db, batchIndex) | ||
if readBatch == nil { | ||
t.Fatal("Failed to read batch from database") | ||
} | ||
if readBatch.BatchHash != batch.BatchHash || readBatch.TotalL1MessagePopped != batch.TotalL1MessagePopped || | ||
readBatch.StateRoot != batch.StateRoot || readBatch.WithdrawRoot != batch.WithdrawRoot { | ||
t.Fatal("Mismatch in read batch", "expected", batch, "got", readBatch) | ||
} | ||
} | ||
|
||
// over-write | ||
newBatch := &FinalizedBatchMeta{ | ||
BatchHash: common.BytesToHash([]byte("newBatch")), | ||
TotalL1MessagePopped: 999, | ||
StateRoot: common.BytesToHash([]byte("newStateRoot")), | ||
WithdrawRoot: common.BytesToHash([]byte("newWithdrawRoot")), | ||
} | ||
WriteFinalizedBatchMeta(db, 0, newBatch) // over-writing the batch with index 0 | ||
readBatch := ReadFinalizedBatchMeta(db, 0) | ||
if readBatch.BatchHash != newBatch.BatchHash || readBatch.TotalL1MessagePopped != newBatch.TotalL1MessagePopped || | ||
readBatch.StateRoot != newBatch.StateRoot || readBatch.WithdrawRoot != newBatch.WithdrawRoot { | ||
t.Fatal("Mismatch after over-writing batch", "expected", newBatch, "got", readBatch) | ||
} | ||
|
||
// read non-existing value | ||
nonExistingIndex := uint64(len(batches) + 1) | ||
readBatch = ReadFinalizedBatchMeta(db, nonExistingIndex) | ||
if readBatch != nil { | ||
t.Fatal("Expected nil for non-existing value", "got", readBatch) | ||
} | ||
} | ||
|
||
func TestBatchChunkRanges(t *testing.T) { | ||
chunks := [][]*ChunkBlockRange{ | ||
{ | ||
{StartBlockNumber: 1, EndBlockNumber: 100}, | ||
{StartBlockNumber: 101, EndBlockNumber: 200}, | ||
}, | ||
{ | ||
{StartBlockNumber: 201, EndBlockNumber: 300}, | ||
{StartBlockNumber: 301, EndBlockNumber: 400}, | ||
}, | ||
{ | ||
{StartBlockNumber: 401, EndBlockNumber: 500}, | ||
}, | ||
} | ||
|
||
db := NewMemoryDatabase() | ||
|
||
for i, chunkRange := range chunks { | ||
batchIndex := uint64(i) | ||
WriteBatchChunkRanges(db, batchIndex, chunkRange) | ||
} | ||
|
||
for i, chunkRange := range chunks { | ||
batchIndex := uint64(i) | ||
readChunkRange := ReadBatchChunkRanges(db, batchIndex) | ||
if len(readChunkRange) != len(chunkRange) { | ||
t.Fatal("Mismatch in number of chunk ranges", "expected", len(chunkRange), "got", len(readChunkRange)) | ||
} | ||
|
||
for j, cr := range readChunkRange { | ||
if cr.StartBlockNumber != chunkRange[j].StartBlockNumber || cr.EndBlockNumber != chunkRange[j].EndBlockNumber { | ||
t.Fatal("Mismatch in chunk range", "batch index", batchIndex, "expected", chunkRange[j], "got", cr) | ||
} | ||
} | ||
} | ||
|
||
// over-write | ||
newRange := []*ChunkBlockRange{{StartBlockNumber: 1001, EndBlockNumber: 1100}} | ||
WriteBatchChunkRanges(db, 0, newRange) | ||
readChunkRange := ReadBatchChunkRanges(db, 0) | ||
if len(readChunkRange) != 1 || readChunkRange[0].StartBlockNumber != 1001 || readChunkRange[0].EndBlockNumber != 1100 { | ||
t.Fatal("Over-write failed for chunk range", "expected", newRange, "got", readChunkRange) | ||
} | ||
|
||
// read non-existing value | ||
if readChunkRange = ReadBatchChunkRanges(db, uint64(len(chunks)+1)); readChunkRange != nil { | ||
t.Fatal("Expected nil for non-existing value", "got", readChunkRange) | ||
} | ||
|
||
// delete: revert batch | ||
for i := range chunks { | ||
batchIndex := uint64(i) | ||
DeleteBatchChunkRanges(db, batchIndex) | ||
|
||
readChunkRange := ReadBatchChunkRanges(db, batchIndex) | ||
if readChunkRange != nil { | ||
t.Fatal("Chunk range was not deleted", "batch index", batchIndex) | ||
} | ||
} | ||
|
||
// delete non-existing value: ensure the delete operation handles non-existing values without errors. | ||
DeleteBatchChunkRanges(db, uint64(len(chunks)+1)) | ||
} |
Oops, something went wrong.