mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 15:16:43 +00:00
* 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 * bump version * remove unused flag * update da-codec commit --------- Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
231 lines
7 KiB
Go
231 lines
7 KiB
Go
package da_syncer
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/scroll-tech/go-ethereum/common/backoff"
|
|
"github.com/scroll-tech/go-ethereum/core"
|
|
"github.com/scroll-tech/go-ethereum/core/rawdb"
|
|
"github.com/scroll-tech/go-ethereum/ethdb"
|
|
"github.com/scroll-tech/go-ethereum/log"
|
|
"github.com/scroll-tech/go-ethereum/params"
|
|
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/blob_client"
|
|
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/serrors"
|
|
"github.com/scroll-tech/go-ethereum/rollup/rollup_sync_service"
|
|
"github.com/scroll-tech/go-ethereum/rollup/sync_service"
|
|
)
|
|
|
|
// Config is the configuration parameters of data availability syncing.
|
|
type Config struct {
|
|
BlobScanAPIEndpoint string // BlobScan blob api endpoint
|
|
BlockNativeAPIEndpoint string // BlockNative blob api endpoint
|
|
BeaconNodeAPIEndpoint string // Beacon node api endpoint
|
|
}
|
|
|
|
// SyncingPipeline is a derivation pipeline for syncing data from L1 and DA and transform it into
|
|
// L2 blocks and chain.
|
|
type SyncingPipeline struct {
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
wg sync.WaitGroup
|
|
expBackoff *backoff.Exponential
|
|
|
|
l1DeploymentBlock uint64
|
|
|
|
db ethdb.Database
|
|
blockchain *core.BlockChain
|
|
blockQueue *BlockQueue
|
|
daSyncer *DASyncer
|
|
}
|
|
|
|
func NewSyncingPipeline(ctx context.Context, blockchain *core.BlockChain, genesisConfig *params.ChainConfig, db ethdb.Database, ethClient sync_service.EthClient, l1DeploymentBlock uint64, config Config) (*SyncingPipeline, error) {
|
|
scrollChainABI, err := rollup_sync_service.ScrollChainMetaData.GetAbi()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get scroll chain abi: %w", err)
|
|
}
|
|
|
|
l1Client, err := rollup_sync_service.NewL1Client(ctx, ethClient, genesisConfig.Scroll.L1Config.L1ChainId, genesisConfig.Scroll.L1Config.ScrollChainAddress, scrollChainABI)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
blobClientList := blob_client.NewBlobClients()
|
|
if config.BeaconNodeAPIEndpoint != "" {
|
|
beaconNodeClient, err := blob_client.NewBeaconNodeClient(config.BeaconNodeAPIEndpoint, l1Client)
|
|
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("DA syncing is enabled but no blob client is configured. Please provide at least one blob client via command line flag")
|
|
}
|
|
|
|
dataSourceFactory := NewDataSourceFactory(blockchain, genesisConfig, config, l1Client, blobClientList, db)
|
|
syncedL1Height := l1DeploymentBlock - 1
|
|
from := rawdb.ReadDASyncedL1BlockNumber(db)
|
|
if from != nil {
|
|
syncedL1Height = *from
|
|
}
|
|
|
|
daQueue := NewDAQueue(syncedL1Height, dataSourceFactory)
|
|
batchQueue := NewBatchQueue(daQueue, db)
|
|
blockQueue := NewBlockQueue(batchQueue)
|
|
daSyncer := NewDASyncer(blockchain)
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
return &SyncingPipeline{
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
expBackoff: backoff.NewExponential(100*time.Millisecond, 10*time.Second, 100*time.Millisecond),
|
|
wg: sync.WaitGroup{},
|
|
l1DeploymentBlock: l1DeploymentBlock,
|
|
db: db,
|
|
blockchain: blockchain,
|
|
blockQueue: blockQueue,
|
|
daSyncer: daSyncer,
|
|
}, nil
|
|
}
|
|
|
|
func (s *SyncingPipeline) Step() error {
|
|
block, err := s.blockQueue.NextBlock(s.ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = s.daSyncer.SyncOneBlock(block)
|
|
return err
|
|
}
|
|
|
|
func (s *SyncingPipeline) Start() {
|
|
log.Info("sync from DA: starting pipeline")
|
|
|
|
s.wg.Add(1)
|
|
go func() {
|
|
s.mainLoop()
|
|
s.wg.Done()
|
|
}()
|
|
}
|
|
|
|
func (s *SyncingPipeline) mainLoop() {
|
|
stepCh := make(chan struct{}, 1)
|
|
var delayedStepCh <-chan time.Time
|
|
var resetCounter int
|
|
var tempErrorCounter int
|
|
|
|
// reqStep is a helper function to request a step to be executed.
|
|
// If delay is true, it will request a delayed step with exponential backoff, otherwise it will request an immediate step.
|
|
reqStep := func(delay bool) {
|
|
if delay {
|
|
if delayedStepCh == nil {
|
|
delayDur := s.expBackoff.NextDuration()
|
|
delayedStepCh = time.After(delayDur)
|
|
log.Debug("requesting delayed step", "delay", delayDur, "attempt", s.expBackoff.Attempt())
|
|
} else {
|
|
log.Debug("ignoring step request because of ongoing delayed step", "attempt", s.expBackoff.Attempt())
|
|
}
|
|
} else {
|
|
select {
|
|
case stepCh <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
// start pipeline
|
|
reqStep(false)
|
|
|
|
for {
|
|
select {
|
|
case <-s.ctx.Done():
|
|
return
|
|
default:
|
|
}
|
|
|
|
select {
|
|
case <-s.ctx.Done():
|
|
return
|
|
case <-delayedStepCh:
|
|
delayedStepCh = nil
|
|
reqStep(false)
|
|
case <-stepCh:
|
|
err := s.Step()
|
|
if err == nil {
|
|
// step succeeded, reset exponential backoff and continue
|
|
reqStep(false)
|
|
s.expBackoff.Reset()
|
|
resetCounter = 0
|
|
tempErrorCounter = 0
|
|
continue
|
|
}
|
|
|
|
if errors.Is(err, serrors.EOFError) {
|
|
// pipeline is empty, request a delayed step
|
|
// TODO: eventually (with state manager) this should not trigger a delayed step because external events will trigger a new step anyway
|
|
reqStep(true)
|
|
tempErrorCounter = 0
|
|
continue
|
|
} else if errors.Is(err, serrors.TemporaryError) {
|
|
log.Warn("syncing pipeline step failed due to temporary error, retrying", "err", err)
|
|
if tempErrorCounter > 100 {
|
|
log.Warn("syncing pipeline step failed due to 100 consecutive temporary errors, stopping pipeline worker", "last err", err)
|
|
return
|
|
}
|
|
|
|
// temporary error, request a delayed step
|
|
reqStep(true)
|
|
tempErrorCounter++
|
|
continue
|
|
} else if errors.Is(err, ErrBlockTooLow) {
|
|
// block number returned by the block queue is too low,
|
|
// we skip the blocks until we reach the correct block number again.
|
|
reqStep(false)
|
|
tempErrorCounter = 0
|
|
continue
|
|
} else if errors.Is(err, ErrBlockTooHigh) {
|
|
// block number returned by the block queue is too high,
|
|
// reset the pipeline and move backwards from the last L1 block we read
|
|
s.reset(resetCounter)
|
|
resetCounter++
|
|
reqStep(false)
|
|
tempErrorCounter = 0
|
|
continue
|
|
} else if errors.Is(err, context.Canceled) {
|
|
log.Info("syncing pipeline stopped due to cancelled context", "err", err)
|
|
return
|
|
}
|
|
|
|
log.Warn("syncing pipeline step failed due to unrecoverable error, stopping pipeline worker", "err", err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *SyncingPipeline) Stop() {
|
|
log.Info("sync from DA: stopping pipeline...")
|
|
s.cancel()
|
|
s.wg.Wait()
|
|
log.Info("sync from DA: stopping pipeline... done")
|
|
}
|
|
|
|
func (s *SyncingPipeline) reset(resetCounter int) {
|
|
amount := 100 * uint64(resetCounter)
|
|
syncedL1Height := s.l1DeploymentBlock - 1
|
|
from := rawdb.ReadDASyncedL1BlockNumber(s.db)
|
|
if from != nil && *from+amount > syncedL1Height {
|
|
syncedL1Height = *from - amount
|
|
rawdb.WriteDASyncedL1BlockNumber(s.db, syncedL1Height)
|
|
}
|
|
log.Info("resetting syncing pipeline", "syncedL1Height", syncedL1Height)
|
|
s.blockQueue.Reset(syncedL1Height)
|
|
}
|