diff --git a/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs b/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs index 4694d5ead47..bf48359a21c 100644 --- a/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs +++ b/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs @@ -164,4 +164,7 @@ public interface ISyncConfig : IConfig [ConfigItem(Description = "_Technical._ Run explicit GC after state sync finished.", DefaultValue = "true", HiddenFromDocs = true)] bool GCOnFeedFinished { get; set; } + + [ConfigItem(Description = "_Technical._ Max distance between best suggested header and available state to assume state is synced.", DefaultValue = "0", HiddenFromDocs = true)] + int HeaderStateDistance { get; set; } } diff --git a/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs b/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs index 6ce0c295c5e..0207b26417d 100644 --- a/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs +++ b/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs @@ -73,6 +73,12 @@ public string? PivotHash public int StateMaxDistanceFromHead { get; set; } = 128; public int StateMinDistanceFromHead { get; set; } = 32; public bool GCOnFeedFinished { get; set; } = true; + /// + /// Additional delay in blocks between best suggested header and synced state to allow faster state switching for PoW chains + /// with higher block processing frequency. Effectively this is the max allowed difference between best header (used as sync + /// pivot) and synced state block, to assume that state is synced and node can start processing blocks + /// + public int HeaderStateDistance { get; set; } = 0; public override string ToString() { diff --git a/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json index d749643923d..482ec5aa48a 100644 --- a/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json @@ -18,10 +18,11 @@ "SnapSync": true, "PivotNumber": 13310000, "PivotHash": "0x4f899c26f26ac8e85272864201f46e030ffed46372dd052998bee8463255fcff", - "PivotTotalDifficulty": "26620001" + "PivotTotalDifficulty": "26620001", + "HeaderStateDistance": 6 }, "JsonRpc": { "Enabled": true, "Port": 8545 } -} \ No newline at end of file +} diff --git a/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json index 2be2d755f1f..a34234f9798 100644 --- a/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json @@ -18,10 +18,11 @@ "SnapSync": true, "PivotNumber": 7090000, "PivotHash": "0xf8e4888bd895671f2290cce1caa4ac326d896df16136476b97a3c3abdb866ada", - "PivotTotalDifficulty": "14180001" + "PivotTotalDifficulty": "14180001", + "HeaderStateDistance": 6 }, "JsonRpc": { "Enabled": true, "Port": 8545 } -} \ No newline at end of file +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorBeaconTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorBeaconTests.cs index b47a353f91c..be2dcae22c9 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorBeaconTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorBeaconTests.cs @@ -336,6 +336,18 @@ public void When_just_started_full_sync() .TheSyncModeShouldBe(GetBeaconSyncExpectations(SyncMode.Full)); } + [Test] + public void When_finished_state_sync_and_header_moved_forward() + { + Scenario.GoesLikeThis(_needToWaitForHeaders) + .WhenInBeaconSyncMode(BeaconSync.None) + .IfThisNodeJustFinishedStateSyncButBehindHeader(FastBlocksState.FinishedHeaders) + .AndGoodPeersAreKnown() + .WhenSnapSyncIsConfigured() + .WhenStateAndBestHeaderCanBeBeDifferent(6) //allow best state to be max 6 block apart from best block header + .TheSyncModeShouldBe(SyncMode.Full | SyncMode.FastBodies); + } + [TestCase(FastBlocksState.None)] [TestCase(FastBlocksState.FinishedHeaders)] [TestCase(FastBlocksState.FinishedBodies)] diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs index 8e94ff818b5..e675ab62333 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs @@ -468,6 +468,25 @@ public ScenarioBuilder IfThisNodeJustStartedFullSyncProcessing(FastBlocksState f return this; } + public ScenarioBuilder IfThisNodeJustFinishedStateSyncButBehindHeader(FastBlocksState fastBlocksState = FastBlocksState.FinishedReceipts) + { + long currentBlock = ChainHead.Number - MultiSyncModeSelector.FastSyncLag; + _syncProgressSetups.Add( + () => + { + SyncProgressResolver.FindBestHeader().Returns(currentBlock); + SyncProgressResolver.FindBestFullBlock().Returns(0); //no full blocks available + SyncProgressResolver.FindBestFullState().Returns(currentBlock - 4); //pivot is set to header, but then header follows the head of the chain + SyncProgressResolver.FindBestProcessedBlock().Returns(0); + SyncProgressResolver.IsFastBlocksFinished().Returns(fastBlocksState); + SyncProgressResolver.ChainDifficulty.Returns((UInt256)currentBlock); + SyncProgressResolver.IsSnapGetRangesFinished().Returns(true); + return "just started full sync"; + } + ); + return this; + } + public ScenarioBuilder IfThisNodeRecentlyStartedFullSyncProcessing(FastBlocksState fastBlocksState = FastBlocksState.FinishedReceipts) { long currentBlock = ChainHead.Number - MultiSyncModeSelector.FastSyncLag / 2; @@ -673,6 +692,12 @@ public ScenarioBuilder WhenThisNodeIsLoadingBlocksFromDb() return this; } + public ScenarioBuilder WhenStateAndBestHeaderCanBeBeDifferent(int maxBlockDiff) + { + _overwrites.Add(() => SyncConfig.HeaderStateDistance = maxBlockDiff); + return this; + } + public ScenarioBuilder ThenInAnySyncConfiguration() { WhenFullArchiveSyncIsConfigured(); diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs index 39eb81fc72d..3459b243bb9 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs @@ -64,6 +64,7 @@ public class MultiSyncModeSelector : ISyncModeSelector private long FastSyncCatchUpHeightDelta => _syncConfig.FastSyncCatchUpHeightDelta ?? FastSyncLag; private bool NotNeedToWaitForHeaders => !_needToWaitForHeaders || FastBlocksHeadersFinished; private long? LastBlockThatEnabledFullSync { get; set; } + private int TotalSyncLag => FastSyncLag + _syncConfig.HeaderStateDistance; private readonly CancellationTokenSource _cancellation = new(); @@ -399,7 +400,7 @@ private bool ShouldBeInFastSyncMode(Snapshot best) // and we need to sync away from it. // Note: its ok if target block height is not accurate as long as long full sync downloader does not stop // earlier than this condition below which would cause a hang. - bool notReachedFullSyncTransition = best.Header < best.TargetBlock - FastSyncLag; + bool notReachedFullSyncTransition = best.Header < best.TargetBlock - TotalSyncLag; bool notInAStickyFullSync = !IsInAStickyFullSyncMode(best); bool notHasJustStartedFullSync = !HasJustStartedFullSync(best); @@ -589,8 +590,8 @@ private bool ShouldBeInStateSyncMode(Snapshot best) bool notInFastSync = !best.IsInFastSync; bool notNeedToWaitForHeaders = NotNeedToWaitForHeaders; bool stickyStateNodes = best.TargetBlock - best.Header < (FastSyncLag + StickyStateNodesDelta); - bool stateNotDownloadedYet = (best.TargetBlock - best.State > FastSyncLag || - best.Header > best.State && best.Header > best.Block); + bool stateNotDownloadedYet = (best.TargetBlock - best.State > TotalSyncLag || + best.Header > (best.State + _syncConfig.HeaderStateDistance) && best.Header > best.Block); bool notInAStickyFullSync = !IsInAStickyFullSyncMode(best); bool notHasJustStartedFullSync = !HasJustStartedFullSync(best); @@ -644,7 +645,7 @@ private bool ShouldBeInStateNodesMode(Snapshot best) private bool ShouldBeInSnapRangesPhase(Snapshot best) { bool isInStateSync = best.IsInStateSync; - bool isCloseToHead = best.TargetBlock >= best.Header && (best.TargetBlock - best.Header) <= FastSyncLag; + bool isCloseToHead = best.TargetBlock >= best.Header && (best.TargetBlock - best.Header) <= TotalSyncLag; bool snapNotFinished = !_syncProgressResolver.IsSnapGetRangesFinished(); if (_logger.IsTrace)