fix(cicd,cmd,consensus/XDPoS,eth): fix fast sync#2272
Conversation
Add new CLI flags to configure a fixed pivot block for fast sync: - --fastsyncpivotnumber: Pivot block number (0 = use default calculation) - --fastsyncpivothash: Pivot block hash for verification Changes: - Add FastSyncPivotNumber and FastSyncPivotHash to ethconfig.Config - Add pivotNumber and pivotHash fields to Downloader struct - Add SetPivotBlock() method to configure pivot before sync - Use configured pivot in syncWithPeer instead of dynamic calculation - Prevent pivot block from moving during sync when configured - Verify pivot block header hash after state sync completes - Add state root verification after state sync completes This allows operators to sync from a specific trusted checkpoint block instead of relying on the dynamic pivot calculation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changes: - Add pivotGapNumbers field to Downloader struct - Calculate gap pivot numbers in SetPivotBlock() - Sync gap pivot states in processFastSyncContent() before primary pivot
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR aims to fix fast sync on XDPoSChain by making fast sync pivot selection configurable (number/hash/root), adding “gap pivot” state sync + snapshot generation, and adjusting consensus header verification behavior to better support fast-sync header insertion.
Changes:
- Add fast-sync pivot configuration to ethconfig (TOML + CLI flags) and wire it into the downloader at node startup.
- Extend the downloader to support a fixed pivot, optional pivot hash verification, gap-pivot state syncs, and snapshot generation.
- Adjust XDPoS v1/v2 header verification and epoch-switch info lookup to support reduced verification modes used during header-chain validation.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| eth/ethconfig/config.go | Adds FastSyncPivot* fields to the eth configuration struct. |
| eth/ethconfig/gen_config.go | Updates TOML marshal/unmarshal for the new fast-sync pivot fields. |
| eth/downloader/statesync.go | Removes an outdated comment about fast sync usage. |
| eth/downloader/downloader.go | Implements configurable pivot/gap pivot logic and snapshot generation; changes fast-sync header verification constants/behavior. |
| eth/backend.go | Wires configured pivot settings into the downloader during backend initialization. |
| consensus/XDPoS/engines/engine_v2/verifyHeader.go | Adjusts verification flow to use header-provided validators/penalties when not full-verifying. |
| consensus/XDPoS/engines/engine_v2/utils.go | Updates getEpochSwitchInfo call sites for new signature. |
| consensus/XDPoS/engines/engine_v2/timeout.go | Updates getEpochSwitchInfo call sites for new signature. |
| consensus/XDPoS/engines/engine_v2/snapshot.go | Exports snapshot constructor/storage helpers (NewSnapshot/StoreSnapshot). |
| consensus/XDPoS/engines/engine_v2/snapshot_test.go | Updates tests to use exported snapshot helpers. |
| consensus/XDPoS/engines/engine_v2/epochSwitch.go | Refactors getEpochSwitchInfo to accept parent-header slices for VerifyHeaders optimization and softens snapshot failure handling. |
| consensus/XDPoS/engines/engine_v2/engine.go | Updates snapshot usage and verifyQC/getEpochSwitchInfo interactions for new signatures/exports. |
| consensus/XDPoS/engines/engine_v1/engine.go | Skips checkpoint signer checks when not doing full verification. |
| cmd/XDC/main.go | Registers new CLI flags for fast-sync pivot configuration. |
| cmd/utils/flags.go | Defines and applies new fast-sync pivot flags into ethconfig. |
| cicd/testnet/start.sh | Adds environment-driven fast-sync pivot args. |
| cicd/mainnet/start.sh | Adds environment-driven fast-sync pivot args. |
| cicd/local/start.sh | Adds environment-driven fast-sync pivot args. |
| cicd/devnet/start.sh | Adds environment-driven fast-sync pivot args. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 9 comments.
Comments suppressed due to low confidence (1)
eth/downloader/downloader.go:523
- When a fixed pivot is configured, syncWithPeer does not validate that pivotNumber is <= the remote height. If pivotNumber is greater than the chain head, header fetching can terminate while d.committed stays 0, potentially stalling fast sync. Consider validating
d.pivotNumber <= height(and alsod.pivotNumber > 0) up front and returning a configuration error if it's out of range.
if d.pivotNumber != 0 {
// Use configured pivot block
log.Info("Using configured pivot block", "number", d.pivotNumber)
pivot = d.pivotNumber
if pivot <= origin {
origin = pivot - 1
}
} else if height <= uint64(fsMinFullBlocks) {
origin = 0
} else {
pivot = height - uint64(fsMinFullBlocks)
if pivot <= origin {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if !pivotHashSet || pivotHash == "" { | ||
| Fatalf("--%s must be set if --%s is set", FastSyncPivotHashFlag.Name, FastSyncPivotNumberFlag.Name) | ||
| } | ||
| if !pivotRootSet || pivotRoot == "" { | ||
| Fatalf("--%s must be set if --%s is set", FastSyncPivotRootFlag.Name, FastSyncPivotNumberFlag.Name) | ||
| } | ||
| cfg.FastSyncPivotNumber = ctx.Uint64(FastSyncPivotNumberFlag.Name) | ||
| cfg.FastSyncPivotHash = common.HexToHash(pivotHash) | ||
| cfg.FastSyncPivotRoot = common.HexToHash(pivotRoot) |
There was a problem hiding this comment.
The pivot hash/root flags are parsed with common.HexToHash, which silently converts invalid hex strings into the zero hash (hex decoding errors are ignored in common.FromHex/Hex2Bytes). This can accidentally bypass pivot verification or state-root selection. Please validate the input as a 32-byte hex value (e.g., via hexutil.Decode/UnmarshalFixedText) and fail fast on parse/length errors; also reconcile the behavior/usage text that says the root can be zero with the current requirement that the flag must be non-empty.
| if !pivotHashSet || pivotHash == "" { | |
| Fatalf("--%s must be set if --%s is set", FastSyncPivotHashFlag.Name, FastSyncPivotNumberFlag.Name) | |
| } | |
| if !pivotRootSet || pivotRoot == "" { | |
| Fatalf("--%s must be set if --%s is set", FastSyncPivotRootFlag.Name, FastSyncPivotNumberFlag.Name) | |
| } | |
| cfg.FastSyncPivotNumber = ctx.Uint64(FastSyncPivotNumberFlag.Name) | |
| cfg.FastSyncPivotHash = common.HexToHash(pivotHash) | |
| cfg.FastSyncPivotRoot = common.HexToHash(pivotRoot) | |
| if !pivotHashSet { | |
| Fatalf("--%s must be set if --%s is set", FastSyncPivotHashFlag.Name, FastSyncPivotNumberFlag.Name) | |
| } | |
| if !pivotRootSet { | |
| Fatalf("--%s must be set if --%s is set", FastSyncPivotRootFlag.Name, FastSyncPivotNumberFlag.Name) | |
| } | |
| var parsedPivotHash common.Hash | |
| if err = parsedPivotHash.UnmarshalText([]byte(pivotHash)); err != nil { | |
| Fatalf("invalid --%s flag: %v", FastSyncPivotHashFlag.Name, err) | |
| } | |
| var parsedPivotRoot common.Hash | |
| if err = parsedPivotRoot.UnmarshalText([]byte(pivotRoot)); err != nil { | |
| Fatalf("invalid --%s flag: %v", FastSyncPivotRootFlag.Name, err) | |
| } | |
| cfg.FastSyncPivotNumber = ctx.Uint64(FastSyncPivotNumberFlag.Name) | |
| cfg.FastSyncPivotHash = parsedPivotHash | |
| cfg.FastSyncPivotRoot = parsedPivotRoot |
| if !pivotHashSet || pivotHash == "" { | ||
| Fatalf("--%s must be set if --%s is set", FastSyncPivotHashFlag.Name, FastSyncPivotNumberFlag.Name) | ||
| } | ||
| if !pivotRootSet || pivotRoot == "" { | ||
| Fatalf("--%s must be set if --%s is set", FastSyncPivotRootFlag.Name, FastSyncPivotNumberFlag.Name) | ||
| } | ||
| cfg.FastSyncPivotNumber = ctx.Uint64(FastSyncPivotNumberFlag.Name) |
There was a problem hiding this comment.
If the user passes --fastsyncpivotnumber 0, ctx.IsSet will be true and the code will require hash/root, but cfg.FastSyncPivotNumber stays 0 (meaning "use default"), and backend.go will not call SetPivotBlock at all. Consider treating a value of 0 as "not configured" (ignore the other flags) or rejecting it with a clear error when the flag is explicitly set.
| if !pivotHashSet || pivotHash == "" { | |
| Fatalf("--%s must be set if --%s is set", FastSyncPivotHashFlag.Name, FastSyncPivotNumberFlag.Name) | |
| } | |
| if !pivotRootSet || pivotRoot == "" { | |
| Fatalf("--%s must be set if --%s is set", FastSyncPivotRootFlag.Name, FastSyncPivotNumberFlag.Name) | |
| } | |
| cfg.FastSyncPivotNumber = ctx.Uint64(FastSyncPivotNumberFlag.Name) | |
| pivotNumber := ctx.Uint64(FastSyncPivotNumberFlag.Name) | |
| if pivotNumber == 0 { | |
| Fatalf("--%s must be greater than 0 when explicitly set", FastSyncPivotNumberFlag.Name) | |
| } | |
| if !pivotHashSet || pivotHash == "" { | |
| Fatalf("--%s must be set if --%s is set", FastSyncPivotHashFlag.Name, FastSyncPivotNumberFlag.Name) | |
| } | |
| if !pivotRootSet || pivotRoot == "" { | |
| Fatalf("--%s must be set if --%s is set", FastSyncPivotRootFlag.Name, FastSyncPivotNumberFlag.Name) | |
| } | |
| cfg.FastSyncPivotNumber = pivotNumber |
| // Chain layout (Epoch=900, Gap=450, pivot=536, gap pivot=[450]): | ||
| // | ||
| // blocks 1-535 → fast-sync (receipts) | ||
| // block 450 → gap pivot: state synced + snapshot generated | ||
| // block 536 → primary pivot: state synced + committed | ||
| // blocks 537-600 → full-sync | ||
| func TestFastSyncGapPivotSync(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| tester := newTester() | ||
| // XDPoS config is required so SetPivotBlock can compute gap numbers. | ||
| // TestXDPoSMockChainConfig has Epoch=900, Gap=450. | ||
| tester.configOverride = params.TestXDPoSMockChainConfig | ||
| defer tester.terminate() | ||
|
|
||
| // 600 blocks: natural pivot = 600-64 = 536, gap pivot = 450. | ||
| chainLen := 600 |
There was a problem hiding this comment.
The comment describing the chain layout/natural pivot is off by one: pivotNum is computed as (chainLen-1-fsMinFullBlocks) = 535 for chainLen=600, but the comment says pivot=536 and blocks 537-600 full-sync. Please update the comment so it matches the actual pivot calculation to avoid confusion when maintaining the test.
Proposed changes
Fix fast sync. I'll add a doc about how to do fast sync. Tested on private net and devnet.
Types of changes
What types of changes does your code introduce to XDC network?
Put an
✅in the boxes that applyImpacted Components
Which parts of the codebase does this PR touch?
Put an
✅in the boxes that applyChecklist
Put an
✅in the boxes once you have confirmed below actions (or provide reasons on not doing so) that