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:
Jonas Theis 2025-02-07 18:43:27 +08:00 committed by GitHub
parent ea43834c19
commit b56dd9f31b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 622 additions and 1364 deletions

View file

@ -1641,15 +1641,15 @@ func setEnableRollupVerify(ctx *cli.Context, cfg *ethconfig.Config) {
func setDA(ctx *cli.Context, cfg *ethconfig.Config) { func setDA(ctx *cli.Context, cfg *ethconfig.Config) {
if ctx.IsSet(DASyncEnabledFlag.Name) { if ctx.IsSet(DASyncEnabledFlag.Name) {
cfg.EnableDASyncing = ctx.Bool(DASyncEnabledFlag.Name) cfg.EnableDASyncing = ctx.Bool(DASyncEnabledFlag.Name)
if ctx.IsSet(DABlobScanAPIEndpointFlag.Name) { }
cfg.DA.BlobScanAPIEndpoint = ctx.String(DABlobScanAPIEndpointFlag.Name) if ctx.IsSet(DABlobScanAPIEndpointFlag.Name) {
} cfg.DA.BlobScanAPIEndpoint = ctx.String(DABlobScanAPIEndpointFlag.Name)
if ctx.IsSet(DABlockNativeAPIEndpointFlag.Name) { }
cfg.DA.BlockNativeAPIEndpoint = ctx.String(DABlockNativeAPIEndpointFlag.Name) if ctx.IsSet(DABlockNativeAPIEndpointFlag.Name) {
} cfg.DA.BlockNativeAPIEndpoint = ctx.String(DABlockNativeAPIEndpointFlag.Name)
if ctx.IsSet(DABeaconNodeAPIEndpointFlag.Name) { }
cfg.DA.BeaconNodeAPIEndpoint = ctx.String(DABeaconNodeAPIEndpointFlag.Name) if ctx.IsSet(DABeaconNodeAPIEndpointFlag.Name) {
} cfg.DA.BeaconNodeAPIEndpoint = ctx.String(DABeaconNodeAPIEndpointFlag.Name)
} }
} }

View file

@ -18,7 +18,8 @@ type ChunkBlockRange struct {
// CommittedBatchMeta holds metadata for committed batches. // CommittedBatchMeta holds metadata for committed batches.
type CommittedBatchMeta struct { type CommittedBatchMeta struct {
Version uint8 Version uint8
// BlobVersionedHashes are the versioned hashes of the blobs in the batch. Currently unused. Left for compatibility.
BlobVersionedHashes []common.Hash BlobVersionedHashes []common.Hash
ChunkBlockRanges []*ChunkBlockRange ChunkBlockRanges []*ChunkBlockRange
} }

View file

@ -245,7 +245,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether
if config.EnableRollupVerify { if config.EnableRollupVerify {
// initialize and start rollup event sync service // 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 { if err != nil {
return nil, fmt.Errorf("cannot initialize rollup event sync service: %w", err) return nil, fmt.Errorf("cannot initialize rollup event sync service: %w", err)
} }

View file

@ -24,7 +24,7 @@ import (
const ( const (
VersionMajor = 5 // Major version component of the current release VersionMajor = 5 // Major version component of the current release
VersionMinor = 8 // Minor 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 VersionMeta = "mainnet" // Version metadata to append to the version string
) )

View file

@ -81,6 +81,10 @@ func (ds *CalldataBlobSource) NextData() (Entries, error) {
return da, nil return da, nil
} }
func (ds *CalldataBlobSource) SetL1Height(l1Height uint64) {
ds.l1Height = l1Height
}
func (ds *CalldataBlobSource) L1Height() uint64 { func (ds *CalldataBlobSource) L1Height() uint64 {
return ds.l1Height return ds.l1Height
} }
@ -106,10 +110,22 @@ func (ds *CalldataBlobSource) processRollupEventsToDA(rollupEvents l1.RollupEven
} }
case l1.RevertEventType: 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: 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: default:
return nil, fmt.Errorf("unknown rollup event, type: %v", rollupEvent.Type()) return nil, fmt.Errorf("unknown rollup event, type: %v", rollupEvent.Type())

View file

@ -6,6 +6,7 @@ import (
"github.com/scroll-tech/da-codec/encoding" "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/rawdb"
"github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/ethdb"
@ -14,14 +15,14 @@ import (
) )
type CommitBatchDAV0 struct { type CommitBatchDAV0 struct {
version uint8 version encoding.CodecVersion
batchIndex uint64 batchIndex uint64
parentTotalL1MessagePopped uint64 parentTotalL1MessagePopped uint64
skippedL1MessageBitmap []byte skippedL1MessageBitmap []byte
chunks []*encoding.DAChunkRawTx chunks []*encoding.DAChunkRawTx
l1Txs []*types.L1MessageTx l1Txs []*types.L1MessageTx
l1BlockNumber uint64 event *l1.CommitBatchEvent
} }
func NewCommitBatchDAV0(db ethdb.Database, 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 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, func NewCommitBatchDAV0WithChunks(db ethdb.Database,
version uint8, version encoding.CodecVersion,
batchIndex uint64, batchIndex uint64,
parentBatchHeader []byte, parentBatchHeader []byte,
decodedChunks []*encoding.DAChunkRawTx, decodedChunks []*encoding.DAChunkRawTx,
skippedL1MessageBitmap []byte, skippedL1MessageBitmap []byte,
l1BlockNumber uint64, event *l1.CommitBatchEvent,
) (*CommitBatchDAV0, error) { ) (*CommitBatchDAV0, error) {
parentTotalL1MessagePopped := getBatchTotalL1MessagePopped(parentBatchHeader) parentTotalL1MessagePopped := getBatchTotalL1MessagePopped(parentBatchHeader)
l1Txs, err := getL1Messages(db, parentTotalL1MessagePopped, skippedL1MessageBitmap, getTotalMessagesPoppedFromChunks(decodedChunks)) l1Txs, err := getL1Messages(db, parentTotalL1MessagePopped, skippedL1MessageBitmap, getTotalMessagesPoppedFromChunks(decodedChunks))
@ -60,7 +61,7 @@ func NewCommitBatchDAV0WithChunks(db ethdb.Database,
skippedL1MessageBitmap: skippedL1MessageBitmap, skippedL1MessageBitmap: skippedL1MessageBitmap,
chunks: decodedChunks, chunks: decodedChunks,
l1Txs: l1Txs, l1Txs: l1Txs,
l1BlockNumber: l1BlockNumber, event: event,
}, nil }, 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 { func (c *CommitBatchDAV0) Type() Type {
return CommitBatchV0Type return CommitBatchV0Type
} }
func (c *CommitBatchDAV0) L1BlockNumber() uint64 { 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 { func (c *CommitBatchDAV0) BatchIndex() uint64 {

View file

@ -17,6 +17,8 @@ import (
type CommitBatchDAV1 struct { type CommitBatchDAV1 struct {
*CommitBatchDAV0 *CommitBatchDAV0
versionedHashes []common.Hash
} }
func NewCommitBatchDAWithBlob(ctx context.Context, db ethdb.Database, 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) 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 { if err != nil {
return nil, fmt.Errorf("failed to fetch blob hash, err: %w", err) 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()) header, err := l1Reader.FetchBlockHeaderByNumber(commitEvent.BlockNumber())
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get header by number, err: %w", err) 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") 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 { if err != nil {
return nil, err return nil, err
} }
return &CommitBatchDAV1{v0}, nil return &CommitBatchDAV1{
CommitBatchDAV0: v0,
versionedHashes: versionedHashes,
}, nil
} }
func (c *CommitBatchDAV1) Type() Type { func (c *CommitBatchDAV1) Type() Type {
return CommitBatchWithBlobType return CommitBatchWithBlobType
} }
func (c *CommitBatchDAV1) BlobVersionedHashes() []common.Hash {
return c.versionedHashes
}

View file

@ -3,7 +3,11 @@ package da
import ( import (
"math/big" "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/core/types"
"github.com/scroll-tech/go-ethereum/rollup/l1"
) )
type Type int type Type int
@ -25,11 +29,15 @@ type Entry interface {
BatchIndex() uint64 BatchIndex() uint64
L1BlockNumber() uint64 L1BlockNumber() uint64
CompareTo(Entry) int CompareTo(Entry) int
Event() l1.RollupEvent
} }
type EntryWithBlocks interface { type EntryWithBlocks interface {
Entry Entry
Blocks() []*PartialBlock Blocks() []*PartialBlock
Version() encoding.CodecVersion
Chunks() []*encoding.DAChunkRawTx
BlobVersionedHashes() []common.Hash
} }
type Entries []Entry type Entries []Entry

View file

@ -1,14 +1,16 @@
package da package da
type FinalizeBatch struct { import (
batchIndex uint64 "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{ return &FinalizeBatch{
batchIndex: batchIndex, event: event,
} }
} }
@ -17,11 +19,15 @@ func (f *FinalizeBatch) Type() Type {
} }
func (f *FinalizeBatch) L1BlockNumber() uint64 { func (f *FinalizeBatch) L1BlockNumber() uint64 {
return f.l1BlockNumber return f.event.BlockNumber()
} }
func (f *FinalizeBatch) BatchIndex() uint64 { 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 { func (f *FinalizeBatch) CompareTo(other Entry) int {

View file

@ -1,14 +1,16 @@
package da package da
type RevertBatch struct { import (
batchIndex uint64 "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{ return &RevertBatch{
batchIndex: batchIndex, event: event,
} }
} }
@ -17,10 +19,14 @@ func (r *RevertBatch) Type() Type {
} }
func (r *RevertBatch) L1BlockNumber() uint64 { func (r *RevertBatch) L1BlockNumber() uint64 {
return r.l1BlockNumber return r.event.BlockNumber()
} }
func (r *RevertBatch) BatchIndex() uint64 { 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 { func (r *RevertBatch) CompareTo(other Entry) int {

View file

@ -158,6 +158,26 @@ type FinalizeBatchEvent struct {
blockNumber uint64 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 { func (f *FinalizeBatchEvent) TxHash() common.Hash {
return f.txHash return f.txHash
} }

View file

@ -139,20 +139,23 @@ func (r *Reader) FetchTxData(txHash, blockHash common.Hash) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return tx.Data(), nil return tx.Data(), nil
} }
// FetchTxBlobHash fetches tx blob hash corresponding to given event log // FetchTxBlobHashes fetches tx blob hash corresponding to given event log
func (r *Reader) FetchTxBlobHash(txHash, blockHash common.Hash) (common.Hash, error) { func (r *Reader) FetchTxBlobHashes(txHash, blockHash common.Hash) ([]common.Hash, error) {
tx, err := r.fetchTx(txHash, blockHash) tx, err := r.fetchTx(txHash, blockHash)
if err != nil { 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() blobHashes := tx.BlobHashes()
if len(blobHashes) == 0 { 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]. // FetchRollupEventsInRange retrieves and parses commit/revert/finalize rollup events between block numbers: [from, to].

View file

@ -20,3 +20,41 @@ type Client interface {
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, 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

View file

@ -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)
})
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -3,34 +3,31 @@ package rollup_sync_service
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os" "os"
"reflect"
"sync" "sync"
"time" "time"
"github.com/scroll-tech/da-codec/encoding" "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"
"github.com/scroll-tech/go-ethereum/core/rawdb" "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/ethdb"
"github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/node" "github.com/scroll-tech/go-ethereum/node"
"github.com/scroll-tech/go-ethereum/params" "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/rcfg"
"github.com/scroll-tech/go-ethereum/rollup/sync_service"
"github.com/scroll-tech/go-ethereum/rollup/withdrawtrie" "github.com/scroll-tech/go-ethereum/rollup/withdrawtrie"
) )
const ( 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 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 is the maximum number of retries allowed when the local node is not synced up to the required block height.
defaultMaxRetries = 20 defaultMaxRetries = 20
@ -40,47 +37,27 @@ const (
// of a specific L1 batch finalize event. // of a specific L1 batch finalize event.
defaultGetBlockInRangeRetryDelay = 60 * time.Second 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 defaultLogInterval = 5 * time.Minute
) )
// RollupSyncService collects ScrollChain batch commit/revert/finalize events and stores metadata into db. // RollupSyncService collects ScrollChain batch commit/revert/finalize events and stores metadata into db.
type RollupSyncService struct { type RollupSyncService struct {
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
client *L1Client db ethdb.Database
db ethdb.Database bc *core.BlockChain
latestProcessedBlock uint64 stack *node.Node
scrollChainABI *abi.ABI stateMu sync.Mutex
l1CommitBatchEventSignature common.Hash
l1RevertBatchEventSignature common.Hash callDataBlobSource *da.CalldataBlobSource
l1FinalizeBatchEventSignature common.Hash
bc *core.BlockChain
stack *node.Node
stateMu sync.Mutex
} }
func NewRollupSyncService(ctx context.Context, genesisConfig *params.ChainConfig, db ethdb.Database, l1Client sync_service.EthClient, bc *core.BlockChain, stack *node.Node) (*RollupSyncService, error) { 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) {
// 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
}
if genesisConfig.Scroll.L1Config == nil { if genesisConfig.Scroll.L1Config == nil {
return nil, fmt.Errorf("missing L1 config in genesis") 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. // 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. // This serves as a default value when there's no L1 rollup events synced in the database.
var latestProcessedBlock uint64 var latestProcessedBlock uint64
@ -94,23 +71,57 @@ func NewRollupSyncService(ctx context.Context, genesisConfig *params.ChainConfig
latestProcessedBlock = *block latestProcessedBlock = *block
} }
var success bool
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer func() {
if !success {
cancel()
}
}()
service := RollupSyncService{ l1Reader, err := l1.NewReader(ctx, l1.Config{
ctx: ctx, ScrollChainAddress: genesisConfig.Scroll.L1Config.ScrollChainAddress,
cancel: cancel, L1MessageQueueAddress: genesisConfig.Scroll.L1Config.L1MessageQueueAddress,
client: client, }, l1Client)
db: db, if err != nil {
latestProcessedBlock: latestProcessedBlock, return nil, fmt.Errorf("failed to initialize l1.Reader, err = %w", err)
scrollChainABI: scrollChainABI,
l1CommitBatchEventSignature: scrollChainABI.Events["CommitBatch"].ID,
l1RevertBatchEventSignature: scrollChainABI.Events["RevertBatch"].ID,
l1FinalizeBatchEventSignature: scrollChainABI.Events["FinalizeBatch"].ID,
bc: bc,
stack: stack,
} }
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() { func (s *RollupSyncService) Start() {
@ -118,7 +129,7 @@ func (s *RollupSyncService) Start() {
return 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() { go func() {
syncTicker := time.NewTicker(defaultSyncInterval) syncTicker := time.NewTicker(defaultSyncInterval)
@ -132,9 +143,12 @@ func (s *RollupSyncService) Start() {
case <-s.ctx.Done(): case <-s.ctx.Done():
return return
case <-syncTicker.C: case <-syncTicker.C:
s.fetchRollupEvents() err := s.fetchRollupEvents()
if err != nil {
log.Error("failed to fetch rollup events", "err", err)
}
case <-logTicker.C: 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() s.stateMu.Lock()
defer s.stateMu.Unlock() defer s.stateMu.Unlock()
s.latestProcessedBlock = height s.callDataBlobSource.SetL1Height(height)
log.Info("Reset sync service", "height", height) log.Info("Reset sync service", "height", height)
} }
func (s *RollupSyncService) fetchRollupEvents() { func (s *RollupSyncService) fetchRollupEvents() error {
s.stateMu.Lock() s.stateMu.Lock()
defer s.stateMu.Unlock() defer s.stateMu.Unlock()
latestConfirmed, err := s.client.GetLatestFinalizedBlockNumber() for {
if err != nil { prevL1Height := s.callDataBlobSource.L1Height()
log.Warn("failed to get latest confirmed block number", "err", err)
return
}
log.Trace("Sync service fetch rollup events", "latest processed block", s.latestProcessedBlock, "latest confirmed", latestConfirmed) daEntries, err := s.callDataBlobSource.NextData()
// 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
}
to := from + defaultFetchBlockRange - 1
if to > latestConfirmed {
to = latestConfirmed
}
logs, err := s.client.FetchRollupEventsInRange(from, to)
if err != nil { if err != nil {
log.Error("failed to fetch rollup events in range", "from block", from, "to block", to, "err", err) if errors.Is(err, da.ErrSourceExhausted) {
return log.Trace("Sync service exhausted data source, waiting for next data")
return nil
}
return fmt.Errorf("failed to get next data: %w", err)
} }
if err := s.parseAndUpdateRollupEventLogs(logs, to); err != nil { if err = s.updateRollupEvents(daEntries); err != nil {
log.Error("failed to parse and update rollup event logs", "err", err) // Reset the L1 height to the previous value to retry fetching the same data.
return s.callDataBlobSource.SetL1Height(prevL1Height)
return fmt.Errorf("failed to parse and update rollup event logs: %w", err)
} }
s.latestProcessedBlock = 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) parseAndUpdateRollupEventLogs(logs []types.Log, endBlockNumber uint64) error { func (s *RollupSyncService) updateRollupEvents(daEntries da.Entries) error {
for _, vLog := range logs { for _, entry := range daEntries {
switch vLog.Topics[0] { switch entry.Type() {
case s.l1CommitBatchEventSignature: case da.CommitBatchV0Type, da.CommitBatchWithBlobType:
event := &L1CommitBatchEvent{} log.Trace("found new CommitBatch event", "batch index", entry.BatchIndex())
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) 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 { if err != nil {
return fmt.Errorf("failed to get chunk ranges, batch index: %v, err: %w", batchIndex, err) return fmt.Errorf("failed to get committed batch meta, batch index: %v, err: %w", entry.BatchIndex(), err)
} }
rawdb.WriteCommittedBatchMeta(s.db, batchIndex, committedBatchMeta)
case s.l1RevertBatchEventSignature: rawdb.WriteCommittedBatchMeta(s.db, entry.BatchIndex(), committedBatchMeta)
event := &L1RevertBatchEvent{}
if err := UnpackLog(s.scrollChainABI, event, "RevertBatch", vLog); err != nil { case da.RevertBatchType:
return fmt.Errorf("failed to unpack revert rollup event log, err: %w", err) 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())
} }
batchIndex := event.BatchIndex.Uint64()
log.Trace("found new RevertBatch event", "batch index", batchIndex)
rawdb.DeleteCommittedBatchMeta(s.db, batchIndex) batchIndex := entry.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()
log.Trace("found new FinalizeBatch event", "batch index", batchIndex) log.Trace("found new FinalizeBatch event", "batch index", batchIndex)
lastFinalizedBatchIndex := rawdb.ReadLastFinalizedBatchIndex(s.db) lastFinalizedBatchIndex := rawdb.ReadLastFinalizedBatchIndex(s.db)
// After darwin, FinalizeBatch event emitted every bundle, which contains multiple batches. // 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. // 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` // per single batch. In this situation, `batchIndex` just equals to `*lastFinalizedBatchIndex + 1`
// and only one batch is processed through the for loop. // and only one batch is processed through the for loop.
startBatchIndex := batchIndex 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) log.Debug("write finalized l2 block number", "batch index", batchIndex, "finalized l2 block height", highestFinalizedBlockNumber)
default: 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 return nil
} }
@ -355,8 +354,8 @@ func (s *RollupSyncService) getLocalChunksForBatch(chunkBlockRanges []*rawdb.Chu
return chunks, nil return chunks, nil
} }
func (s *RollupSyncService) getCommittedBatchMeta(batchIndex uint64, vLog *types.Log) (*rawdb.CommittedBatchMeta, error) { func (s *RollupSyncService) getCommittedBatchMeta(commitedBatch da.EntryWithBlocks) (*rawdb.CommittedBatchMeta, error) {
if batchIndex == 0 { if commitedBatch.BatchIndex() == 0 {
return &rawdb.CommittedBatchMeta{ return &rawdb.CommittedBatchMeta{
Version: 0, Version: 0,
BlobVersionedHashes: nil, BlobVersionedHashes: nil,
@ -364,111 +363,16 @@ func (s *RollupSyncService) getCommittedBatchMeta(batchIndex uint64, vLog *types
}, nil }, nil
} }
tx, _, err := s.client.client.TransactionByHash(s.ctx, vLog.TxHash) chunkRanges, err := blockRangesFromChunks(commitedBatch.Chunks())
if err != nil { if err != nil {
log.Debug("failed to get transaction by hash, probably an unindexed transaction, fetching the whole block to get the transaction", return nil, fmt.Errorf("failed to decode block ranges from chunks, batch index: %v, err: %w", commitedBatch.BatchIndex(), err)
"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)
}
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 return &rawdb.CommittedBatchMeta{
Version: uint8(commitedBatch.Version()),
if tx.Type() == types.BlobTxType { ChunkBlockRanges: chunkRanges,
blobVersionedHashes := tx.BlobHashes() BlobVersionedHashes: commitedBatch.BlobVersionedHashes(),
if blobVersionedHashes == nil { }, 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)
} }
// validateBatch verifies the consistency between the L1 contract and L2 node data. // 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. // 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. // 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. // 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 { if len(chunks) == 0 {
return 0, nil, fmt.Errorf("invalid argument: length of chunks is 0, batch index: %v", batchIndex) 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": // 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 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 // - 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 batchIndex == event.BatchIndex().Uint64() {
if localStateRoot != event.StateRoot { 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()) 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() stack.Close()
os.Exit(1) os.Exit(1)
} }
if localWithdrawRoot != event.WithdrawRoot { 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()) 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() stack.Close()
os.Exit(1) os.Exit(1)
} }
@ -556,8 +460,8 @@ func validateBatch(batchIndex uint64, event *L1FinalizeBatchEvent, parentFinaliz
// Verify batch hash // Verify batch hash
// This check ensures the correctness of all batch hashes in the bundle // This check ensures the correctness of all batch hashes in the bundle
// due to the parent-child relationship between batch hashes // due to the parent-child relationship between batch hashes
if localBatchHash != event.BatchHash { 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()) 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) chunksJson, err := json.Marshal(chunks)
if err != nil { if err != nil {
log.Error("marshal chunks failed", "err", err) 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 return endBlock.Header.Number.Uint64(), finalizedBatchMeta, nil
} }
// decodeBlockRangesFromEncodedChunks decodes the provided chunks into a list of block ranges. // blockRangesFromChunks decodes the provided chunks into a list of block ranges.
func decodeBlockRangesFromEncodedChunks(codecVersion encoding.CodecVersion, chunks [][]byte) ([]*rawdb.ChunkBlockRange, error) { func blockRangesFromChunks(chunks []*encoding.DAChunkRawTx) ([]*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)
}
var chunkBlockRanges []*rawdb.ChunkBlockRange var chunkBlockRanges []*rawdb.ChunkBlockRange
for _, daChunkRawTx := range daChunksRawTx { for _, daChunkRawTx := range chunks {
if len(daChunkRawTx.Blocks) == 0 { 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{ chunkBlockRanges = append(chunkBlockRanges, &rawdb.ChunkBlockRange{

File diff suppressed because it is too large Load diff

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

File diff suppressed because one or more lines are too long

View file

@ -25,9 +25,8 @@ import (
"sync" "sync"
"testing" "testing"
"github.com/stretchr/testify/assert"
zkt "github.com/scroll-tech/zktrie/types" 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/common"
"github.com/scroll-tech/go-ethereum/ethdb/leveldb" "github.com/scroll-tech/go-ethereum/ethdb/leveldb"