mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 21:26:42 +00:00
feat(permissionless batches): recovery mode after permissionless batches (#1115)
* 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 * implement first version of new da-codec and to handle multiple batches submitted in one transaction * add CommitBatchDAV7 and handle multiple commit events submitted in a single transactions * fix bug due to previous batch being empty when processing the first batch within a set of batches * Allow using MPT * update to latest da-codec * add field to CommittedBatchMeta to store LastL1MessageQueueHash for CodecV7 batches * adjust rollup verifier to support CodecV7 batches * address review comments * fix issues after merge * go mod tidy * fix unit tests * update da-codec * add test TestValidateBatchCodecV7 * go mod tidy * do not log error on shutdown * add sanity check for version to deserialization of committedBatchMetaV7 * port changes from #1073 * chore: auto version bump [bot] * address review comments * add more logs * disable ENRUpdater if DA sync mode is enabled * exit pipeline if context is cancelled * correctly handle override by setting the head of the chain to the parent's height so that created blocks will always become part of canonical chain * fix error with genesis event being nil * chore: auto version bump [bot] * chore: auto version bump [bot] * goimports --------- Co-authored-by: Ömer Faruk Irmak <omerfirmak@gmail.com> Co-authored-by: Thegaram <Thegaram@users.noreply.github.com> Co-authored-by: jonastheis <jonastheis@users.noreply.github.com> Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
This commit is contained in:
parent
60cbf64f9c
commit
65a0d4e87d
14 changed files with 216 additions and 54 deletions
|
|
@ -178,6 +178,11 @@ var (
|
||||||
utils.DABlockNativeAPIEndpointFlag,
|
utils.DABlockNativeAPIEndpointFlag,
|
||||||
utils.DABlobScanAPIEndpointFlag,
|
utils.DABlobScanAPIEndpointFlag,
|
||||||
utils.DABeaconNodeAPIEndpointFlag,
|
utils.DABeaconNodeAPIEndpointFlag,
|
||||||
|
utils.DARecoveryModeFlag,
|
||||||
|
utils.DARecoveryInitialL1BlockFlag,
|
||||||
|
utils.DARecoveryInitialBatchFlag,
|
||||||
|
utils.DARecoverySignBlocksFlag,
|
||||||
|
utils.DARecoveryL2EndBlockFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
rpcFlags = []cli.Flag{
|
rpcFlags = []cli.Flag{
|
||||||
|
|
|
||||||
|
|
@ -890,22 +890,42 @@ var (
|
||||||
}
|
}
|
||||||
|
|
||||||
// DA syncing settings
|
// DA syncing settings
|
||||||
DASyncEnabledFlag = &cli.BoolFlag{
|
DASyncEnabledFlag = cli.BoolFlag{
|
||||||
Name: "da.sync",
|
Name: "da.sync",
|
||||||
Usage: "Enable node syncing from DA",
|
Usage: "Enable node syncing from DA",
|
||||||
}
|
}
|
||||||
DABlobScanAPIEndpointFlag = &cli.StringFlag{
|
DABlobScanAPIEndpointFlag = cli.StringFlag{
|
||||||
Name: "da.blob.blobscan",
|
Name: "da.blob.blobscan",
|
||||||
Usage: "BlobScan blob API endpoint",
|
Usage: "BlobScan blob API endpoint",
|
||||||
}
|
}
|
||||||
DABlockNativeAPIEndpointFlag = &cli.StringFlag{
|
DABlockNativeAPIEndpointFlag = cli.StringFlag{
|
||||||
Name: "da.blob.blocknative",
|
Name: "da.blob.blocknative",
|
||||||
Usage: "BlockNative blob API endpoint",
|
Usage: "BlockNative blob API endpoint",
|
||||||
}
|
}
|
||||||
DABeaconNodeAPIEndpointFlag = &cli.StringFlag{
|
DABeaconNodeAPIEndpointFlag = cli.StringFlag{
|
||||||
Name: "da.blob.beaconnode",
|
Name: "da.blob.beaconnode",
|
||||||
Usage: "Beacon node API endpoint",
|
Usage: "Beacon node API endpoint",
|
||||||
}
|
}
|
||||||
|
DARecoveryModeFlag = cli.BoolFlag{
|
||||||
|
Name: "da.recovery",
|
||||||
|
Usage: "Enable recovery mode for DA syncing",
|
||||||
|
}
|
||||||
|
DARecoveryInitialL1BlockFlag = cli.Uint64Flag{
|
||||||
|
Name: "da.recovery.initiall1block",
|
||||||
|
Usage: "Initial L1 block to start recovery from",
|
||||||
|
}
|
||||||
|
DARecoveryInitialBatchFlag = cli.Uint64Flag{
|
||||||
|
Name: "da.recovery.initialbatch",
|
||||||
|
Usage: "Initial batch to start recovery from",
|
||||||
|
}
|
||||||
|
DARecoverySignBlocksFlag = cli.BoolFlag{
|
||||||
|
Name: "da.recovery.signblocks",
|
||||||
|
Usage: "Sign blocks during recovery (requires correct Clique signer key and history of blocks with Clique signatures)",
|
||||||
|
}
|
||||||
|
DARecoveryL2EndBlockFlag = cli.Uint64Flag{
|
||||||
|
Name: "da.recovery.l2endblock",
|
||||||
|
Usage: "End L2 block to recover to",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// MakeDataDir retrieves the currently requested data directory, terminating
|
// MakeDataDir retrieves the currently requested data directory, terminating
|
||||||
|
|
@ -1658,6 +1678,21 @@ func setDA(ctx *cli.Context, cfg *ethconfig.Config) {
|
||||||
if ctx.IsSet(DABeaconNodeAPIEndpointFlag.Name) {
|
if ctx.IsSet(DABeaconNodeAPIEndpointFlag.Name) {
|
||||||
cfg.DA.BeaconNodeAPIEndpoint = ctx.String(DABeaconNodeAPIEndpointFlag.Name)
|
cfg.DA.BeaconNodeAPIEndpoint = ctx.String(DABeaconNodeAPIEndpointFlag.Name)
|
||||||
}
|
}
|
||||||
|
if ctx.IsSet(DARecoveryModeFlag.Name) {
|
||||||
|
cfg.DA.RecoveryMode = ctx.Bool(DARecoveryModeFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.IsSet(DARecoveryInitialL1BlockFlag.Name) {
|
||||||
|
cfg.DA.InitialL1Block = ctx.Uint64(DARecoveryInitialL1BlockFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.IsSet(DARecoveryInitialBatchFlag.Name) {
|
||||||
|
cfg.DA.InitialBatch = ctx.Uint64(DARecoveryInitialBatchFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.IsSet(DARecoverySignBlocksFlag.Name) {
|
||||||
|
cfg.DA.SignBlocks = ctx.Bool(DARecoverySignBlocksFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.IsSet(DARecoveryL2EndBlockFlag.Name) {
|
||||||
|
cfg.DA.L2EndBlock = ctx.Uint64(DARecoveryL2EndBlockFlag.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setMaxBlockRange(ctx *cli.Context, cfg *ethconfig.Config) {
|
func setMaxBlockRange(ctx *cli.Context, cfg *ethconfig.Config) {
|
||||||
|
|
|
||||||
|
|
@ -1806,15 +1806,15 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
|
||||||
return it.index, err
|
return it.index, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *BlockChain) BuildAndWriteBlock(parentBlock *types.Block, header *types.Header, txs types.Transactions) (WriteStatus, error) {
|
func (bc *BlockChain) BuildAndWriteBlock(parentBlock *types.Block, header *types.Header, txs types.Transactions, sign bool) (*types.Block, WriteStatus, error) {
|
||||||
if !bc.chainmu.TryLock() {
|
if !bc.chainmu.TryLock() {
|
||||||
return NonStatTy, errInsertionInterrupted
|
return nil, NonStatTy, errInsertionInterrupted
|
||||||
}
|
}
|
||||||
defer bc.chainmu.Unlock()
|
defer bc.chainmu.Unlock()
|
||||||
|
|
||||||
statedb, err := state.New(parentBlock.Root(), bc.stateCache, bc.snaps)
|
statedb, err := state.New(parentBlock.Root(), bc.stateCache, bc.snaps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NonStatTy, err
|
return nil, NonStatTy, err
|
||||||
}
|
}
|
||||||
|
|
||||||
statedb.StartPrefetcher("l1sync", nil)
|
statedb.StartPrefetcher("l1sync", nil)
|
||||||
|
|
@ -1825,18 +1825,51 @@ func (bc *BlockChain) BuildAndWriteBlock(parentBlock *types.Block, header *types
|
||||||
tempBlock := types.NewBlockWithHeader(header).WithBody(txs, nil)
|
tempBlock := types.NewBlockWithHeader(header).WithBody(txs, nil)
|
||||||
receipts, logs, gasUsed, err := bc.processor.Process(tempBlock, statedb, bc.vmConfig)
|
receipts, logs, gasUsed, err := bc.processor.Process(tempBlock, statedb, bc.vmConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NonStatTy, fmt.Errorf("error processing block: %w", err)
|
return nil, NonStatTy, fmt.Errorf("error processing block: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: once we have the extra and difficulty we need to verify the signature of the block with Clique
|
// TODO: once we have the extra and difficulty we need to verify the signature of the block with Clique
|
||||||
// This should be done with https://github.com/scroll-tech/go-ethereum/pull/913.
|
// This should be done with https://github.com/scroll-tech/go-ethereum/pull/913.
|
||||||
|
|
||||||
// finalize and assemble block as fullBlock
|
if sign {
|
||||||
|
// remember the time as Clique will override it
|
||||||
|
originalTime := header.Time
|
||||||
|
|
||||||
|
err = bc.engine.Prepare(bc, header)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NonStatTy, fmt.Errorf("error preparing block %d: %w", tempBlock.Number().Uint64(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we want to re-sign the block: set time to original value again.
|
||||||
|
header.Time = originalTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalize and assemble block as fullBlock: replicates consensus.FinalizeAndAssemble()
|
||||||
header.GasUsed = gasUsed
|
header.GasUsed = gasUsed
|
||||||
header.Root = statedb.IntermediateRoot(bc.chainConfig.IsEIP158(header.Number))
|
header.Root = statedb.IntermediateRoot(bc.chainConfig.IsEIP158(header.Number))
|
||||||
|
|
||||||
fullBlock := types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil))
|
fullBlock := types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil))
|
||||||
|
|
||||||
|
// Sign the block if requested
|
||||||
|
if sign {
|
||||||
|
resultCh, stopCh := make(chan *types.Block), make(chan struct{})
|
||||||
|
if err = bc.engine.Seal(bc, fullBlock, resultCh, stopCh); err != nil {
|
||||||
|
return nil, NonStatTy, fmt.Errorf("error sealing block %d: %w", fullBlock.Number().Uint64(), err)
|
||||||
|
}
|
||||||
|
// Clique.Seal() will only wait for a second before giving up on us. So make sure there is nothing computational heavy
|
||||||
|
// or a call that blocks between the call to Seal and the line below. Seal might introduce some delay, so we keep track of
|
||||||
|
// that artificially added delay and subtract it from overall runtime of commit().
|
||||||
|
fullBlock = <-resultCh
|
||||||
|
if fullBlock == nil {
|
||||||
|
return nil, NonStatTy, fmt.Errorf("sealing block failed %d: block is nil", header.Number.Uint64())
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify the generated block with local consensus engine to make sure everything is as expected
|
||||||
|
if err = bc.engine.VerifyHeader(bc, fullBlock.Header(), true); err != nil {
|
||||||
|
return nil, NonStatTy, fmt.Errorf("error verifying signed block %d: %w", fullBlock.Number().Uint64(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
blockHash := fullBlock.Hash()
|
blockHash := fullBlock.Hash()
|
||||||
// manually replace the block hash in the receipts
|
// manually replace the block hash in the receipts
|
||||||
for i, receipt := range receipts {
|
for i, receipt := range receipts {
|
||||||
|
|
@ -1856,16 +1889,17 @@ func (bc *BlockChain) BuildAndWriteBlock(parentBlock *types.Block, header *types
|
||||||
// Make sure the block body is valid e.g. ordering of L1 messages is correct and continuous.
|
// Make sure the block body is valid e.g. ordering of L1 messages is correct and continuous.
|
||||||
if err = bc.validator.ValidateBody(fullBlock); err != nil {
|
if err = bc.validator.ValidateBody(fullBlock); err != nil {
|
||||||
bc.reportBlock(fullBlock, receipts, err)
|
bc.reportBlock(fullBlock, receipts, err)
|
||||||
return NonStatTy, fmt.Errorf("error validating block body %d: %w", fullBlock.Number().Uint64(), err)
|
return nil, NonStatTy, fmt.Errorf("error validating block body %d: %w", fullBlock.Number().Uint64(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double check: even though we just built the block, make sure it is valid.
|
// Double check: even though we just built the block, make sure it is valid.
|
||||||
if err = bc.validator.ValidateState(fullBlock, statedb, receipts, gasUsed); err != nil {
|
if err = bc.validator.ValidateState(fullBlock, statedb, receipts, gasUsed); err != nil {
|
||||||
bc.reportBlock(fullBlock, receipts, err)
|
bc.reportBlock(fullBlock, receipts, err)
|
||||||
return NonStatTy, fmt.Errorf("error validating block %d: %w", fullBlock.Number().Uint64(), err)
|
return nil, NonStatTy, fmt.Errorf("error validating block %d: %w", fullBlock.Number().Uint64(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return bc.writeBlockWithState(fullBlock, receipts, logs, statedb, false)
|
writeStatus, err := bc.writeBlockWithState(fullBlock, receipts, logs, statedb, false)
|
||||||
|
return fullBlock, writeStatus, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// insertSideChain is called when an import batch hits upon a pruned ancestor
|
// insertSideChain is called when an import batch hits upon a pruned ancestor
|
||||||
|
|
|
||||||
|
|
@ -588,7 +588,10 @@ func (s *Ethereum) Protocols() []p2p.Protocol {
|
||||||
// Start implements node.Lifecycle, starting all internal goroutines needed by the
|
// Start implements node.Lifecycle, starting all internal goroutines needed by the
|
||||||
// Ethereum protocol implementation.
|
// Ethereum protocol implementation.
|
||||||
func (s *Ethereum) Start() error {
|
func (s *Ethereum) Start() error {
|
||||||
|
// handler is not enabled when DA syncing enabled
|
||||||
|
if !s.config.EnableDASyncing {
|
||||||
eth.StartENRUpdater(s.blockchain, s.p2pServer.LocalNode())
|
eth.StartENRUpdater(s.blockchain, s.p2pServer.LocalNode())
|
||||||
|
}
|
||||||
|
|
||||||
// Start the bloom bits servicing goroutines
|
// Start the bloom bits servicing goroutines
|
||||||
s.startBloomHandlers(params.BloomBitsBlocks)
|
s.startBloomHandlers(params.BloomBitsBlocks)
|
||||||
|
|
|
||||||
|
|
@ -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 = 10 // Patch version component of the current release
|
VersionPatch = 11 // 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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,12 @@ func (bq *BatchQueue) NextBatch(ctx context.Context) (da.Entry, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
daEntry, err := bq.DAQueue.NextDA(ctx)
|
daEntry, err := bq.DAQueue.NextDA(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/scroll-tech/go-ethereum/accounts/abi"
|
"github.com/scroll-tech/go-ethereum/accounts/abi"
|
||||||
"github.com/scroll-tech/go-ethereum/common"
|
"github.com/scroll-tech/go-ethereum/common"
|
||||||
"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/rollup/da_syncer/blob_client"
|
"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/da_syncer/serrors"
|
||||||
"github.com/scroll-tech/go-ethereum/rollup/l1"
|
"github.com/scroll-tech/go-ethereum/rollup/l1"
|
||||||
|
|
@ -65,6 +66,8 @@ func (ds *CalldataBlobSource) NextData() (Entries, error) {
|
||||||
to = min(to, ds.l1Finalized)
|
to = min(to, ds.l1Finalized)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debug("Fetching rollup events", "from", ds.l1Height, "to", to, "finalized", ds.l1Finalized)
|
||||||
|
|
||||||
if ds.l1Height > to {
|
if ds.l1Height > to {
|
||||||
return nil, ErrSourceExhausted
|
return nil, ErrSourceExhausted
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +197,7 @@ func (ds *CalldataBlobSource) getCommitBatchDA(commitEvents []*l1.CommitBatchEve
|
||||||
}
|
}
|
||||||
|
|
||||||
if commitEvents[0].BatchIndex().Uint64() == 0 {
|
if commitEvents[0].BatchIndex().Uint64() == 0 {
|
||||||
return Entries{NewCommitBatchDAV0Empty()}, nil
|
return Entries{NewCommitBatchDAV0Empty(commitEvents[0])}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
firstCommitEvent := commitEvents[0]
|
firstCommitEvent := commitEvents[0]
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"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"
|
||||||
|
"github.com/scroll-tech/go-ethereum/log"
|
||||||
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/serrors"
|
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/serrors"
|
||||||
"github.com/scroll-tech/go-ethereum/rollup/l1"
|
"github.com/scroll-tech/go-ethereum/rollup/l1"
|
||||||
)
|
)
|
||||||
|
|
@ -65,9 +66,10 @@ func NewCommitBatchDAV0WithChunks(db ethdb.Database,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommitBatchDAV0Empty() *CommitBatchDAV0 {
|
func NewCommitBatchDAV0Empty(event *l1.CommitBatchEvent) *CommitBatchDAV0 {
|
||||||
return &CommitBatchDAV0{
|
return &CommitBatchDAV0{
|
||||||
batchIndex: 0,
|
batchIndex: 0,
|
||||||
|
event: event,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,6 +174,7 @@ func getL1Messages(db ethdb.Database, parentTotalL1MessagePopped uint64, skipped
|
||||||
}
|
}
|
||||||
l1Tx := rawdb.ReadL1Message(db, currentIndex)
|
l1Tx := rawdb.ReadL1Message(db, currentIndex)
|
||||||
if l1Tx == nil {
|
if l1Tx == nil {
|
||||||
|
log.Info("L1 message not yet available", "index", currentIndex)
|
||||||
// message not yet available
|
// message not yet available
|
||||||
// we return serrors.EOFError as this will be handled in the syncing pipeline with a backoff and retry
|
// we return serrors.EOFError as this will be handled in the syncing pipeline with a backoff and retry
|
||||||
return nil, serrors.EOFError
|
return nil, serrors.EOFError
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"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/log"
|
||||||
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/blob_client"
|
"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/da_syncer/serrors"
|
||||||
"github.com/scroll-tech/go-ethereum/rollup/l1"
|
"github.com/scroll-tech/go-ethereum/rollup/l1"
|
||||||
|
|
@ -168,6 +169,7 @@ func getL1MessagesV7(db ethdb.Database, blocks []encoding.DABlock, initialL1Mess
|
||||||
for i := messageIndex; i < messageIndex+uint64(block.NumL1Messages()); i++ {
|
for i := messageIndex; i < messageIndex+uint64(block.NumL1Messages()); i++ {
|
||||||
l1Tx := rawdb.ReadL1Message(db, i)
|
l1Tx := rawdb.ReadL1Message(db, i)
|
||||||
if l1Tx == nil {
|
if l1Tx == nil {
|
||||||
|
log.Info("L1 message not yet available", "index", i)
|
||||||
// message not yet available
|
// message not yet available
|
||||||
// we return serrors.EOFError as this will be handled in the syncing pipeline with a backoff and retry
|
// we return serrors.EOFError as this will be handled in the syncing pipeline with a backoff and retry
|
||||||
return nil, serrors.EOFError
|
return nil, serrors.EOFError
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/scroll-tech/go-ethereum/log"
|
||||||
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/da"
|
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/da"
|
||||||
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/serrors"
|
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/serrors"
|
||||||
)
|
)
|
||||||
|
|
@ -11,14 +12,17 @@ import (
|
||||||
// DAQueue is a pipeline stage that reads DA entries from a DataSource and provides them to the next stage.
|
// DAQueue is a pipeline stage that reads DA entries from a DataSource and provides them to the next stage.
|
||||||
type DAQueue struct {
|
type DAQueue struct {
|
||||||
l1height uint64
|
l1height uint64
|
||||||
|
initialBatch uint64
|
||||||
|
|
||||||
dataSourceFactory *DataSourceFactory
|
dataSourceFactory *DataSourceFactory
|
||||||
dataSource DataSource
|
dataSource DataSource
|
||||||
da da.Entries
|
da da.Entries
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDAQueue(l1height uint64, dataSourceFactory *DataSourceFactory) *DAQueue {
|
func NewDAQueue(l1height uint64, initialBatch uint64, dataSourceFactory *DataSourceFactory) *DAQueue {
|
||||||
return &DAQueue{
|
return &DAQueue{
|
||||||
l1height: l1height,
|
l1height: l1height,
|
||||||
|
initialBatch: initialBatch,
|
||||||
dataSourceFactory: dataSourceFactory,
|
dataSourceFactory: dataSourceFactory,
|
||||||
dataSource: nil,
|
dataSource: nil,
|
||||||
da: make(da.Entries, 0),
|
da: make(da.Entries, 0),
|
||||||
|
|
@ -26,22 +30,31 @@ func NewDAQueue(l1height uint64, dataSourceFactory *DataSourceFactory) *DAQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dq *DAQueue) NextDA(ctx context.Context) (da.Entry, error) {
|
func (dq *DAQueue) NextDA(ctx context.Context) (da.Entry, error) {
|
||||||
for len(dq.da) == 0 {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for len(dq.da) == 0 {
|
||||||
err := dq.getNextData(ctx)
|
err := dq.getNextData(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
daEntry := dq.da[0]
|
daEntry := dq.da[0]
|
||||||
dq.da = dq.da[1:]
|
dq.da = dq.da[1:]
|
||||||
|
|
||||||
|
if daEntry.BatchIndex() < dq.initialBatch {
|
||||||
|
log.Debug("Skipping DA entry due to initial batch requirement", "batchIndex", daEntry.BatchIndex(), "initialBatch", dq.initialBatch)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
return daEntry, nil
|
return daEntry, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (dq *DAQueue) getNextData(ctx context.Context) error {
|
func (dq *DAQueue) getNextData(ctx context.Context) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/scroll-tech/go-ethereum/core"
|
"github.com/scroll-tech/go-ethereum/core"
|
||||||
"github.com/scroll-tech/go-ethereum/log"
|
"github.com/scroll-tech/go-ethereum/log"
|
||||||
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/da"
|
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/da"
|
||||||
|
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/serrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -14,21 +15,24 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type DASyncer struct {
|
type DASyncer struct {
|
||||||
|
l2EndBlock uint64
|
||||||
blockchain *core.BlockChain
|
blockchain *core.BlockChain
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDASyncer(blockchain *core.BlockChain) *DASyncer {
|
func NewDASyncer(blockchain *core.BlockChain, l2EndBlock uint64) *DASyncer {
|
||||||
return &DASyncer{
|
return &DASyncer{
|
||||||
|
l2EndBlock: l2EndBlock,
|
||||||
blockchain: blockchain,
|
blockchain: blockchain,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncOneBlock receives a PartialBlock, makes sure it's the next block in the chain, executes it and inserts it to the blockchain.
|
// SyncOneBlock receives a PartialBlock, makes sure it's the next block in the chain, executes it and inserts it to the blockchain.
|
||||||
func (s *DASyncer) SyncOneBlock(block *da.PartialBlock) error {
|
func (s *DASyncer) SyncOneBlock(block *da.PartialBlock, override bool, sign bool) error {
|
||||||
currentBlock := s.blockchain.CurrentBlock()
|
currentBlock := s.blockchain.CurrentBlock()
|
||||||
|
|
||||||
// we expect blocks to be consecutive. block.PartialHeader.Number == parentBlock.Number+1.
|
// we expect blocks to be consecutive. block.PartialHeader.Number == parentBlock.Number+1.
|
||||||
if block.PartialHeader.Number <= currentBlock.Number().Uint64() {
|
// if override is true, we allow blocks to be lower than the current block number and replace the blocks.
|
||||||
|
if !override && block.PartialHeader.Number <= currentBlock.Number().Uint64() {
|
||||||
log.Debug("block number is too low", "block number", block.PartialHeader.Number, "parent block number", currentBlock.Number().Uint64())
|
log.Debug("block number is too low", "block number", block.PartialHeader.Number, "parent block number", currentBlock.Number().Uint64())
|
||||||
return ErrBlockTooLow
|
return ErrBlockTooLow
|
||||||
} else if block.PartialHeader.Number > currentBlock.Number().Uint64()+1 {
|
} else if block.PartialHeader.Number > currentBlock.Number().Uint64()+1 {
|
||||||
|
|
@ -36,17 +40,41 @@ func (s *DASyncer) SyncOneBlock(block *da.PartialBlock) error {
|
||||||
return ErrBlockTooHigh
|
return ErrBlockTooHigh
|
||||||
}
|
}
|
||||||
|
|
||||||
parentBlock := s.blockchain.GetBlockByNumber(currentBlock.Number().Uint64())
|
parentBlockNumber := currentBlock.Number().Uint64()
|
||||||
if parentBlock == nil {
|
if override {
|
||||||
return fmt.Errorf("parent block not found at height %d", currentBlock.Number().Uint64())
|
parentBlockNumber = block.PartialHeader.Number - 1
|
||||||
|
// reset the chain head to the parent block so that the new block can be inserted as part of the new canonical chain.
|
||||||
|
err := s.blockchain.SetHead(parentBlockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed setting head, number: %d, error: %v", parentBlockNumber, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := s.blockchain.BuildAndWriteBlock(parentBlock, block.PartialHeader.ToHeader(), block.Transactions); err != nil {
|
parentBlock := s.blockchain.GetBlockByNumber(parentBlockNumber)
|
||||||
|
if parentBlock == nil {
|
||||||
|
return fmt.Errorf("failed getting parent block, number: %d", parentBlockNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
fullBlock, writeStatus, err := s.blockchain.BuildAndWriteBlock(parentBlock, block.PartialHeader.ToHeader(), block.Transactions, sign)
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed building and writing block, number: %d, error: %v", block.PartialHeader.Number, err)
|
return fmt.Errorf("failed building and writing block, number: %d, error: %v", block.PartialHeader.Number, err)
|
||||||
}
|
}
|
||||||
|
if writeStatus != core.CanonStatTy {
|
||||||
|
return fmt.Errorf("failed writing block as part of canonical chain, number: %d, status: %d", block.PartialHeader.Number, writeStatus)
|
||||||
|
}
|
||||||
|
|
||||||
if s.blockchain.CurrentBlock().Number().Uint64()%1000 == 0 {
|
currentBlock = s.blockchain.CurrentBlock()
|
||||||
log.Info("L1 sync progress", "blockchain height", s.blockchain.CurrentBlock().Number().Uint64(), "block hash", s.blockchain.CurrentBlock().Hash().Hex(), "root", s.blockchain.CurrentBlock().Root().Hex())
|
if currentBlock.Number().Uint64() != fullBlock.NumberU64() || currentBlock.Hash() != fullBlock.Hash() {
|
||||||
|
return fmt.Errorf("failed to insert block: not part of canonical chain, number: %d, hash: %s - canonical: number: %d, hash: %s", fullBlock.NumberU64(), fullBlock.Hash(), currentBlock.Number().Uint64(), currentBlock.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullBlock.Number().Uint64()%100 == 0 {
|
||||||
|
log.Info("L1 sync progress", "blockchain height", fullBlock.Number().Uint64(), "block hash", fullBlock.Hash(), "root", fullBlock.Root())
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.l2EndBlock > 0 && s.l2EndBlock == block.PartialHeader.Number {
|
||||||
|
log.Warn("L1 sync reached L2EndBlock: you can terminate recovery mode now", "L2EndBlock", fullBlock.NumberU64(), "block hash", fullBlock.Hash(), "root", fullBlock.Root())
|
||||||
|
return serrors.Terminated
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const (
|
||||||
var (
|
var (
|
||||||
TemporaryError = NewTemporaryError(nil)
|
TemporaryError = NewTemporaryError(nil)
|
||||||
EOFError = NewEOFError(nil)
|
EOFError = NewEOFError(nil)
|
||||||
|
Terminated = fmt.Errorf("terminated")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Type uint8
|
type Type uint8
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ type Config struct {
|
||||||
BlobScanAPIEndpoint string // BlobScan blob api endpoint
|
BlobScanAPIEndpoint string // BlobScan blob api endpoint
|
||||||
BlockNativeAPIEndpoint string // BlockNative blob api endpoint
|
BlockNativeAPIEndpoint string // BlockNative blob api endpoint
|
||||||
BeaconNodeAPIEndpoint string // Beacon node api endpoint
|
BeaconNodeAPIEndpoint string // Beacon node api endpoint
|
||||||
|
|
||||||
|
RecoveryMode bool // Recovery mode is used to override existing blocks with the blocks read from the pipeline and start from a specific L1 block and batch
|
||||||
|
InitialL1Block uint64 // L1 block in which the InitialBatch was committed (or any earlier L1 block but requires more RPC requests)
|
||||||
|
InitialBatch uint64 // Batch number from which to start syncing and overriding blocks
|
||||||
|
SignBlocks bool // Whether to sign the blocks after reading them from the pipeline (requires correct Clique signer key) and history of blocks with Clique signatures
|
||||||
|
L2EndBlock uint64 // L2 block number to sync until
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncingPipeline is a derivation pipeline for syncing data from L1 and DA and transform it into
|
// SyncingPipeline is a derivation pipeline for syncing data from L1 and DA and transform it into
|
||||||
|
|
@ -33,7 +39,7 @@ type SyncingPipeline struct {
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
expBackoff *backoff.Exponential
|
expBackoff *backoff.Exponential
|
||||||
|
|
||||||
l1DeploymentBlock uint64
|
config Config
|
||||||
|
|
||||||
db ethdb.Database
|
db ethdb.Database
|
||||||
blockchain *core.BlockChain
|
blockchain *core.BlockChain
|
||||||
|
|
@ -71,16 +77,31 @@ func NewSyncingPipeline(ctx context.Context, blockchain *core.BlockChain, genesi
|
||||||
}
|
}
|
||||||
|
|
||||||
dataSourceFactory := NewDataSourceFactory(blockchain, genesisConfig, config, l1Reader, blobClientList, db)
|
dataSourceFactory := NewDataSourceFactory(blockchain, genesisConfig, config, l1Reader, blobClientList, db)
|
||||||
syncedL1Height := l1DeploymentBlock - 1
|
var initialL1Block uint64
|
||||||
from := rawdb.ReadDASyncedL1BlockNumber(db)
|
if config.RecoveryMode {
|
||||||
if from != nil {
|
initialL1Block = config.InitialL1Block
|
||||||
syncedL1Height = *from
|
if initialL1Block == 0 {
|
||||||
|
return nil, errors.New("sync from DA: initial L1 block must be set in recovery mode")
|
||||||
|
}
|
||||||
|
if config.InitialBatch == 0 {
|
||||||
|
return nil, errors.New("sync from DA: initial batch must be set in recovery mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
daQueue := NewDAQueue(syncedL1Height, dataSourceFactory)
|
log.Info("sync from DA: initializing pipeline in recovery mode", "initialL1Block", initialL1Block, "initialBatch", config.InitialBatch)
|
||||||
|
} else {
|
||||||
|
initialL1Block = l1DeploymentBlock - 1
|
||||||
|
config.InitialL1Block = initialL1Block
|
||||||
|
from := rawdb.ReadDASyncedL1BlockNumber(db)
|
||||||
|
if from != nil {
|
||||||
|
initialL1Block = *from
|
||||||
|
}
|
||||||
|
log.Info("sync from DA: initializing pipeline", "initialL1Block", initialL1Block)
|
||||||
|
}
|
||||||
|
|
||||||
|
daQueue := NewDAQueue(initialL1Block, config.InitialBatch, dataSourceFactory)
|
||||||
batchQueue := NewBatchQueue(daQueue, db)
|
batchQueue := NewBatchQueue(daQueue, db)
|
||||||
blockQueue := NewBlockQueue(batchQueue)
|
blockQueue := NewBlockQueue(batchQueue)
|
||||||
daSyncer := NewDASyncer(blockchain)
|
daSyncer := NewDASyncer(blockchain, config.L2EndBlock)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
return &SyncingPipeline{
|
return &SyncingPipeline{
|
||||||
|
|
@ -88,7 +109,7 @@ func NewSyncingPipeline(ctx context.Context, blockchain *core.BlockChain, genesi
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
expBackoff: backoff.NewExponential(100*time.Millisecond, 10*time.Second, 100*time.Millisecond),
|
expBackoff: backoff.NewExponential(100*time.Millisecond, 10*time.Second, 100*time.Millisecond),
|
||||||
wg: sync.WaitGroup{},
|
wg: sync.WaitGroup{},
|
||||||
l1DeploymentBlock: l1DeploymentBlock,
|
config: config,
|
||||||
db: db,
|
db: db,
|
||||||
blockchain: blockchain,
|
blockchain: blockchain,
|
||||||
blockQueue: blockQueue,
|
blockQueue: blockQueue,
|
||||||
|
|
@ -102,7 +123,10 @@ func (s *SyncingPipeline) Step() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = s.daSyncer.SyncOneBlock(block)
|
|
||||||
|
// in recovery mode, we override already existing blocks with whatever we read from the pipeline
|
||||||
|
err = s.daSyncer.SyncOneBlock(block, s.config.RecoveryMode, s.config.SignBlocks)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,6 +207,7 @@ func (s *SyncingPipeline) mainLoop() {
|
||||||
// pipeline is empty, request a delayed step
|
// 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
|
// TODO: eventually (with state manager) this should not trigger a delayed step because external events will trigger a new step anyway
|
||||||
reqStep(true)
|
reqStep(true)
|
||||||
|
log.Debug("syncing pipeline is empty, requesting delayed step")
|
||||||
tempErrorCounter = 0
|
tempErrorCounter = 0
|
||||||
continue
|
continue
|
||||||
} else if errors.Is(err, serrors.TemporaryError) {
|
} else if errors.Is(err, serrors.TemporaryError) {
|
||||||
|
|
@ -213,6 +238,9 @@ func (s *SyncingPipeline) mainLoop() {
|
||||||
} else if errors.Is(err, context.Canceled) {
|
} else if errors.Is(err, context.Canceled) {
|
||||||
log.Info("syncing pipeline stopped due to cancelled context", "err", err)
|
log.Info("syncing pipeline stopped due to cancelled context", "err", err)
|
||||||
return
|
return
|
||||||
|
} else if errors.Is(err, serrors.Terminated) {
|
||||||
|
log.Info("syncing pipeline stopped due to terminated state", "err", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warn("syncing pipeline step failed due to unrecoverable error, stopping pipeline worker", "err", err)
|
log.Warn("syncing pipeline step failed due to unrecoverable error, stopping pipeline worker", "err", err)
|
||||||
|
|
@ -230,7 +258,7 @@ func (s *SyncingPipeline) Stop() {
|
||||||
|
|
||||||
func (s *SyncingPipeline) reset(resetCounter int) {
|
func (s *SyncingPipeline) reset(resetCounter int) {
|
||||||
amount := 100 * uint64(resetCounter)
|
amount := 100 * uint64(resetCounter)
|
||||||
syncedL1Height := s.l1DeploymentBlock - 1
|
syncedL1Height := s.config.InitialL1Block
|
||||||
from := rawdb.ReadDASyncedL1BlockNumber(s.db)
|
from := rawdb.ReadDASyncedL1BlockNumber(s.db)
|
||||||
if from != nil && *from+amount > syncedL1Height {
|
if from != nil && *from+amount > syncedL1Height {
|
||||||
syncedL1Height = *from - amount
|
syncedL1Height = *from - amount
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,11 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/scroll-tech/da-codec/encoding"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/scroll-tech/da-codec/encoding"
|
||||||
|
|
||||||
"github.com/scroll-tech/go-ethereum/common"
|
"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"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue