mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-05 03:23:46 +00:00
This PR improves the sync mode management, disabling the snap mode once the pivot block is committed. Originally the mode will only be flipped from snap to full once the entire sync cycle is completed. However, it's theoretically possible to cancel the sync cycle after committing the pivot but before inserting the remaining blocks. It drags the following sync cycle back to the snap mode. The snap mode should only be used if the head state is missing and unrecoverable. Enabling the snap mode for multiple times should be strictly prevented. What's more, it brings a fix to pivot block management, that once the pivot block is committed, the pivot block marker should be no longer advanced, preventing the situation that the real pivot state is below the pivot marker.
121 lines
4.4 KiB
Go
121 lines
4.4 KiB
Go
// Copyright 2025 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package downloader
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
|
"github.com/ethereum/go-ethereum/ethdb"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
)
|
|
|
|
// syncModer is responsible for managing the downloader's sync mode. It takes the
|
|
// user's preference at startup and then determines the appropriate sync mode
|
|
// based on the current chain status.
|
|
type syncModer struct {
|
|
mode ethconfig.SyncMode
|
|
chain BlockChain
|
|
disk ethdb.KeyValueReader
|
|
lock sync.Mutex
|
|
}
|
|
|
|
func newSyncModer(mode ethconfig.SyncMode, chain BlockChain, disk ethdb.KeyValueReader) *syncModer {
|
|
if mode == ethconfig.FullSync {
|
|
// The database seems empty as the current block is the genesis. Yet the snap
|
|
// block is ahead, so snap sync was enabled for this node at a certain point.
|
|
// The scenarios where this can happen is
|
|
// * if an internal reset (a fork config change or corruption recovery)
|
|
// rolled a snap sync node back below the sync point.
|
|
// * the last snap sync is not finished while user specifies a full sync this
|
|
// time. But we don't have any recent state for full sync.
|
|
// In these cases however it's safe to reenable snap sync.
|
|
fullBlock, snapBlock := chain.CurrentBlock(), chain.CurrentSnapBlock()
|
|
if fullBlock.Number.Uint64() == 0 && snapBlock.Number.Uint64() > 0 {
|
|
mode = ethconfig.SnapSync
|
|
log.Warn("Switching from full-sync to snap-sync", "reason", "snap-sync incomplete")
|
|
} else if !chain.HasState(fullBlock.Root) {
|
|
mode = ethconfig.SnapSync
|
|
log.Warn("Switching from full-sync to snap-sync", "reason", "head state missing")
|
|
} else {
|
|
// Grant the full sync mode
|
|
log.Info("Enabled full-sync", "head", fullBlock.Number, "hash", fullBlock.Hash())
|
|
}
|
|
} else {
|
|
head := chain.CurrentBlock()
|
|
if head.Number.Uint64() > 0 && chain.HasState(head.Root) {
|
|
mode = ethconfig.FullSync
|
|
log.Info("Switching from snap-sync to full-sync", "reason", "snap-sync complete")
|
|
} else {
|
|
// If snap sync was requested and our database is empty, grant it
|
|
log.Info("Enabled snap-sync", "head", head.Number, "hash", head.Hash())
|
|
}
|
|
}
|
|
return &syncModer{
|
|
mode: mode,
|
|
chain: chain,
|
|
disk: disk,
|
|
}
|
|
}
|
|
|
|
// get retrieves the current sync mode, either explicitly set, or derived
|
|
// from the chain status.
|
|
func (m *syncModer) get(report bool) ethconfig.SyncMode {
|
|
m.lock.Lock()
|
|
defer m.lock.Unlock()
|
|
|
|
// If we're in snap sync mode, return that directly
|
|
if m.mode == ethconfig.SnapSync {
|
|
return ethconfig.SnapSync
|
|
}
|
|
logger := log.Debug
|
|
if report {
|
|
logger = log.Info
|
|
}
|
|
// We are probably in full sync, but an internal reset may have rewound the
|
|
// chain below the snap sync pivot, check if we should re-enable snap sync.
|
|
head := m.chain.CurrentBlock()
|
|
if pivot := rawdb.ReadLastPivotNumber(m.disk); pivot != nil {
|
|
if head.Number.Uint64() < *pivot {
|
|
logger("Reenabled snap-sync as chain is lagging behind the pivot", "head", head.Number, "pivot", pivot)
|
|
return ethconfig.SnapSync
|
|
}
|
|
}
|
|
// We are in a full sync, but the associated head state is missing. To complete
|
|
// the head state, forcefully rerun the snap sync. Note it doesn't mean the
|
|
// persistent state is corrupted, just mismatch with the head block.
|
|
if !m.chain.HasState(head.Root) {
|
|
logger("Reenabled snap-sync as chain is stateless")
|
|
return ethconfig.SnapSync
|
|
}
|
|
// Nope, we're really full syncing
|
|
return ethconfig.FullSync
|
|
}
|
|
|
|
// disableSnap disables the snap sync mode and reports whether the mode was
|
|
// actually flipped.
|
|
func (m *syncModer) disableSnap() bool {
|
|
m.lock.Lock()
|
|
defer m.lock.Unlock()
|
|
|
|
if m.mode == ethconfig.FullSync {
|
|
return false
|
|
}
|
|
m.mode = ethconfig.FullSync
|
|
return true
|
|
}
|