mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 21:56:43 +00:00
refactor(rollup sync service): use CalldataBlobSource to retrieve data from L1 (#1103)
* port changes from #1013 * port changes from #1068 * go.mod tidy * fix compile error * fix goimports * fix log * address review comments * upgrade golang.org/x/net to 0.23.0 * port changes from #1018 * fix tests and linter errors * address review comments * refactor rollup sync service / verifier to use CalldataBlobSource to retrieve data from L1 * add configuration and initialize blob clients * fix unit tests * remove unused code * address review comments * address more review comments * Allow using MPT * fix issues after merge * bump version --------- Co-authored-by: Ömer Faruk Irmak <omerfirmak@gmail.com> Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
This commit is contained in:
parent
ea43834c19
commit
b56dd9f31b
28 changed files with 622 additions and 1364 deletions
|
|
@ -1641,6 +1641,7 @@ func setEnableRollupVerify(ctx *cli.Context, cfg *ethconfig.Config) {
|
|||
func setDA(ctx *cli.Context, cfg *ethconfig.Config) {
|
||||
if ctx.IsSet(DASyncEnabledFlag.Name) {
|
||||
cfg.EnableDASyncing = ctx.Bool(DASyncEnabledFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(DABlobScanAPIEndpointFlag.Name) {
|
||||
cfg.DA.BlobScanAPIEndpoint = ctx.String(DABlobScanAPIEndpointFlag.Name)
|
||||
}
|
||||
|
|
@ -1651,7 +1652,6 @@ func setDA(ctx *cli.Context, cfg *ethconfig.Config) {
|
|||
cfg.DA.BeaconNodeAPIEndpoint = ctx.String(DABeaconNodeAPIEndpointFlag.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setMaxBlockRange(ctx *cli.Context, cfg *ethconfig.Config) {
|
||||
if ctx.GlobalIsSet(MaxBlockRangeFlag.Name) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ type ChunkBlockRange struct {
|
|||
// CommittedBatchMeta holds metadata for committed batches.
|
||||
type CommittedBatchMeta struct {
|
||||
Version uint8
|
||||
// BlobVersionedHashes are the versioned hashes of the blobs in the batch. Currently unused. Left for compatibility.
|
||||
BlobVersionedHashes []common.Hash
|
||||
ChunkBlockRanges []*ChunkBlockRange
|
||||
}
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether
|
|||
|
||||
if config.EnableRollupVerify {
|
||||
// initialize and start rollup event sync service
|
||||
eth.rollupSyncService, err = rollup_sync_service.NewRollupSyncService(context.Background(), chainConfig, eth.chainDb, l1Client, eth.blockchain, stack)
|
||||
eth.rollupSyncService, err = rollup_sync_service.NewRollupSyncService(context.Background(), chainConfig, eth.chainDb, l1Client, eth.blockchain, stack, config.DA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot initialize rollup event sync service: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import (
|
|||
const (
|
||||
VersionMajor = 5 // Major version component of the current release
|
||||
VersionMinor = 8 // Minor version component of the current release
|
||||
VersionPatch = 4 // Patch version component of the current release
|
||||
VersionPatch = 5 // Patch version component of the current release
|
||||
VersionMeta = "mainnet" // Version metadata to append to the version string
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,10 @@ func (ds *CalldataBlobSource) NextData() (Entries, error) {
|
|||
return da, nil
|
||||
}
|
||||
|
||||
func (ds *CalldataBlobSource) SetL1Height(l1Height uint64) {
|
||||
ds.l1Height = l1Height
|
||||
}
|
||||
|
||||
func (ds *CalldataBlobSource) L1Height() uint64 {
|
||||
return ds.l1Height
|
||||
}
|
||||
|
|
@ -106,10 +110,22 @@ func (ds *CalldataBlobSource) processRollupEventsToDA(rollupEvents l1.RollupEven
|
|||
}
|
||||
|
||||
case l1.RevertEventType:
|
||||
entry = NewRevertBatch(rollupEvent.BatchIndex().Uint64())
|
||||
revertEvent, ok := rollupEvent.(*l1.RevertBatchEvent)
|
||||
// this should never happen because we just check event type
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type of rollup event: %T", rollupEvent)
|
||||
}
|
||||
|
||||
entry = NewRevertBatch(revertEvent)
|
||||
|
||||
case l1.FinalizeEventType:
|
||||
entry = NewFinalizeBatch(rollupEvent.BatchIndex().Uint64())
|
||||
finalizeEvent, ok := rollupEvent.(*l1.FinalizeBatchEvent)
|
||||
// this should never happen because we just check event type
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type of rollup event: %T", rollupEvent)
|
||||
}
|
||||
|
||||
entry = NewFinalizeBatch(finalizeEvent)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown rollup event, type: %v", rollupEvent.Type())
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/scroll-tech/da-codec/encoding"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/rawdb"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/ethdb"
|
||||
|
|
@ -14,14 +15,14 @@ import (
|
|||
)
|
||||
|
||||
type CommitBatchDAV0 struct {
|
||||
version uint8
|
||||
version encoding.CodecVersion
|
||||
batchIndex uint64
|
||||
parentTotalL1MessagePopped uint64
|
||||
skippedL1MessageBitmap []byte
|
||||
chunks []*encoding.DAChunkRawTx
|
||||
l1Txs []*types.L1MessageTx
|
||||
|
||||
l1BlockNumber uint64
|
||||
event *l1.CommitBatchEvent
|
||||
}
|
||||
|
||||
func NewCommitBatchDAV0(db ethdb.Database,
|
||||
|
|
@ -36,16 +37,16 @@ func NewCommitBatchDAV0(db ethdb.Database,
|
|||
return nil, fmt.Errorf("failed to unpack chunks: %d, err: %w", commitEvent.BatchIndex().Uint64(), err)
|
||||
}
|
||||
|
||||
return NewCommitBatchDAV0WithChunks(db, uint8(codec.Version()), commitEvent.BatchIndex().Uint64(), parentBatchHeader, decodedChunks, skippedL1MessageBitmap, commitEvent.BlockNumber())
|
||||
return NewCommitBatchDAV0WithChunks(db, codec.Version(), commitEvent.BatchIndex().Uint64(), parentBatchHeader, decodedChunks, skippedL1MessageBitmap, commitEvent)
|
||||
}
|
||||
|
||||
func NewCommitBatchDAV0WithChunks(db ethdb.Database,
|
||||
version uint8,
|
||||
version encoding.CodecVersion,
|
||||
batchIndex uint64,
|
||||
parentBatchHeader []byte,
|
||||
decodedChunks []*encoding.DAChunkRawTx,
|
||||
skippedL1MessageBitmap []byte,
|
||||
l1BlockNumber uint64,
|
||||
event *l1.CommitBatchEvent,
|
||||
) (*CommitBatchDAV0, error) {
|
||||
parentTotalL1MessagePopped := getBatchTotalL1MessagePopped(parentBatchHeader)
|
||||
l1Txs, err := getL1Messages(db, parentTotalL1MessagePopped, skippedL1MessageBitmap, getTotalMessagesPoppedFromChunks(decodedChunks))
|
||||
|
|
@ -60,7 +61,7 @@ func NewCommitBatchDAV0WithChunks(db ethdb.Database,
|
|||
skippedL1MessageBitmap: skippedL1MessageBitmap,
|
||||
chunks: decodedChunks,
|
||||
l1Txs: l1Txs,
|
||||
l1BlockNumber: l1BlockNumber,
|
||||
event: event,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -70,12 +71,28 @@ func NewCommitBatchDAV0Empty() *CommitBatchDAV0 {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *CommitBatchDAV0) Version() encoding.CodecVersion {
|
||||
return c.version
|
||||
}
|
||||
|
||||
func (c *CommitBatchDAV0) Chunks() []*encoding.DAChunkRawTx {
|
||||
return c.chunks
|
||||
}
|
||||
|
||||
func (c *CommitBatchDAV0) BlobVersionedHashes() []common.Hash {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CommitBatchDAV0) Type() Type {
|
||||
return CommitBatchV0Type
|
||||
}
|
||||
|
||||
func (c *CommitBatchDAV0) L1BlockNumber() uint64 {
|
||||
return c.l1BlockNumber
|
||||
return c.event.BlockNumber()
|
||||
}
|
||||
|
||||
func (c *CommitBatchDAV0) Event() l1.RollupEvent {
|
||||
return c.event
|
||||
}
|
||||
|
||||
func (c *CommitBatchDAV0) BatchIndex() uint64 {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import (
|
|||
|
||||
type CommitBatchDAV1 struct {
|
||||
*CommitBatchDAV0
|
||||
|
||||
versionedHashes []common.Hash
|
||||
}
|
||||
|
||||
func NewCommitBatchDAWithBlob(ctx context.Context, db ethdb.Database,
|
||||
|
|
@ -33,11 +35,17 @@ func NewCommitBatchDAWithBlob(ctx context.Context, db ethdb.Database,
|
|||
return nil, fmt.Errorf("failed to unpack chunks: %v, err: %w", commitEvent.BatchIndex().Uint64(), err)
|
||||
}
|
||||
|
||||
versionedHash, err := l1Reader.FetchTxBlobHash(commitEvent.TxHash(), commitEvent.BlockHash())
|
||||
versionedHashes, err := l1Reader.FetchTxBlobHashes(commitEvent.TxHash(), commitEvent.BlockHash())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch blob hash, err: %w", err)
|
||||
}
|
||||
|
||||
// with CommitBatchDAV1 we expect only one versioned hash as we commit only one blob per batch submission
|
||||
if len(versionedHashes) != 1 {
|
||||
return nil, fmt.Errorf("unexpected number of versioned hashes: %d", len(versionedHashes))
|
||||
}
|
||||
versionedHash := versionedHashes[0]
|
||||
|
||||
header, err := l1Reader.FetchBlockHeaderByNumber(commitEvent.BlockNumber())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get header by number, err: %w", err)
|
||||
|
|
@ -70,14 +78,21 @@ func NewCommitBatchDAWithBlob(ctx context.Context, db ethdb.Database,
|
|||
return nil, fmt.Errorf("decodedChunks is nil after decoding")
|
||||
}
|
||||
|
||||
v0, err := NewCommitBatchDAV0WithChunks(db, uint8(codec.Version()), commitEvent.BatchIndex().Uint64(), parentBatchHeader, decodedChunks, skippedL1MessageBitmap, commitEvent.BlockNumber())
|
||||
v0, err := NewCommitBatchDAV0WithChunks(db, codec.Version(), commitEvent.BatchIndex().Uint64(), parentBatchHeader, decodedChunks, skippedL1MessageBitmap, commitEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &CommitBatchDAV1{v0}, nil
|
||||
return &CommitBatchDAV1{
|
||||
CommitBatchDAV0: v0,
|
||||
versionedHashes: versionedHashes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *CommitBatchDAV1) Type() Type {
|
||||
return CommitBatchWithBlobType
|
||||
}
|
||||
|
||||
func (c *CommitBatchDAV1) BlobVersionedHashes() []common.Hash {
|
||||
return c.versionedHashes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@ package da
|
|||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/scroll-tech/da-codec/encoding"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/l1"
|
||||
)
|
||||
|
||||
type Type int
|
||||
|
|
@ -25,11 +29,15 @@ type Entry interface {
|
|||
BatchIndex() uint64
|
||||
L1BlockNumber() uint64
|
||||
CompareTo(Entry) int
|
||||
Event() l1.RollupEvent
|
||||
}
|
||||
|
||||
type EntryWithBlocks interface {
|
||||
Entry
|
||||
Blocks() []*PartialBlock
|
||||
Version() encoding.CodecVersion
|
||||
Chunks() []*encoding.DAChunkRawTx
|
||||
BlobVersionedHashes() []common.Hash
|
||||
}
|
||||
|
||||
type Entries []Entry
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
package da
|
||||
|
||||
type FinalizeBatch struct {
|
||||
batchIndex uint64
|
||||
import (
|
||||
"github.com/scroll-tech/go-ethereum/rollup/l1"
|
||||
)
|
||||
|
||||
l1BlockNumber uint64
|
||||
type FinalizeBatch struct {
|
||||
event *l1.FinalizeBatchEvent
|
||||
}
|
||||
|
||||
func NewFinalizeBatch(batchIndex uint64) *FinalizeBatch {
|
||||
func NewFinalizeBatch(event *l1.FinalizeBatchEvent) *FinalizeBatch {
|
||||
return &FinalizeBatch{
|
||||
batchIndex: batchIndex,
|
||||
event: event,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -17,11 +19,15 @@ func (f *FinalizeBatch) Type() Type {
|
|||
}
|
||||
|
||||
func (f *FinalizeBatch) L1BlockNumber() uint64 {
|
||||
return f.l1BlockNumber
|
||||
return f.event.BlockNumber()
|
||||
}
|
||||
|
||||
func (f *FinalizeBatch) BatchIndex() uint64 {
|
||||
return f.batchIndex
|
||||
return f.event.BatchIndex().Uint64()
|
||||
}
|
||||
|
||||
func (f *FinalizeBatch) Event() l1.RollupEvent {
|
||||
return f.event
|
||||
}
|
||||
|
||||
func (f *FinalizeBatch) CompareTo(other Entry) int {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
package da
|
||||
|
||||
type RevertBatch struct {
|
||||
batchIndex uint64
|
||||
import (
|
||||
"github.com/scroll-tech/go-ethereum/rollup/l1"
|
||||
)
|
||||
|
||||
l1BlockNumber uint64
|
||||
type RevertBatch struct {
|
||||
event *l1.RevertBatchEvent
|
||||
}
|
||||
|
||||
func NewRevertBatch(batchIndex uint64) *RevertBatch {
|
||||
func NewRevertBatch(event *l1.RevertBatchEvent) *RevertBatch {
|
||||
return &RevertBatch{
|
||||
batchIndex: batchIndex,
|
||||
event: event,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -17,10 +19,14 @@ func (r *RevertBatch) Type() Type {
|
|||
}
|
||||
|
||||
func (r *RevertBatch) L1BlockNumber() uint64 {
|
||||
return r.l1BlockNumber
|
||||
return r.event.BlockNumber()
|
||||
}
|
||||
func (r *RevertBatch) BatchIndex() uint64 {
|
||||
return r.batchIndex
|
||||
return r.event.BatchIndex().Uint64()
|
||||
}
|
||||
|
||||
func (r *RevertBatch) Event() l1.RollupEvent {
|
||||
return r.event
|
||||
}
|
||||
|
||||
func (r *RevertBatch) CompareTo(other Entry) int {
|
||||
|
|
|
|||
|
|
@ -158,6 +158,26 @@ type FinalizeBatchEvent struct {
|
|||
blockNumber uint64
|
||||
}
|
||||
|
||||
func NewFinalizeBatchEvent(
|
||||
batchIndex *big.Int,
|
||||
batchHash common.Hash,
|
||||
stateRoot common.Hash,
|
||||
withdrawRoot common.Hash,
|
||||
txHash common.Hash,
|
||||
blockHash common.Hash,
|
||||
blockNumber uint64,
|
||||
) *FinalizeBatchEvent {
|
||||
return &FinalizeBatchEvent{
|
||||
batchIndex: batchIndex,
|
||||
batchHash: batchHash,
|
||||
stateRoot: stateRoot,
|
||||
withdrawRoot: withdrawRoot,
|
||||
txHash: txHash,
|
||||
blockHash: blockHash,
|
||||
blockNumber: blockNumber,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FinalizeBatchEvent) TxHash() common.Hash {
|
||||
return f.txHash
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,20 +139,23 @@ func (r *Reader) FetchTxData(txHash, blockHash common.Hash) ([]byte, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tx.Data(), nil
|
||||
}
|
||||
|
||||
// FetchTxBlobHash fetches tx blob hash corresponding to given event log
|
||||
func (r *Reader) FetchTxBlobHash(txHash, blockHash common.Hash) (common.Hash, error) {
|
||||
// FetchTxBlobHashes fetches tx blob hash corresponding to given event log
|
||||
func (r *Reader) FetchTxBlobHashes(txHash, blockHash common.Hash) ([]common.Hash, error) {
|
||||
tx, err := r.fetchTx(txHash, blockHash)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
return nil, fmt.Errorf("failed to fetch tx, tx hash: %v, block hash: %v, err: %w", txHash.Hex(), blockHash.Hex(), err)
|
||||
}
|
||||
|
||||
blobHashes := tx.BlobHashes()
|
||||
if len(blobHashes) == 0 {
|
||||
return common.Hash{}, fmt.Errorf("transaction does not contain any blobs, tx hash: %v", txHash.Hex())
|
||||
return nil, fmt.Errorf("transaction does not contain any blobs, tx hash: %v", txHash.Hex())
|
||||
}
|
||||
return blobHashes[0], nil
|
||||
|
||||
return blobHashes, nil
|
||||
}
|
||||
|
||||
// FetchRollupEventsInRange retrieves and parses commit/revert/finalize rollup events between block numbers: [from, to].
|
||||
|
|
|
|||
|
|
@ -20,3 +20,41 @@ type Client interface {
|
|||
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
|
||||
CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
|
||||
}
|
||||
|
||||
type MockNopClient struct{}
|
||||
|
||||
func (m *MockNopClient) BlockNumber(ctx context.Context) (uint64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (m *MockNopClient) ChainID(ctx context.Context) (*big.Int, error) {
|
||||
return big.NewInt(0), nil
|
||||
}
|
||||
|
||||
func (m *MockNopClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockNopClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockNopClient) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockNopClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockNopClient) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (m *MockNopClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockNopClient) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,82 +0,0 @@
|
|||
package rollup_sync_service
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
func TestEventSignatures(t *testing.T) {
|
||||
scrollChainABI, err := ScrollChainMetaData.GetAbi()
|
||||
if err != nil {
|
||||
t.Fatal("failed to get scroll chain abi", "err", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, crypto.Keccak256Hash([]byte("CommitBatch(uint256,bytes32)")), scrollChainABI.Events["CommitBatch"].ID)
|
||||
assert.Equal(t, crypto.Keccak256Hash([]byte("RevertBatch(uint256,bytes32)")), scrollChainABI.Events["RevertBatch"].ID)
|
||||
assert.Equal(t, crypto.Keccak256Hash([]byte("FinalizeBatch(uint256,bytes32,bytes32,bytes32)")), scrollChainABI.Events["FinalizeBatch"].ID)
|
||||
}
|
||||
|
||||
func TestUnpackLog(t *testing.T) {
|
||||
scrollChainABI, err := ScrollChainMetaData.GetAbi()
|
||||
require.NoError(t, err)
|
||||
|
||||
mockBatchIndex := big.NewInt(123)
|
||||
mockBatchHash := crypto.Keccak256Hash([]byte("mockBatch"))
|
||||
mockStateRoot := crypto.Keccak256Hash([]byte("mockStateRoot"))
|
||||
mockWithdrawRoot := crypto.Keccak256Hash([]byte("mockWithdrawRoot"))
|
||||
|
||||
tests := []struct {
|
||||
eventName string
|
||||
mockLog types.Log
|
||||
expected interface{}
|
||||
out interface{}
|
||||
}{
|
||||
{
|
||||
"CommitBatch",
|
||||
types.Log{
|
||||
Data: []byte{},
|
||||
Topics: []common.Hash{scrollChainABI.Events["CommitBatch"].ID, common.BigToHash(mockBatchIndex), mockBatchHash},
|
||||
},
|
||||
&L1CommitBatchEvent{BatchIndex: mockBatchIndex, BatchHash: mockBatchHash},
|
||||
&L1CommitBatchEvent{},
|
||||
},
|
||||
{
|
||||
"RevertBatch",
|
||||
types.Log{
|
||||
Data: []byte{},
|
||||
Topics: []common.Hash{scrollChainABI.Events["RevertBatch"].ID, common.BigToHash(mockBatchIndex), mockBatchHash},
|
||||
},
|
||||
&L1RevertBatchEvent{BatchIndex: mockBatchIndex, BatchHash: mockBatchHash},
|
||||
&L1RevertBatchEvent{},
|
||||
},
|
||||
{
|
||||
"FinalizeBatch",
|
||||
types.Log{
|
||||
Data: append(mockStateRoot.Bytes(), mockWithdrawRoot.Bytes()...),
|
||||
Topics: []common.Hash{scrollChainABI.Events["FinalizeBatch"].ID, common.BigToHash(mockBatchIndex), mockBatchHash},
|
||||
},
|
||||
&L1FinalizeBatchEvent{
|
||||
BatchIndex: mockBatchIndex,
|
||||
BatchHash: mockBatchHash,
|
||||
StateRoot: mockStateRoot,
|
||||
WithdrawRoot: mockWithdrawRoot,
|
||||
},
|
||||
&L1FinalizeBatchEvent{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.eventName, func(t *testing.T) {
|
||||
err := UnpackLog(scrollChainABI, tt.out, tt.eventName, tt.mockLog)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, tt.out)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
package rollup_sync_service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum"
|
||||
"github.com/scroll-tech/go-ethereum/accounts/abi"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"github.com/scroll-tech/go-ethereum/rpc"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/rollup/sync_service"
|
||||
)
|
||||
|
||||
// L1Client is a wrapper around EthClient that adds
|
||||
// methods for conveniently collecting rollup events of ScrollChain contract.
|
||||
type L1Client struct {
|
||||
ctx context.Context
|
||||
client sync_service.EthClient
|
||||
scrollChainAddress common.Address
|
||||
l1CommitBatchEventSignature common.Hash
|
||||
l1RevertBatchEventSignature common.Hash
|
||||
l1FinalizeBatchEventSignature common.Hash
|
||||
}
|
||||
|
||||
// NewL1Client initializes a new L1Client instance with the provided configuration.
|
||||
// It checks for a valid scrollChainAddress and verifies the chain ID.
|
||||
func NewL1Client(ctx context.Context, l1Client sync_service.EthClient, l1ChainId uint64, scrollChainAddress common.Address, scrollChainABI *abi.ABI) (*L1Client, error) {
|
||||
if scrollChainAddress == (common.Address{}) {
|
||||
return nil, errors.New("must pass non-zero scrollChainAddress to L1Client")
|
||||
}
|
||||
|
||||
// sanity check: compare chain IDs
|
||||
got, err := l1Client.ChainID(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query L1 chain ID, err: %w", err)
|
||||
}
|
||||
if got.Cmp(big.NewInt(0).SetUint64(l1ChainId)) != 0 {
|
||||
return nil, fmt.Errorf("unexpected chain ID, expected: %v, got: %v", l1ChainId, got)
|
||||
}
|
||||
|
||||
client := L1Client{
|
||||
ctx: ctx,
|
||||
client: l1Client,
|
||||
scrollChainAddress: scrollChainAddress,
|
||||
l1CommitBatchEventSignature: scrollChainABI.Events["CommitBatch"].ID,
|
||||
l1RevertBatchEventSignature: scrollChainABI.Events["RevertBatch"].ID,
|
||||
l1FinalizeBatchEventSignature: scrollChainABI.Events["FinalizeBatch"].ID,
|
||||
}
|
||||
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
// FetchRollupEventsInRange retrieves and parses commit/revert/finalize rollup events between block numbers: [from, to].
|
||||
func (c *L1Client) FetchRollupEventsInRange(from, to uint64) ([]types.Log, error) {
|
||||
log.Trace("L1Client FetchRollupEventsInRange", "fromBlock", from, "toBlock", to)
|
||||
|
||||
query := ethereum.FilterQuery{
|
||||
FromBlock: big.NewInt(int64(from)), // inclusive
|
||||
ToBlock: big.NewInt(int64(to)), // inclusive
|
||||
Addresses: []common.Address{
|
||||
c.scrollChainAddress,
|
||||
},
|
||||
Topics: make([][]common.Hash, 1),
|
||||
}
|
||||
query.Topics[0] = make([]common.Hash, 3)
|
||||
query.Topics[0][0] = c.l1CommitBatchEventSignature
|
||||
query.Topics[0][1] = c.l1RevertBatchEventSignature
|
||||
query.Topics[0][2] = c.l1FinalizeBatchEventSignature
|
||||
|
||||
logs, err := c.client.FilterLogs(c.ctx, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to filter logs, err: %w", err)
|
||||
}
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
// GetLatestFinalizedBlockNumber fetches the block number of the latest finalized block from the L1 chain.
|
||||
func (c *L1Client) GetLatestFinalizedBlockNumber() (uint64, error) {
|
||||
header, err := c.client.HeaderByNumber(c.ctx, big.NewInt(int64(rpc.FinalizedBlockNumber)))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !header.Number.IsInt64() {
|
||||
return 0, fmt.Errorf("received unexpected block number in L1Client: %v", header.Number)
|
||||
}
|
||||
return header.Number.Uint64(), nil
|
||||
}
|
||||
|
||||
// FetchTxData fetches tx data corresponding to given event log
|
||||
func (c *L1Client) FetchTxData(vLog *types.Log) ([]byte, error) {
|
||||
tx, _, err := c.client.TransactionByHash(c.ctx, vLog.TxHash)
|
||||
if err != nil {
|
||||
log.Debug("failed to get transaction by hash, probably an unindexed transaction, fetching the whole block to get the transaction",
|
||||
"tx hash", vLog.TxHash.Hex(), "block number", vLog.BlockNumber, "block hash", vLog.BlockHash.Hex(), "err", err)
|
||||
block, err := c.client.BlockByHash(c.ctx, vLog.BlockHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get block by hash, block number: %v, block hash: %v, err: %w", vLog.BlockNumber, vLog.BlockHash.Hex(), err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, txInBlock := range block.Transactions() {
|
||||
if txInBlock.Hash() == vLog.TxHash {
|
||||
tx = txInBlock
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, fmt.Errorf("transaction not found in the block, tx hash: %v, block number: %v, block hash: %v", vLog.TxHash.Hex(), vLog.BlockNumber, vLog.BlockHash.Hex())
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Data(), nil
|
||||
}
|
||||
|
||||
// FetchTxBlobHash fetches tx blob hash corresponding to given event log
|
||||
func (c *L1Client) FetchTxBlobHash(vLog *types.Log) (common.Hash, error) {
|
||||
tx, _, err := c.client.TransactionByHash(c.ctx, vLog.TxHash)
|
||||
if err != nil {
|
||||
log.Debug("failed to get transaction by hash, probably an unindexed transaction, fetching the whole block to get the transaction",
|
||||
"tx hash", vLog.TxHash.Hex(), "block number", vLog.BlockNumber, "block hash", vLog.BlockHash.Hex(), "err", err)
|
||||
block, err := c.client.BlockByHash(c.ctx, vLog.BlockHash)
|
||||
if err != nil {
|
||||
return common.Hash{}, fmt.Errorf("failed to get block by hash, block number: %v, block hash: %v, err: %w", vLog.BlockNumber, vLog.BlockHash.Hex(), err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, txInBlock := range block.Transactions() {
|
||||
if txInBlock.Hash() == vLog.TxHash {
|
||||
tx = txInBlock
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return common.Hash{}, fmt.Errorf("transaction not found in the block, tx hash: %v, block number: %v, block hash: %v", vLog.TxHash.Hex(), vLog.BlockNumber, vLog.BlockHash.Hex())
|
||||
}
|
||||
}
|
||||
blobHashes := tx.BlobHashes()
|
||||
if len(blobHashes) == 0 {
|
||||
return common.Hash{}, fmt.Errorf("transaction does not contain any blobs, tx hash: %v", vLog.TxHash.Hex())
|
||||
}
|
||||
return blobHashes[0], nil
|
||||
}
|
||||
|
||||
// GetHeaderByNumber fetches the block header by number
|
||||
func (c *L1Client) GetHeaderByNumber(blockNumber uint64) (*types.Header, error) {
|
||||
header, err := c.client.HeaderByNumber(c.ctx, big.NewInt(0).SetUint64(blockNumber))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return header, nil
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
package rollup_sync_service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
func TestL1Client(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockClient := &mockEthClient{}
|
||||
|
||||
scrollChainABI, err := ScrollChainMetaData.GetAbi()
|
||||
if err != nil {
|
||||
t.Fatal("failed to get scroll chain abi", "err", err)
|
||||
}
|
||||
scrollChainAddress := common.HexToAddress("0x0123456789abcdef")
|
||||
l1Client, err := NewL1Client(ctx, mockClient, 11155111, scrollChainAddress, scrollChainABI)
|
||||
require.NoError(t, err, "Failed to initialize L1Client")
|
||||
|
||||
blockNumber, err := l1Client.GetLatestFinalizedBlockNumber()
|
||||
assert.NoError(t, err, "Error getting latest confirmed block number")
|
||||
assert.Equal(t, uint64(36), blockNumber, "Unexpected block number")
|
||||
|
||||
logs, err := l1Client.FetchRollupEventsInRange(0, blockNumber)
|
||||
assert.NoError(t, err, "Error fetching rollup events in range")
|
||||
assert.Empty(t, logs, "Expected no logs from FetchRollupEventsInRange")
|
||||
}
|
||||
|
||||
type mockEthClient struct {
|
||||
txRLP []byte
|
||||
}
|
||||
|
||||
func (m *mockEthClient) BlockNumber(ctx context.Context) (uint64, error) {
|
||||
return 11155111, nil
|
||||
}
|
||||
|
||||
func (m *mockEthClient) ChainID(ctx context.Context) (*big.Int, error) {
|
||||
return big.NewInt(11155111), nil
|
||||
}
|
||||
|
||||
func (m *mockEthClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
|
||||
return []types.Log{}, nil
|
||||
}
|
||||
|
||||
func (m *mockEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
|
||||
return &types.Header{
|
||||
Number: big.NewInt(100 - 64),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *mockEthClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockEthClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
|
||||
var tx types.Transaction
|
||||
if err := rlp.DecodeBytes(m.txRLP, &tx); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return &tx, false, nil
|
||||
}
|
||||
|
||||
func (m *mockEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -3,34 +3,31 @@ package rollup_sync_service
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/scroll-tech/da-codec/encoding"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/accounts/abi"
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core"
|
||||
"github.com/scroll-tech/go-ethereum/core/rawdb"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/ethdb"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"github.com/scroll-tech/go-ethereum/node"
|
||||
"github.com/scroll-tech/go-ethereum/params"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/da_syncer"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/blob_client"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/da"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/l1"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/rcfg"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/sync_service"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/withdrawtrie"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultFetchBlockRange is the number of blocks that we collect in a single eth_getLogs query.
|
||||
defaultFetchBlockRange = uint64(100)
|
||||
|
||||
// defaultSyncInterval is the frequency at which we query for new rollup event.
|
||||
defaultSyncInterval = 60 * time.Second
|
||||
defaultSyncInterval = 30 * time.Second
|
||||
|
||||
// defaultMaxRetries is the maximum number of retries allowed when the local node is not synced up to the required block height.
|
||||
defaultMaxRetries = 20
|
||||
|
|
@ -40,7 +37,7 @@ const (
|
|||
// of a specific L1 batch finalize event.
|
||||
defaultGetBlockInRangeRetryDelay = 60 * time.Second
|
||||
|
||||
// defaultLogInterval is the frequency at which we print the latestProcessedBlock.
|
||||
// defaultLogInterval is the frequency at which we print the latest processed block.
|
||||
defaultLogInterval = 5 * time.Minute
|
||||
)
|
||||
|
||||
|
|
@ -48,39 +45,19 @@ const (
|
|||
type RollupSyncService struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
client *L1Client
|
||||
db ethdb.Database
|
||||
latestProcessedBlock uint64
|
||||
scrollChainABI *abi.ABI
|
||||
l1CommitBatchEventSignature common.Hash
|
||||
l1RevertBatchEventSignature common.Hash
|
||||
l1FinalizeBatchEventSignature common.Hash
|
||||
bc *core.BlockChain
|
||||
stack *node.Node
|
||||
stateMu sync.Mutex
|
||||
|
||||
callDataBlobSource *da.CalldataBlobSource
|
||||
}
|
||||
|
||||
func NewRollupSyncService(ctx context.Context, genesisConfig *params.ChainConfig, db ethdb.Database, l1Client sync_service.EthClient, bc *core.BlockChain, stack *node.Node) (*RollupSyncService, error) {
|
||||
// terminate if the caller does not provide an L1 client (e.g. in tests)
|
||||
if l1Client == nil || (reflect.ValueOf(l1Client).Kind() == reflect.Ptr && reflect.ValueOf(l1Client).IsNil()) {
|
||||
log.Warn("No L1 client provided, L1 rollup sync service will not run")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NewRollupSyncService(ctx context.Context, genesisConfig *params.ChainConfig, db ethdb.Database, l1Client l1.Client, bc *core.BlockChain, stack *node.Node, config da_syncer.Config) (*RollupSyncService, error) {
|
||||
if genesisConfig.Scroll.L1Config == nil {
|
||||
return nil, fmt.Errorf("missing L1 config in genesis")
|
||||
}
|
||||
|
||||
scrollChainABI, err := ScrollChainMetaData.GetAbi()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get scroll chain abi: %w", err)
|
||||
}
|
||||
|
||||
client, err := NewL1Client(ctx, l1Client, genesisConfig.Scroll.L1Config.L1ChainId, genesisConfig.Scroll.L1Config.ScrollChainAddress, scrollChainABI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize l1 client: %w", err)
|
||||
}
|
||||
|
||||
// Initialize the latestProcessedBlock with the block just before the L1 deployment block.
|
||||
// This serves as a default value when there's no L1 rollup events synced in the database.
|
||||
var latestProcessedBlock uint64
|
||||
|
|
@ -94,23 +71,57 @@ func NewRollupSyncService(ctx context.Context, genesisConfig *params.ChainConfig
|
|||
latestProcessedBlock = *block
|
||||
}
|
||||
|
||||
var success bool
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer func() {
|
||||
if !success {
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
service := RollupSyncService{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
client: client,
|
||||
db: db,
|
||||
latestProcessedBlock: latestProcessedBlock,
|
||||
scrollChainABI: scrollChainABI,
|
||||
l1CommitBatchEventSignature: scrollChainABI.Events["CommitBatch"].ID,
|
||||
l1RevertBatchEventSignature: scrollChainABI.Events["RevertBatch"].ID,
|
||||
l1FinalizeBatchEventSignature: scrollChainABI.Events["FinalizeBatch"].ID,
|
||||
bc: bc,
|
||||
stack: stack,
|
||||
l1Reader, err := l1.NewReader(ctx, l1.Config{
|
||||
ScrollChainAddress: genesisConfig.Scroll.L1Config.ScrollChainAddress,
|
||||
L1MessageQueueAddress: genesisConfig.Scroll.L1Config.L1MessageQueueAddress,
|
||||
}, l1Client)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize l1.Reader, err = %w", err)
|
||||
}
|
||||
|
||||
return &service, nil
|
||||
blobClientList := blob_client.NewBlobClients()
|
||||
if config.BeaconNodeAPIEndpoint != "" {
|
||||
beaconNodeClient, err := blob_client.NewBeaconNodeClient(config.BeaconNodeAPIEndpoint)
|
||||
if err != nil {
|
||||
log.Warn("failed to create BeaconNodeClient", "err", err)
|
||||
} else {
|
||||
blobClientList.AddBlobClient(beaconNodeClient)
|
||||
}
|
||||
}
|
||||
if config.BlobScanAPIEndpoint != "" {
|
||||
blobClientList.AddBlobClient(blob_client.NewBlobScanClient(config.BlobScanAPIEndpoint))
|
||||
}
|
||||
if config.BlockNativeAPIEndpoint != "" {
|
||||
blobClientList.AddBlobClient(blob_client.NewBlockNativeClient(config.BlockNativeAPIEndpoint))
|
||||
}
|
||||
if blobClientList.Size() == 0 {
|
||||
return nil, errors.New("no blob client is configured for rollup verifier. Please provide at least one blob client via command line flag")
|
||||
}
|
||||
|
||||
calldataBlobSource, err := da.NewCalldataBlobSource(ctx, latestProcessedBlock, l1Reader, blobClientList, db)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create calldata blob source: %w", err)
|
||||
}
|
||||
|
||||
success = true
|
||||
|
||||
return &RollupSyncService{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
db: db,
|
||||
bc: bc,
|
||||
stack: stack,
|
||||
|
||||
callDataBlobSource: calldataBlobSource,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *RollupSyncService) Start() {
|
||||
|
|
@ -118,7 +129,7 @@ func (s *RollupSyncService) Start() {
|
|||
return
|
||||
}
|
||||
|
||||
log.Info("Starting rollup event sync background service", "latest processed block", s.latestProcessedBlock)
|
||||
log.Info("Starting rollup event sync background service", "latest processed block", s.callDataBlobSource.L1Height())
|
||||
|
||||
go func() {
|
||||
syncTicker := time.NewTicker(defaultSyncInterval)
|
||||
|
|
@ -132,9 +143,12 @@ func (s *RollupSyncService) Start() {
|
|||
case <-s.ctx.Done():
|
||||
return
|
||||
case <-syncTicker.C:
|
||||
s.fetchRollupEvents()
|
||||
err := s.fetchRollupEvents()
|
||||
if err != nil {
|
||||
log.Error("failed to fetch rollup events", "err", err)
|
||||
}
|
||||
case <-logTicker.C:
|
||||
log.Info("Sync rollup events progress update", "latestProcessedBlock", s.latestProcessedBlock)
|
||||
log.Info("Sync rollup events progress update", "latest processed block", s.callDataBlobSource.L1Height())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
|
@ -161,90 +175,79 @@ func (s *RollupSyncService) ResetStartSyncHeight(height uint64) {
|
|||
s.stateMu.Lock()
|
||||
defer s.stateMu.Unlock()
|
||||
|
||||
s.latestProcessedBlock = height
|
||||
s.callDataBlobSource.SetL1Height(height)
|
||||
log.Info("Reset sync service", "height", height)
|
||||
}
|
||||
|
||||
func (s *RollupSyncService) fetchRollupEvents() {
|
||||
func (s *RollupSyncService) fetchRollupEvents() error {
|
||||
s.stateMu.Lock()
|
||||
defer s.stateMu.Unlock()
|
||||
|
||||
latestConfirmed, err := s.client.GetLatestFinalizedBlockNumber()
|
||||
for {
|
||||
prevL1Height := s.callDataBlobSource.L1Height()
|
||||
|
||||
daEntries, err := s.callDataBlobSource.NextData()
|
||||
if err != nil {
|
||||
log.Warn("failed to get latest confirmed block number", "err", err)
|
||||
return
|
||||
if errors.Is(err, da.ErrSourceExhausted) {
|
||||
log.Trace("Sync service exhausted data source, waiting for next data")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Trace("Sync service fetch rollup events", "latest processed block", s.latestProcessedBlock, "latest confirmed", latestConfirmed)
|
||||
|
||||
// query in batches
|
||||
for from := s.latestProcessedBlock + 1; from <= latestConfirmed; from += defaultFetchBlockRange {
|
||||
if s.ctx.Err() != nil {
|
||||
log.Info("Context canceled", "reason", s.ctx.Err())
|
||||
return
|
||||
return fmt.Errorf("failed to get next data: %w", err)
|
||||
}
|
||||
|
||||
to := from + defaultFetchBlockRange - 1
|
||||
if to > latestConfirmed {
|
||||
to = latestConfirmed
|
||||
if err = s.updateRollupEvents(daEntries); err != nil {
|
||||
// Reset the L1 height to the previous value to retry fetching the same data.
|
||||
s.callDataBlobSource.SetL1Height(prevL1Height)
|
||||
return fmt.Errorf("failed to parse and update rollup event logs: %w", err)
|
||||
}
|
||||
|
||||
logs, err := s.client.FetchRollupEventsInRange(from, to)
|
||||
log.Trace("Sync service fetched rollup events", "latest processed L1 block", s.callDataBlobSource.L1Height(), "latest finalized L1 block", s.callDataBlobSource.L1Finalized())
|
||||
|
||||
// note: the batch updates in updateRollupEvents are idempotent, if we crash
|
||||
// before this line and re-execute the previous steps, we will get the same result.
|
||||
rawdb.WriteRollupEventSyncedL1BlockNumber(s.db, s.callDataBlobSource.L1Height())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RollupSyncService) updateRollupEvents(daEntries da.Entries) error {
|
||||
for _, entry := range daEntries {
|
||||
switch entry.Type() {
|
||||
case da.CommitBatchV0Type, da.CommitBatchWithBlobType:
|
||||
log.Trace("found new CommitBatch event", "batch index", entry.BatchIndex())
|
||||
|
||||
entryWithBlocks, ok := entry.(da.EntryWithBlocks)
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to cast to EntryWithBlocks, batch index: %v", entry.BatchIndex())
|
||||
}
|
||||
|
||||
committedBatchMeta, err := s.getCommittedBatchMeta(entryWithBlocks)
|
||||
if err != nil {
|
||||
log.Error("failed to fetch rollup events in range", "from block", from, "to block", to, "err", err)
|
||||
return
|
||||
return fmt.Errorf("failed to get committed batch meta, batch index: %v, err: %w", entry.BatchIndex(), err)
|
||||
}
|
||||
|
||||
if err := s.parseAndUpdateRollupEventLogs(logs, to); err != nil {
|
||||
log.Error("failed to parse and update rollup event logs", "err", err)
|
||||
return
|
||||
rawdb.WriteCommittedBatchMeta(s.db, entry.BatchIndex(), committedBatchMeta)
|
||||
|
||||
case da.RevertBatchType:
|
||||
log.Trace("found new RevertBatch event", "batch index", entry.BatchIndex())
|
||||
rawdb.DeleteCommittedBatchMeta(s.db, entry.BatchIndex())
|
||||
|
||||
case da.FinalizeBatchType:
|
||||
event, ok := entry.Event().(*l1.FinalizeBatchEvent)
|
||||
// This should never happen because we just checked the batch type
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to cast to FinalizeBatchEvent, batch index: %v", entry.BatchIndex())
|
||||
}
|
||||
|
||||
s.latestProcessedBlock = to
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RollupSyncService) parseAndUpdateRollupEventLogs(logs []types.Log, endBlockNumber uint64) error {
|
||||
for _, vLog := range logs {
|
||||
switch vLog.Topics[0] {
|
||||
case s.l1CommitBatchEventSignature:
|
||||
event := &L1CommitBatchEvent{}
|
||||
if err := UnpackLog(s.scrollChainABI, event, "CommitBatch", vLog); err != nil {
|
||||
return fmt.Errorf("failed to unpack commit rollup event log, err: %w", err)
|
||||
}
|
||||
batchIndex := event.BatchIndex.Uint64()
|
||||
log.Trace("found new CommitBatch event", "batch index", batchIndex)
|
||||
|
||||
committedBatchMeta, err := s.getCommittedBatchMeta(batchIndex, &vLog)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get chunk ranges, batch index: %v, err: %w", batchIndex, err)
|
||||
}
|
||||
rawdb.WriteCommittedBatchMeta(s.db, batchIndex, committedBatchMeta)
|
||||
|
||||
case s.l1RevertBatchEventSignature:
|
||||
event := &L1RevertBatchEvent{}
|
||||
if err := UnpackLog(s.scrollChainABI, event, "RevertBatch", vLog); err != nil {
|
||||
return fmt.Errorf("failed to unpack revert rollup event log, err: %w", err)
|
||||
}
|
||||
batchIndex := event.BatchIndex.Uint64()
|
||||
log.Trace("found new RevertBatch event", "batch index", batchIndex)
|
||||
|
||||
rawdb.DeleteCommittedBatchMeta(s.db, batchIndex)
|
||||
|
||||
case s.l1FinalizeBatchEventSignature:
|
||||
event := &L1FinalizeBatchEvent{}
|
||||
if err := UnpackLog(s.scrollChainABI, event, "FinalizeBatch", vLog); err != nil {
|
||||
return fmt.Errorf("failed to unpack finalized rollup event log, err: %w", err)
|
||||
}
|
||||
batchIndex := event.BatchIndex.Uint64()
|
||||
batchIndex := entry.BatchIndex()
|
||||
log.Trace("found new FinalizeBatch event", "batch index", batchIndex)
|
||||
|
||||
lastFinalizedBatchIndex := rawdb.ReadLastFinalizedBatchIndex(s.db)
|
||||
|
||||
// After darwin, FinalizeBatch event emitted every bundle, which contains multiple batches.
|
||||
// Therefore there are a range of finalized batches need to be saved into db.
|
||||
// After Darwin, FinalizeBatch event emitted every bundle, which contains multiple batches.
|
||||
// Therefore, there are a range of finalized batches need to be saved into db.
|
||||
//
|
||||
// The range logic also applies to the batches before darwin when FinalizeBatch event emitted
|
||||
// The range logic also applies to the batches before Darwin when FinalizeBatch event emitted
|
||||
// per single batch. In this situation, `batchIndex` just equals to `*lastFinalizedBatchIndex + 1`
|
||||
// and only one batch is processed through the for loop.
|
||||
startBatchIndex := batchIndex
|
||||
|
|
@ -293,14 +296,10 @@ func (s *RollupSyncService) parseAndUpdateRollupEventLogs(logs []types.Log, endB
|
|||
log.Debug("write finalized l2 block number", "batch index", batchIndex, "finalized l2 block height", highestFinalizedBlockNumber)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown event, topic: %v, tx hash: %v", vLog.Topics[0].Hex(), vLog.TxHash.Hex())
|
||||
return fmt.Errorf("unknown daEntry, type: %d, batch index: %d", entry.Type(), entry.BatchIndex())
|
||||
}
|
||||
}
|
||||
|
||||
// note: the batch updates above are idempotent, if we crash
|
||||
// before this line and reexecute the previous steps, we will
|
||||
// get the same result.
|
||||
rawdb.WriteRollupEventSyncedL1BlockNumber(s.db, endBlockNumber)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -355,8 +354,8 @@ func (s *RollupSyncService) getLocalChunksForBatch(chunkBlockRanges []*rawdb.Chu
|
|||
return chunks, nil
|
||||
}
|
||||
|
||||
func (s *RollupSyncService) getCommittedBatchMeta(batchIndex uint64, vLog *types.Log) (*rawdb.CommittedBatchMeta, error) {
|
||||
if batchIndex == 0 {
|
||||
func (s *RollupSyncService) getCommittedBatchMeta(commitedBatch da.EntryWithBlocks) (*rawdb.CommittedBatchMeta, error) {
|
||||
if commitedBatch.BatchIndex() == 0 {
|
||||
return &rawdb.CommittedBatchMeta{
|
||||
Version: 0,
|
||||
BlobVersionedHashes: nil,
|
||||
|
|
@ -364,111 +363,16 @@ func (s *RollupSyncService) getCommittedBatchMeta(batchIndex uint64, vLog *types
|
|||
}, nil
|
||||
}
|
||||
|
||||
tx, _, err := s.client.client.TransactionByHash(s.ctx, vLog.TxHash)
|
||||
chunkRanges, err := blockRangesFromChunks(commitedBatch.Chunks())
|
||||
if err != nil {
|
||||
log.Debug("failed to get transaction by hash, probably an unindexed transaction, fetching the whole block to get the transaction",
|
||||
"tx hash", vLog.TxHash.Hex(), "block number", vLog.BlockNumber, "block hash", vLog.BlockHash.Hex(), "err", err)
|
||||
block, err := s.client.client.BlockByHash(s.ctx, vLog.BlockHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get block by hash, block number: %v, block hash: %v, err: %w", vLog.BlockNumber, vLog.BlockHash.Hex(), err)
|
||||
return nil, fmt.Errorf("failed to decode block ranges from chunks, batch index: %v, err: %w", commitedBatch.BatchIndex(), err)
|
||||
}
|
||||
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("failed to get block by hash, block not found, block number: %v, block hash: %v", vLog.BlockNumber, vLog.BlockHash.Hex())
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, txInBlock := range block.Transactions() {
|
||||
if txInBlock.Hash() == vLog.TxHash {
|
||||
tx = txInBlock
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, fmt.Errorf("transaction not found in the block, tx hash: %v, block number: %v, block hash: %v", vLog.TxHash.Hex(), vLog.BlockNumber, vLog.BlockHash.Hex())
|
||||
}
|
||||
}
|
||||
|
||||
var commitBatchMeta rawdb.CommittedBatchMeta
|
||||
|
||||
if tx.Type() == types.BlobTxType {
|
||||
blobVersionedHashes := tx.BlobHashes()
|
||||
if blobVersionedHashes == nil {
|
||||
return nil, fmt.Errorf("invalid blob transaction, blob hashes is nil, tx hash: %v", tx.Hash().Hex())
|
||||
}
|
||||
commitBatchMeta.BlobVersionedHashes = blobVersionedHashes
|
||||
}
|
||||
|
||||
version, ranges, err := s.decodeBatchVersionAndChunkBlockRanges(tx.Data())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode chunk block ranges, batch index: %v, err: %w", batchIndex, err)
|
||||
}
|
||||
|
||||
commitBatchMeta.Version = version
|
||||
commitBatchMeta.ChunkBlockRanges = ranges
|
||||
return &commitBatchMeta, nil
|
||||
}
|
||||
|
||||
// decodeBatchVersionAndChunkBlockRanges decodes version and chunks' block ranges in a batch based on the commit batch transaction's calldata.
|
||||
func (s *RollupSyncService) decodeBatchVersionAndChunkBlockRanges(txData []byte) (uint8, []*rawdb.ChunkBlockRange, error) {
|
||||
const methodIDLength = 4
|
||||
if len(txData) < methodIDLength {
|
||||
return 0, nil, fmt.Errorf("transaction data is too short, length of tx data: %v, minimum length required: %v", len(txData), methodIDLength)
|
||||
}
|
||||
|
||||
method, err := s.scrollChainABI.MethodById(txData[:methodIDLength])
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("failed to get method by ID, ID: %v, err: %w", txData[:methodIDLength], err)
|
||||
}
|
||||
|
||||
values, err := method.Inputs.Unpack(txData[methodIDLength:])
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("failed to unpack transaction data using ABI, tx data: %v, err: %w", txData, err)
|
||||
}
|
||||
|
||||
if method.Name == "commitBatch" {
|
||||
type commitBatchArgs struct {
|
||||
Version uint8
|
||||
ParentBatchHeader []byte
|
||||
Chunks [][]byte
|
||||
SkippedL1MessageBitmap []byte
|
||||
}
|
||||
|
||||
var args commitBatchArgs
|
||||
if err = method.Inputs.Copy(&args, values); err != nil {
|
||||
return 0, nil, fmt.Errorf("failed to decode calldata into commitBatch args, values: %+v, err: %w", values, err)
|
||||
}
|
||||
|
||||
chunkRanges, err := decodeBlockRangesFromEncodedChunks(encoding.CodecVersion(args.Version), args.Chunks)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("failed to decode block ranges from encoded chunks, version: %v, chunks: %+v, err: %w", args.Version, args.Chunks, err)
|
||||
}
|
||||
|
||||
return args.Version, chunkRanges, nil
|
||||
} else if method.Name == "commitBatchWithBlobProof" {
|
||||
type commitBatchWithBlobProofArgs struct {
|
||||
Version uint8
|
||||
ParentBatchHeader []byte
|
||||
Chunks [][]byte
|
||||
SkippedL1MessageBitmap []byte
|
||||
BlobDataProof []byte
|
||||
}
|
||||
|
||||
var args commitBatchWithBlobProofArgs
|
||||
if err = method.Inputs.Copy(&args, values); err != nil {
|
||||
return 0, nil, fmt.Errorf("failed to decode calldata into commitBatchWithBlobProofArgs args, values: %+v, err: %w", values, err)
|
||||
}
|
||||
|
||||
chunkRanges, err := decodeBlockRangesFromEncodedChunks(encoding.CodecVersion(args.Version), args.Chunks)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("failed to decode block ranges from encoded chunks, version: %v, chunks: %+v, err: %w", args.Version, args.Chunks, err)
|
||||
}
|
||||
|
||||
return args.Version, chunkRanges, nil
|
||||
}
|
||||
|
||||
return 0, nil, fmt.Errorf("unexpected method name: %v", method.Name)
|
||||
return &rawdb.CommittedBatchMeta{
|
||||
Version: uint8(commitedBatch.Version()),
|
||||
ChunkBlockRanges: chunkRanges,
|
||||
BlobVersionedHashes: commitedBatch.BlobVersionedHashes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validateBatch verifies the consistency between the L1 contract and L2 node data.
|
||||
|
|
@ -494,7 +398,7 @@ func (s *RollupSyncService) decodeBatchVersionAndChunkBlockRanges(txData []byte)
|
|||
// Note: This function is compatible with both "finalize by batch" and "finalize by bundle" methods.
|
||||
// In "finalize by bundle", only the last batch of each bundle is fully verified.
|
||||
// This check still ensures the correctness of all batch hashes in the bundle due to the parent-child relationship between batch hashes.
|
||||
func validateBatch(batchIndex uint64, event *L1FinalizeBatchEvent, parentFinalizedBatchMeta *rawdb.FinalizedBatchMeta, committedBatchMeta *rawdb.CommittedBatchMeta, chunks []*encoding.Chunk, stack *node.Node) (uint64, *rawdb.FinalizedBatchMeta, error) {
|
||||
func validateBatch(batchIndex uint64, event *l1.FinalizeBatchEvent, parentFinalizedBatchMeta *rawdb.FinalizedBatchMeta, committedBatchMeta *rawdb.CommittedBatchMeta, chunks []*encoding.Chunk, stack *node.Node) (uint64, *rawdb.FinalizedBatchMeta, error) {
|
||||
if len(chunks) == 0 {
|
||||
return 0, nil, fmt.Errorf("invalid argument: length of chunks is 0, batch index: %v", batchIndex)
|
||||
}
|
||||
|
|
@ -540,15 +444,15 @@ func validateBatch(batchIndex uint64, event *L1FinalizeBatchEvent, parentFinaliz
|
|||
// Only check when batch index matches the index of the event. This is compatible with both "finalize by batch" and "finalize by bundle":
|
||||
// - finalize by batch: check all batches
|
||||
// - finalize by bundle: check the last batch, because only one event (containing the info of the last batch) is emitted per bundle
|
||||
if batchIndex == event.BatchIndex.Uint64() {
|
||||
if localStateRoot != event.StateRoot {
|
||||
log.Error("State root mismatch", "batch index", event.BatchIndex.Uint64(), "start block", startBlock.Header.Number.Uint64(), "end block", endBlock.Header.Number.Uint64(), "parent batch hash", parentFinalizedBatchMeta.BatchHash.Hex(), "l1 finalized state root", event.StateRoot.Hex(), "l2 state root", localStateRoot.Hex())
|
||||
if batchIndex == event.BatchIndex().Uint64() {
|
||||
if localStateRoot != event.StateRoot() {
|
||||
log.Error("State root mismatch", "batch index", event.BatchIndex().Uint64(), "start block", startBlock.Header.Number.Uint64(), "end block", endBlock.Header.Number.Uint64(), "parent batch hash", parentFinalizedBatchMeta.BatchHash.Hex(), "l1 finalized state root", event.StateRoot().Hex(), "l2 state root", localStateRoot.Hex())
|
||||
stack.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if localWithdrawRoot != event.WithdrawRoot {
|
||||
log.Error("Withdraw root mismatch", "batch index", event.BatchIndex.Uint64(), "start block", startBlock.Header.Number.Uint64(), "end block", endBlock.Header.Number.Uint64(), "parent batch hash", parentFinalizedBatchMeta.BatchHash.Hex(), "l1 finalized withdraw root", event.WithdrawRoot.Hex(), "l2 withdraw root", localWithdrawRoot.Hex())
|
||||
if localWithdrawRoot != event.WithdrawRoot() {
|
||||
log.Error("Withdraw root mismatch", "batch index", event.BatchIndex().Uint64(), "start block", startBlock.Header.Number.Uint64(), "end block", endBlock.Header.Number.Uint64(), "parent batch hash", parentFinalizedBatchMeta.BatchHash.Hex(), "l1 finalized withdraw root", event.WithdrawRoot().Hex(), "l2 withdraw root", localWithdrawRoot.Hex())
|
||||
stack.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
@ -556,8 +460,8 @@ func validateBatch(batchIndex uint64, event *L1FinalizeBatchEvent, parentFinaliz
|
|||
// Verify batch hash
|
||||
// This check ensures the correctness of all batch hashes in the bundle
|
||||
// due to the parent-child relationship between batch hashes
|
||||
if localBatchHash != event.BatchHash {
|
||||
log.Error("Batch hash mismatch", "batch index", event.BatchIndex.Uint64(), "start block", startBlock.Header.Number.Uint64(), "end block", endBlock.Header.Number.Uint64(), "parent batch hash", parentFinalizedBatchMeta.BatchHash.Hex(), "parent TotalL1MessagePopped", parentFinalizedBatchMeta.TotalL1MessagePopped, "l1 finalized batch hash", event.BatchHash.Hex(), "l2 batch hash", localBatchHash.Hex())
|
||||
if localBatchHash != event.BatchHash() {
|
||||
log.Error("Batch hash mismatch", "batch index", event.BatchIndex().Uint64(), "start block", startBlock.Header.Number.Uint64(), "end block", endBlock.Header.Number.Uint64(), "parent batch hash", parentFinalizedBatchMeta.BatchHash.Hex(), "parent TotalL1MessagePopped", parentFinalizedBatchMeta.TotalL1MessagePopped, "l1 finalized batch hash", event.BatchHash().Hex(), "l2 batch hash", localBatchHash.Hex())
|
||||
chunksJson, err := json.Marshal(chunks)
|
||||
if err != nil {
|
||||
log.Error("marshal chunks failed", "err", err)
|
||||
|
|
@ -581,22 +485,12 @@ func validateBatch(batchIndex uint64, event *L1FinalizeBatchEvent, parentFinaliz
|
|||
return endBlock.Header.Number.Uint64(), finalizedBatchMeta, nil
|
||||
}
|
||||
|
||||
// decodeBlockRangesFromEncodedChunks decodes the provided chunks into a list of block ranges.
|
||||
func decodeBlockRangesFromEncodedChunks(codecVersion encoding.CodecVersion, chunks [][]byte) ([]*rawdb.ChunkBlockRange, error) {
|
||||
codec, err := encoding.CodecFromVersion(codecVersion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get codec from version: %v, err: %w", codecVersion, err)
|
||||
}
|
||||
|
||||
daChunksRawTx, err := codec.DecodeDAChunksRawTx(chunks)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode DA chunks, version: %v, err: %w", codecVersion, err)
|
||||
}
|
||||
|
||||
// blockRangesFromChunks decodes the provided chunks into a list of block ranges.
|
||||
func blockRangesFromChunks(chunks []*encoding.DAChunkRawTx) ([]*rawdb.ChunkBlockRange, error) {
|
||||
var chunkBlockRanges []*rawdb.ChunkBlockRange
|
||||
for _, daChunkRawTx := range daChunksRawTx {
|
||||
for _, daChunkRawTx := range chunks {
|
||||
if len(daChunkRawTx.Blocks) == 0 {
|
||||
return nil, fmt.Errorf("no blocks found in DA chunk, version: %v", codecVersion)
|
||||
return nil, fmt.Errorf("no blocks found in DA chunk, chunk: %+v", daChunkRawTx)
|
||||
}
|
||||
|
||||
chunkBlockRanges = append(chunkBlockRanges, &rawdb.ChunkBlockRange{
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -25,9 +25,8 @@ import (
|
|||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
zkt "github.com/scroll-tech/zktrie/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/ethdb/leveldb"
|
||||
|
|
|
|||
Loading…
Reference in a new issue