From a6b245c308c0fad680197d028f31190d6a5ee276 Mon Sep 17 00:00:00 2001 From: Marcin Sobczak Date: Fri, 11 Oct 2024 10:20:05 +0200 Subject: [PATCH 1/4] pivot updator for optimism --- .../Synchronization/PivotUpdatorTests.cs | 55 ++++++++--- .../Handlers/BlockCacheService.cs | 1 + .../Handlers/ForkchoiceUpdatedHandler.cs | 2 + .../Handlers/IBlockCacheService.cs | 1 + .../Synchronization/BeaconSync.cs | 5 + .../Synchronization/PivotUpdator.cs | 92 ++++++++++--------- .../OptimismPivotUpdator.cs | 34 +++++++ .../Nethermind.Optimism/OptimismPlugin.cs | 2 +- .../IBeaconSyncStrategy.cs | 2 + 9 files changed, 138 insertions(+), 56 deletions(-) create mode 100644 src/Nethermind/Nethermind.Optimism/OptimismPivotUpdator.cs diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/PivotUpdatorTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/PivotUpdatorTests.cs index 132e1f12c71..f8d4464c546 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/PivotUpdatorTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/PivotUpdatorTests.cs @@ -14,6 +14,7 @@ using Nethermind.Logging; using Nethermind.Merge.Plugin.Handlers; using Nethermind.Merge.Plugin.Synchronization; +using Nethermind.Optimism; using Nethermind.Serialization.Rlp; using Nethermind.Specs; using Nethermind.Specs.Forks; @@ -60,22 +61,22 @@ public void Setup() _blockCacheService = new BlockCacheService(); _beaconSyncStrategy = Substitute.For(); _metadataDb = new MemDb(); - - PivotUpdator pivotUpdator = new( - _blockTree, - _syncModeSelector, - _syncPeerPool, - _syncConfig, - _blockCacheService, - _beaconSyncStrategy, - _metadataDb, - LimboLogs.Instance - ); } [Test] - public void TrySetFreshPivot_SavesFinalizedHashInDb() + public void TrySetFreshPivot_saves_FinalizedHash_in_db() { + PivotUpdator pivotUpdator = new( + _blockTree!, + _syncModeSelector!, + _syncPeerPool!, + _syncConfig!, + _blockCacheService!, + _beaconSyncStrategy!, + _metadataDb!, + LimboLogs.Instance + ); + SyncModeChangedEventArgs args = new(SyncMode.FastSync, SyncMode.UpdatingPivot); Hash256 expectedFinalizedHash = _externalPeerBlockTree!.HeadHash; long expectedPivotBlockNumber = _externalPeerBlockTree!.Head!.Number; @@ -91,5 +92,35 @@ public void TrySetFreshPivot_SavesFinalizedHashInDb() storedFinalizedHash.Should().Be(expectedFinalizedHash); expectedPivotBlockNumber.Should().Be(storedPivotBlockNumber); } + + [Test] + public void TrySetFreshPivot_for_optimism_saves_HeadBlockHash_in_db() + { + OptimismPivotUpdator optimismPivotUpdator = new( + _blockTree!, + _syncModeSelector!, + _syncPeerPool!, + _syncConfig!, + _blockCacheService!, + _beaconSyncStrategy!, + _metadataDb!, + LimboLogs.Instance + ); + + SyncModeChangedEventArgs args = new(SyncMode.FastSync, SyncMode.UpdatingPivot); + Hash256 expectedHeadBlockHash = _externalPeerBlockTree!.HeadHash; + long expectedPivotBlockNumber = _externalPeerBlockTree!.Head!.Number; + _beaconSyncStrategy!.GetHeadBlockHash().Returns(expectedHeadBlockHash); + + _syncModeSelector!.Changed += Raise.EventWith(args); + + byte[] storedData = _metadataDb!.Get(MetadataDbKeys.UpdatedPivotData)!; + RlpStream pivotStream = new(storedData!); + long storedPivotBlockNumber = pivotStream.DecodeLong(); + Hash256 storedHeadBlockHash = pivotStream.DecodeKeccak()!; + + storedHeadBlockHash.Should().Be(expectedHeadBlockHash); + expectedPivotBlockNumber.Should().Be(storedPivotBlockNumber); + } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/BlockCacheService.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/BlockCacheService.cs index dcad32d335f..ac87244b025 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/BlockCacheService.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/BlockCacheService.cs @@ -11,4 +11,5 @@ public class BlockCacheService : IBlockCacheService { public ConcurrentDictionary BlockCache { get; } = new(); public Hash256? FinalizedHash { get; set; } + public Hash256? HeadBlockHash { get; set; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs index f5d95a00ac4..e3178f6d9d1 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs @@ -185,6 +185,7 @@ public async Task> Handle(ForkchoiceSta { _peerRefresher.RefreshPeers(newHeadBlock!.Hash!, newHeadBlock.ParentHash!, forkchoiceState.FinalizedBlockHash); _blockCacheService.FinalizedHash = forkchoiceState.FinalizedBlockHash; + _blockCacheService.HeadBlockHash = forkchoiceState.HeadBlockHash; _mergeSyncController.StopBeaconModeControl(); // Debug as already output in Received ForkChoice @@ -326,6 +327,7 @@ private void StartNewBeaconHeaderSync(ForkchoiceStateV1 forkchoiceState, BlockHe _beaconPivot.ProcessDestination = blockHeader; _peerRefresher.RefreshPeers(blockHeader.Hash!, blockHeader.ParentHash!, forkchoiceState.FinalizedBlockHash); _blockCacheService.FinalizedHash = forkchoiceState.FinalizedBlockHash; + _blockCacheService.HeadBlockHash = forkchoiceState.HeadBlockHash; if (_logger.IsInfo) _logger.Info($"Start a new sync process, Request: {requestStr}."); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IBlockCacheService.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IBlockCacheService.cs index 8e464dda273..080e03dd9f1 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IBlockCacheService.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IBlockCacheService.cs @@ -11,4 +11,5 @@ public interface IBlockCacheService { public ConcurrentDictionary BlockCache { get; } Hash256? FinalizedHash { get; set; } + Hash256? HeadBlockHash { get; set; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconSync.cs b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconSync.cs index 368c5d11516..36583d14744 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconSync.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconSync.cs @@ -124,6 +124,11 @@ lowestInsertedBeaconHeader is not null && { return _blockCacheService.FinalizedHash; } + + public Hash256? GetHeadBlockHash() + { + return _blockCacheService.HeadBlockHash; + } } public interface IMergeSyncController diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/PivotUpdator.cs b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/PivotUpdator.cs index d4f874d75fb..03f65bb7c2a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/PivotUpdator.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/PivotUpdator.cs @@ -27,7 +27,7 @@ public class PivotUpdator private readonly ISyncPeerPool _syncPeerPool; private readonly ISyncConfig _syncConfig; private readonly IBlockCacheService _blockCacheService; - private readonly IBeaconSyncStrategy _beaconSyncStrategy; + protected readonly IBeaconSyncStrategy _beaconSyncStrategy; private readonly IDb _metadataDb; private readonly ILogger _logger; @@ -125,74 +125,80 @@ private async void OnSyncModeChanged(object? sender, SyncModeChangedEventArgs sy private async Task TrySetFreshPivot(CancellationToken cancellationToken) { - Hash256? finalizedBlockHash = TryGetFinalizedBlockHashFromCl(); + Hash256? potentialPivotBlockHash = TryGetPotentialPivotBlockHashFromCl(); - if (finalizedBlockHash is null || finalizedBlockHash == Keccak.Zero) + if (potentialPivotBlockHash is null || potentialPivotBlockHash == Keccak.Zero) { return false; } - long? finalizedBlockNumber = TryGetFinalizedBlockNumberFromBlockCache(finalizedBlockHash); - finalizedBlockNumber ??= TryGetFinalizedBlockNumberFromBlockTree(finalizedBlockHash); - finalizedBlockNumber ??= await TryGetFinalizedBlockNumberFromPeers(finalizedBlockHash, cancellationToken); + long? potentialPivotBlockNumber = TryGetPotentialPivotBlockNumberFromBlockCache(potentialPivotBlockHash); + potentialPivotBlockNumber ??= TryGetPotentialPivotBlockNumberFromBlockTree(potentialPivotBlockHash); + potentialPivotBlockNumber ??= await TryGetPotentialPivotBlockNumberFromPeers(potentialPivotBlockHash, cancellationToken); - return finalizedBlockNumber is not null && TryOverwritePivot(finalizedBlockHash, (long)finalizedBlockNumber); + return potentialPivotBlockNumber is not null && TryOverwritePivot(potentialPivotBlockHash, (long)potentialPivotBlockNumber); } - private Hash256? TryGetFinalizedBlockHashFromCl() + private Hash256? TryGetPotentialPivotBlockHashFromCl() { - Hash256? finalizedBlockHash = _beaconSyncStrategy.GetFinalizedHash(); + Hash256? potentialPivotBlockHash = GetPotentialPivotBlockHash(); - if (finalizedBlockHash is null || finalizedBlockHash == Keccak.Zero) + if (potentialPivotBlockHash is null || potentialPivotBlockHash == Keccak.Zero) { if (_logger.IsInfo && (_maxAttempts - _attemptsLeft) % 10 == 0) _logger.Info($"Waiting for Forkchoice message from Consensus Layer to set fresh pivot block [{_maxAttempts - _attemptsLeft}s]"); return null; } - if (_alreadyAnnouncedNewPivotHash != finalizedBlockHash) + if (_alreadyAnnouncedNewPivotHash != potentialPivotBlockHash) { - if (_logger.IsInfo) _logger.Info($"Potential new pivot block hash: {finalizedBlockHash}"); - _alreadyAnnouncedNewPivotHash = finalizedBlockHash; + if (_logger.IsInfo) _logger.Info($"Potential new pivot block hash: {potentialPivotBlockHash}"); + _alreadyAnnouncedNewPivotHash = potentialPivotBlockHash; } - return finalizedBlockHash; + return potentialPivotBlockHash; } - private long? TryGetFinalizedBlockNumberFromBlockCache(Hash256 finalizedBlockHash) + protected virtual Hash256? GetPotentialPivotBlockHash() + { + // getting finalized block hash as it is safe, because can't be reorganized + return _beaconSyncStrategy.GetFinalizedHash(); + } + + private long? TryGetPotentialPivotBlockNumberFromBlockCache(Hash256 potentialPivotBlockHash) { if (_logger.IsDebug) _logger.Debug("Looking for pivot block in block cache"); - if (_blockCacheService.BlockCache.TryGetValue(finalizedBlockHash, out Block? finalizedBlock)) + if (_blockCacheService.BlockCache.TryGetValue(potentialPivotBlockHash, out Block? potentialPivotBlock)) { - if (HeaderValidator.ValidateHash(finalizedBlock.Header)) + if (HeaderValidator.ValidateHash(potentialPivotBlock.Header)) { if (_logger.IsDebug) _logger.Debug("Found pivot block in block cache"); - return finalizedBlock.Header.Number; + return potentialPivotBlock.Header.Number; } - if (_logger.IsDebug) _logger.Debug($"Hash of header found in block cache is {finalizedBlock.Header.Hash} when expecting {finalizedBlockHash}"); + if (_logger.IsDebug) _logger.Debug($"Hash of header found in block cache is {potentialPivotBlock.Header.Hash} when expecting {potentialPivotBlockHash}"); } return null; } - private long? TryGetFinalizedBlockNumberFromBlockTree(Hash256 finalizedBlockHash) + private long? TryGetPotentialPivotBlockNumberFromBlockTree(Hash256 potentialPivotBlockHash) { if (_logger.IsDebug) _logger.Debug("Looking for header of pivot block in blockTree"); - BlockHeader? finalizedHeader = _blockTree.FindHeader(finalizedBlockHash, BlockTreeLookupOptions.DoNotCreateLevelIfMissing); - if (finalizedHeader is not null) + BlockHeader? potentialPivotBlock = _blockTree.FindHeader(potentialPivotBlockHash, BlockTreeLookupOptions.DoNotCreateLevelIfMissing); + if (potentialPivotBlock is not null) { - if (HeaderValidator.ValidateHash(finalizedHeader)) + if (HeaderValidator.ValidateHash(potentialPivotBlock)) { if (_logger.IsDebug) _logger.Debug("Found header of pivot block in block tree"); - return finalizedHeader.Number; + return potentialPivotBlock.Number; } - if (_logger.IsDebug) _logger.Debug($"Hash of header found in block tree is {finalizedHeader.Hash} when expecting {finalizedBlockHash}"); + if (_logger.IsDebug) _logger.Debug($"Hash of header found in block tree is {potentialPivotBlock.Hash} when expecting {potentialPivotBlockHash}"); } return null; } - private async Task TryGetFinalizedBlockNumberFromPeers(Hash256 finalizedBlockHash, CancellationToken cancellationToken) + private async Task TryGetPotentialPivotBlockNumberFromPeers(Hash256 potentialPivotBlockHash, CancellationToken cancellationToken) { foreach (PeerInfo peer in _syncPeerPool.InitializedPeers) { @@ -202,50 +208,50 @@ private async Task TrySetFreshPivot(CancellationToken cancellationToken) } try { - if (_logger.IsInfo) _logger.Info($"Asking peer {peer.SyncPeer.Node.ClientId} for header of pivot block {finalizedBlockHash}"); - BlockHeader? finalizedHeader = await peer.SyncPeer.GetHeadBlockHeader(finalizedBlockHash, cancellationToken); - if (finalizedHeader is not null) + if (_logger.IsInfo) _logger.Info($"Asking peer {peer.SyncPeer.Node.ClientId} for header of pivot block {potentialPivotBlockHash}"); + BlockHeader? potentialPivotBlock = await peer.SyncPeer.GetHeadBlockHeader(potentialPivotBlockHash, cancellationToken); + if (potentialPivotBlock is not null) { - if (HeaderValidator.ValidateHash(finalizedHeader)) + if (HeaderValidator.ValidateHash(potentialPivotBlock)) { if (_logger.IsInfo) _logger.Info($"Received header of pivot block from peer {peer.SyncPeer.Node.ClientId}"); - return finalizedHeader.Number; + return potentialPivotBlock.Number; } - if (_logger.IsInfo) _logger.Info($"Hash of header received from peer {peer.SyncPeer.Node.ClientId} is {finalizedHeader.Hash} when expecting {finalizedBlockHash}"); + if (_logger.IsInfo) _logger.Info($"Hash of header received from peer {peer.SyncPeer.Node.ClientId} is {potentialPivotBlock.Hash} when expecting {potentialPivotBlockHash}"); } } catch (Exception exception) when (exception is TimeoutException or OperationCanceledException) { - if (_logger.IsInfo) _logger.Info($"Peer {peer.SyncPeer.Node.ClientId} didn't respond to request for header of pivot block {finalizedBlockHash}"); + if (_logger.IsInfo) _logger.Info($"Peer {peer.SyncPeer.Node.ClientId} didn't respond to request for header of pivot block {potentialPivotBlockHash}"); if (_logger.IsDebug) _logger.Debug($"Exception in GetHeadBlockHeader request to peer {peer.SyncPeer.Node.ClientId}. {exception}"); } } - if (_logger.IsInfo && (_maxAttempts - _attemptsLeft) % 10 == 0) _logger.Info($"Potential new pivot block hash: {finalizedBlockHash}. Waiting for pivot block header [{_maxAttempts - _attemptsLeft}s]"); + if (_logger.IsInfo && (_maxAttempts - _attemptsLeft) % 10 == 0) _logger.Info($"Potential new pivot block hash: {potentialPivotBlockHash}. Waiting for pivot block header [{_maxAttempts - _attemptsLeft}s]"); return null; } - private bool TryOverwritePivot(Hash256 finalizedBlockHash, long finalizedBlockNumber) + private bool TryOverwritePivot(Hash256 potentialPivotBlockHash, long potentialPivotBlockNumber) { long targetBlock = _beaconSyncStrategy.GetTargetBlockHeight() ?? 0; - bool isCloseToHead = targetBlock <= finalizedBlockNumber || (targetBlock - finalizedBlockNumber) < Constants.MaxDistanceFromHead; - bool newPivotHigherThanOld = finalizedBlockNumber > _syncConfig.PivotNumberParsed; + bool isCloseToHead = targetBlock <= potentialPivotBlockNumber || (targetBlock - potentialPivotBlockNumber) < Constants.MaxDistanceFromHead; + bool newPivotHigherThanOld = potentialPivotBlockNumber > _syncConfig.PivotNumberParsed; if (isCloseToHead && newPivotHigherThanOld) { - UpdateConfigValues(finalizedBlockHash, finalizedBlockNumber); + UpdateConfigValues(potentialPivotBlockHash, potentialPivotBlockNumber); RlpStream pivotData = new(38); //1 byte (prefix) + 4 bytes (long) + 1 byte (prefix) + 32 bytes (Keccak) - pivotData.Encode(finalizedBlockNumber); - pivotData.Encode(finalizedBlockHash); + pivotData.Encode(potentialPivotBlockNumber); + pivotData.Encode(potentialPivotBlockHash); _metadataDb.Set(MetadataDbKeys.UpdatedPivotData, pivotData.Data.ToArray()!); - if (_logger.IsInfo) _logger.Info($"New pivot block has been set based on ForkChoiceUpdate from CL. Pivot block number: {finalizedBlockNumber}, hash: {finalizedBlockHash}"); + if (_logger.IsInfo) _logger.Info($"New pivot block has been set based on ForkChoiceUpdate from CL. Pivot block number: {potentialPivotBlockNumber}, hash: {potentialPivotBlockHash}"); return true; } - if (!isCloseToHead && _logger.IsInfo) _logger.Info($"Pivot block from Consensus Layer too far from head. PivotBlockNumber: {finalizedBlockNumber}, TargetBlockNumber: {targetBlock}, difference: {targetBlock - finalizedBlockNumber} blocks. Max difference allowed: {Constants.MaxDistanceFromHead}"); - if (!newPivotHigherThanOld && _logger.IsInfo) _logger.Info($"Pivot block from Consensus Layer isn't higher than pivot from initial config. New PivotBlockNumber: {finalizedBlockNumber}, old: {_syncConfig.PivotNumber}"); + if (!isCloseToHead && _logger.IsInfo) _logger.Info($"Pivot block from Consensus Layer too far from head. PivotBlockNumber: {potentialPivotBlockNumber}, TargetBlockNumber: {targetBlock}, difference: {targetBlock - potentialPivotBlockNumber} blocks. Max difference allowed: {Constants.MaxDistanceFromHead}"); + if (!newPivotHigherThanOld && _logger.IsInfo) _logger.Info($"Pivot block from Consensus Layer isn't higher than pivot from initial config. New PivotBlockNumber: {potentialPivotBlockNumber}, old: {_syncConfig.PivotNumber}"); return false; } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismPivotUpdator.cs b/src/Nethermind/Nethermind.Optimism/OptimismPivotUpdator.cs new file mode 100644 index 00000000000..3e6bb16f24b --- /dev/null +++ b/src/Nethermind/Nethermind.Optimism/OptimismPivotUpdator.cs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core.Crypto; +using Nethermind.Db; +using Nethermind.Logging; +using Nethermind.Merge.Plugin.Handlers; +using Nethermind.Merge.Plugin.Synchronization; +using Nethermind.Synchronization; +using Nethermind.Synchronization.ParallelSync; +using Nethermind.Synchronization.Peers; + +namespace Nethermind.Optimism; + +public class OptimismPivotUpdator( + IBlockTree blockTree, + ISyncModeSelector syncModeSelector, + ISyncPeerPool syncPeerPool, + ISyncConfig syncConfig, + IBlockCacheService blockCacheService, + IBeaconSyncStrategy beaconSyncStrategy, + IDb metadataDb, + ILogManager logManager) + : PivotUpdator(blockTree, syncModeSelector, syncPeerPool, syncConfig, + blockCacheService, beaconSyncStrategy, metadataDb, logManager) +{ + protected override Hash256? GetPotentialPivotBlockHash() + { + // getting potentially unsafe head block hash, because optimism isn't providing finalized one until fully synced + return _beaconSyncStrategy.GetHeadBlockHash(); + } +} diff --git a/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs b/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs index 82d7d0665c4..65e4812a58b 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs @@ -180,7 +180,7 @@ public Task InitSynchronization() _api.LogManager ); - _ = new PivotUpdator( + _ = new OptimismPivotUpdator( _api.BlockTree, _api.Synchronizer.SyncModeSelector, _api.SyncPeerPool, diff --git a/src/Nethermind/Nethermind.Synchronization/IBeaconSyncStrategy.cs b/src/Nethermind/Nethermind.Synchronization/IBeaconSyncStrategy.cs index 9043437fe28..51b78b1c351 100644 --- a/src/Nethermind/Nethermind.Synchronization/IBeaconSyncStrategy.cs +++ b/src/Nethermind/Nethermind.Synchronization/IBeaconSyncStrategy.cs @@ -20,6 +20,7 @@ private No() { } public bool MergeTransitionFinished => false; public long? GetTargetBlockHeight() => null; public Hash256? GetFinalizedHash() => null; + public Hash256? GetHeadBlockHash() => null; } public interface IBeaconSyncStrategy @@ -32,5 +33,6 @@ public interface IBeaconSyncStrategy public long? GetTargetBlockHeight(); public Hash256? GetFinalizedHash(); + public Hash256? GetHeadBlockHash(); } } From 23a2a6f03280a86bb0092a99ba16742842c09ac0 Mon Sep 17 00:00:00 2001 From: Marcin Sobczak Date: Fri, 11 Oct 2024 10:37:58 +0200 Subject: [PATCH 2/4] fix file encoding --- src/Nethermind/Nethermind.Optimism/OptimismPivotUpdator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Optimism/OptimismPivotUpdator.cs b/src/Nethermind/Nethermind.Optimism/OptimismPivotUpdator.cs index 3e6bb16f24b..752ca65c855 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismPivotUpdator.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismPivotUpdator.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Blockchain; From db45de3ae576bf075784612079e2b3398bf3b070 Mon Sep 17 00:00:00 2001 From: Marcin Sobczak Date: Fri, 11 Oct 2024 16:08:46 +0200 Subject: [PATCH 3/4] use head - 64 --- .../Synchronization/PivotUpdatorTests.cs | 59 ++++---- .../Synchronization/PivotUpdator.cs | 53 ++++---- .../Synchronization/UnsafePivotUpdator.cs | 128 ++++++++++++++++++ .../OptimismPivotUpdator.cs | 34 ----- .../Nethermind.Optimism/OptimismPlugin.cs | 2 +- 5 files changed, 188 insertions(+), 88 deletions(-) create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Synchronization/UnsafePivotUpdator.cs delete mode 100644 src/Nethermind/Nethermind.Optimism/OptimismPivotUpdator.cs diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/PivotUpdatorTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/PivotUpdatorTests.cs index f8d4464c546..7ffbf82527e 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/PivotUpdatorTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/PivotUpdatorTests.cs @@ -14,7 +14,6 @@ using Nethermind.Logging; using Nethermind.Merge.Plugin.Handlers; using Nethermind.Merge.Plugin.Synchronization; -using Nethermind.Optimism; using Nethermind.Serialization.Rlp; using Nethermind.Specs; using Nethermind.Specs.Forks; @@ -93,34 +92,34 @@ public void TrySetFreshPivot_saves_FinalizedHash_in_db() expectedPivotBlockNumber.Should().Be(storedPivotBlockNumber); } - [Test] - public void TrySetFreshPivot_for_optimism_saves_HeadBlockHash_in_db() - { - OptimismPivotUpdator optimismPivotUpdator = new( - _blockTree!, - _syncModeSelector!, - _syncPeerPool!, - _syncConfig!, - _blockCacheService!, - _beaconSyncStrategy!, - _metadataDb!, - LimboLogs.Instance - ); - - SyncModeChangedEventArgs args = new(SyncMode.FastSync, SyncMode.UpdatingPivot); - Hash256 expectedHeadBlockHash = _externalPeerBlockTree!.HeadHash; - long expectedPivotBlockNumber = _externalPeerBlockTree!.Head!.Number; - _beaconSyncStrategy!.GetHeadBlockHash().Returns(expectedHeadBlockHash); - - _syncModeSelector!.Changed += Raise.EventWith(args); - - byte[] storedData = _metadataDb!.Get(MetadataDbKeys.UpdatedPivotData)!; - RlpStream pivotStream = new(storedData!); - long storedPivotBlockNumber = pivotStream.DecodeLong(); - Hash256 storedHeadBlockHash = pivotStream.DecodeKeccak()!; - - storedHeadBlockHash.Should().Be(expectedHeadBlockHash); - expectedPivotBlockNumber.Should().Be(storedPivotBlockNumber); - } + // [Test] + // public void TrySetFreshPivot_for_optimism_saves_HeadBlockHash_in_db() + // { + // UnsafePivotUpdator unsafePivotUpdator = new( + // _blockTree!, + // _syncModeSelector!, + // _syncPeerPool!, + // _syncConfig!, + // _blockCacheService!, + // _beaconSyncStrategy!, + // _metadataDb!, + // LimboLogs.Instance + // ); + // + // SyncModeChangedEventArgs args = new(SyncMode.FastSync, SyncMode.UpdatingPivot); + // Hash256 expectedHeadBlockHash = _externalPeerBlockTree!.HeadHash; + // long expectedPivotBlockNumber = _externalPeerBlockTree!.Head!.Number; + // _beaconSyncStrategy!.GetHeadBlockHash().Returns(expectedHeadBlockHash); + // + // _syncModeSelector!.Changed += Raise.EventWith(args); + // + // byte[] storedData = _metadataDb!.Get(MetadataDbKeys.UpdatedPivotData)!; + // RlpStream pivotStream = new(storedData!); + // long storedPivotBlockNumber = pivotStream.DecodeLong(); + // Hash256 storedHeadBlockHash = pivotStream.DecodeKeccak()!; + // + // storedHeadBlockHash.Should().Be(expectedHeadBlockHash); + // expectedPivotBlockNumber.Should().Be(storedPivotBlockNumber); + // } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/PivotUpdator.cs b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/PivotUpdator.cs index 03f65bb7c2a..9e76e492efa 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/PivotUpdator.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/PivotUpdator.cs @@ -24,12 +24,12 @@ public class PivotUpdator { private readonly IBlockTree _blockTree; private readonly ISyncModeSelector _syncModeSelector; - private readonly ISyncPeerPool _syncPeerPool; + protected readonly ISyncPeerPool _syncPeerPool; private readonly ISyncConfig _syncConfig; private readonly IBlockCacheService _blockCacheService; protected readonly IBeaconSyncStrategy _beaconSyncStrategy; private readonly IDb _metadataDb; - private readonly ILogger _logger; + protected readonly ILogger _logger; private readonly CancellationTokenSource _cancellation = new(); @@ -125,7 +125,7 @@ private async void OnSyncModeChanged(object? sender, SyncModeChangedEventArgs sy private async Task TrySetFreshPivot(CancellationToken cancellationToken) { - Hash256? potentialPivotBlockHash = TryGetPotentialPivotBlockHashFromCl(); + Hash256? potentialPivotBlockHash = await TryGetPotentialPivotBlockHash(cancellationToken); if (potentialPivotBlockHash is null || potentialPivotBlockHash == Keccak.Zero) { @@ -139,30 +139,19 @@ private async Task TrySetFreshPivot(CancellationToken cancellationToken) return potentialPivotBlockNumber is not null && TryOverwritePivot(potentialPivotBlockHash, (long)potentialPivotBlockNumber); } - private Hash256? TryGetPotentialPivotBlockHashFromCl() + protected virtual Task TryGetPotentialPivotBlockHash(CancellationToken cancellationToken) { - Hash256? potentialPivotBlockHash = GetPotentialPivotBlockHash(); - - if (potentialPivotBlockHash is null || potentialPivotBlockHash == Keccak.Zero) - { - if (_logger.IsInfo && (_maxAttempts - _attemptsLeft) % 10 == 0) _logger.Info($"Waiting for Forkchoice message from Consensus Layer to set fresh pivot block [{_maxAttempts - _attemptsLeft}s]"); - - return null; - } + // getting finalized block hash as it is safe, because can't be reorganized + Hash256? finalizedBlockHash = _beaconSyncStrategy.GetFinalizedHash(); - if (_alreadyAnnouncedNewPivotHash != potentialPivotBlockHash) + if (finalizedBlockHash is null || finalizedBlockHash == Keccak.Zero) { - if (_logger.IsInfo) _logger.Info($"Potential new pivot block hash: {potentialPivotBlockHash}"); - _alreadyAnnouncedNewPivotHash = potentialPivotBlockHash; + PrintWaitingForMessageFromCl(); + return Task.FromResult(null); } - return potentialPivotBlockHash; - } - - protected virtual Hash256? GetPotentialPivotBlockHash() - { - // getting finalized block hash as it is safe, because can't be reorganized - return _beaconSyncStrategy.GetFinalizedHash(); + UpdateAndPrintPotentialNewPivot(finalizedBlockHash); + return Task.FromResult(finalizedBlockHash); } private long? TryGetPotentialPivotBlockNumberFromBlockCache(Hash256 potentialPivotBlockHash) @@ -227,7 +216,7 @@ private async Task TrySetFreshPivot(CancellationToken cancellationToken) } } - if (_logger.IsInfo && (_maxAttempts - _attemptsLeft) % 10 == 0) _logger.Info($"Potential new pivot block hash: {potentialPivotBlockHash}. Waiting for pivot block header [{_maxAttempts - _attemptsLeft}s]"); + PrintPotentialNewPivotAndWaiting(potentialPivotBlockHash.ToString()); return null; } @@ -262,4 +251,22 @@ private void UpdateConfigValues(Hash256 finalizedBlockHash, long finalizedBlockN _syncConfig.MaxAttemptsToUpdatePivot = 0; } + protected void PrintWaitingForMessageFromCl() + { + if (_logger.IsInfo && (_maxAttempts - _attemptsLeft) % 10 == 0) _logger.Info($"Waiting for Forkchoice message from Consensus Layer to set fresh pivot block [{_maxAttempts - _attemptsLeft}s]"); + } + + protected void PrintPotentialNewPivotAndWaiting(string potentialPivotBlockHash) + { + if (_logger.IsInfo && (_maxAttempts - _attemptsLeft) % 10 == 0) _logger.Info($"Potential new pivot block: {potentialPivotBlockHash}. Waiting for pivot block header [{_maxAttempts - _attemptsLeft}s]"); + } + + protected void UpdateAndPrintPotentialNewPivot(Hash256 finalizedBlockHash) + { + if (_alreadyAnnouncedNewPivotHash != finalizedBlockHash) + { + if (_logger.IsInfo) _logger.Info($"Potential new pivot block hash: {finalizedBlockHash}"); + _alreadyAnnouncedNewPivotHash = finalizedBlockHash; + } + } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/UnsafePivotUpdator.cs b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/UnsafePivotUpdator.cs new file mode 100644 index 00000000000..1d0d1977402 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/UnsafePivotUpdator.cs @@ -0,0 +1,128 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Consensus.Validators; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Db; +using Nethermind.Logging; +using Nethermind.Merge.Plugin.Handlers; +using Nethermind.Synchronization; +using Nethermind.Synchronization.ParallelSync; +using Nethermind.Synchronization.Peers; + +namespace Nethermind.Merge.Plugin.Synchronization; + +public class UnsafePivotUpdator( + IBlockTree blockTree, + ISyncModeSelector syncModeSelector, + ISyncPeerPool syncPeerPool, + ISyncConfig syncConfig, + IBlockCacheService blockCacheService, + IBeaconSyncStrategy beaconSyncStrategy, + IDb metadataDb, + ILogManager logManager) + : PivotUpdator(blockTree, syncModeSelector, syncPeerPool, syncConfig, + blockCacheService, beaconSyncStrategy, metadataDb, logManager) +{ + + private const int NumberOfBlocksBehindHeadForSettingPivot = 64; + + protected override async Task TryGetPotentialPivotBlockHash(CancellationToken cancellationToken) + { + // getting potentially unsafe head block hash, because some chains (e.g. optimism) aren't providing finalized block hash until fully synced + Hash256? headBlockHash = _beaconSyncStrategy.GetHeadBlockHash(); + + if (headBlockHash is not null + && headBlockHash != Keccak.Zero) + { + long headBlockNumber = await TryGetHeadBlockNumberFromPeers(headBlockHash, cancellationToken); + if (headBlockNumber > NumberOfBlocksBehindHeadForSettingPivot) + { + long potentialPivotBlockNumber = headBlockNumber - NumberOfBlocksBehindHeadForSettingPivot; + + Hash256? potentialPivotBlockHash = await TryGetPotentialPivotBlockHashFromPeers(potentialPivotBlockNumber, cancellationToken); + + if (potentialPivotBlockHash is not null + && potentialPivotBlockHash != Keccak.Zero) + { + UpdateAndPrintPotentialNewPivot(potentialPivotBlockHash); + return potentialPivotBlockHash; + } + } + } + + PrintWaitingForMessageFromCl(); + return null; + } + + private async Task TryGetHeadBlockNumberFromPeers(Hash256 headBlockHash, CancellationToken cancellationToken) + { + foreach (PeerInfo peer in _syncPeerPool.InitializedPeers) + { + if (cancellationToken.IsCancellationRequested) + { + return 0; + } + try + { + if (_logger.IsInfo) _logger.Info($"Asking peer {peer.SyncPeer.Node.ClientId} for header of head block {headBlockHash}"); + BlockHeader? headBlockHeader = await peer.SyncPeer.GetHeadBlockHeader(headBlockHash, cancellationToken); + if (headBlockHeader is not null) + { + if (HeaderValidator.ValidateHash(headBlockHeader)) + { + if (_logger.IsInfo) _logger.Info($"Received header of head block from peer {peer.SyncPeer.Node.ClientId}"); + return headBlockHeader.Number; + } + if (_logger.IsInfo) _logger.Info($"Hash of header received from peer {peer.SyncPeer.Node.ClientId} is {headBlockHeader.Hash} when expecting {headBlockHash}"); + } + } + catch (Exception exception) when (exception is TimeoutException or OperationCanceledException) + { + if (_logger.IsInfo) _logger.Info($"Peer {peer.SyncPeer.Node.ClientId} didn't respond to request for header of head block {headBlockHash}"); + if (_logger.IsDebug) _logger.Debug($"Exception in GetHeadBlockHeader request to peer {peer.SyncPeer.Node.ClientId}. {exception}"); + } + } + + return 0; + } + + private async Task TryGetPotentialPivotBlockHashFromPeers(long potentialPivotBlockNumber, CancellationToken cancellationToken) + { + foreach (PeerInfo peer in _syncPeerPool.InitializedPeers) + { + if (cancellationToken.IsCancellationRequested) + { + return null; + } + try + { + if (_logger.IsInfo) _logger.Info($"Asking peer {peer.SyncPeer.Node.ClientId} for header of pivot block {potentialPivotBlockNumber}"); + BlockHeader? potentialPivotBlockHeader = (await peer.SyncPeer.GetBlockHeaders(potentialPivotBlockNumber, 1, 0, cancellationToken))?[0]; + if (potentialPivotBlockHeader is not null) + { + if (HeaderValidator.ValidateHash(potentialPivotBlockHeader)) + { + if (_logger.IsInfo) _logger.Info($"Received header of pivot block from peer {peer.SyncPeer.Node.ClientId}"); + return potentialPivotBlockHeader.Hash; + } + if (_logger.IsInfo) _logger.Info($"Header received from peer {peer.SyncPeer.Node.ClientId} wasn't valid"); + } + } + catch (Exception exception) when (exception is TimeoutException or OperationCanceledException) + { + if (_logger.IsInfo) _logger.Info($"Peer {peer.SyncPeer.Node.ClientId} didn't respond to request for header of pivot block {potentialPivotBlockNumber}"); + if (_logger.IsDebug) _logger.Debug($"Exception in GetHeadBlockHeader request to peer {peer.SyncPeer.Node.ClientId}. {exception}"); + } + } + + PrintPotentialNewPivotAndWaiting(potentialPivotBlockNumber.ToString()); + return null; + } +} diff --git a/src/Nethermind/Nethermind.Optimism/OptimismPivotUpdator.cs b/src/Nethermind/Nethermind.Optimism/OptimismPivotUpdator.cs deleted file mode 100644 index 752ca65c855..00000000000 --- a/src/Nethermind/Nethermind.Optimism/OptimismPivotUpdator.cs +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Blockchain; -using Nethermind.Blockchain.Synchronization; -using Nethermind.Core.Crypto; -using Nethermind.Db; -using Nethermind.Logging; -using Nethermind.Merge.Plugin.Handlers; -using Nethermind.Merge.Plugin.Synchronization; -using Nethermind.Synchronization; -using Nethermind.Synchronization.ParallelSync; -using Nethermind.Synchronization.Peers; - -namespace Nethermind.Optimism; - -public class OptimismPivotUpdator( - IBlockTree blockTree, - ISyncModeSelector syncModeSelector, - ISyncPeerPool syncPeerPool, - ISyncConfig syncConfig, - IBlockCacheService blockCacheService, - IBeaconSyncStrategy beaconSyncStrategy, - IDb metadataDb, - ILogManager logManager) - : PivotUpdator(blockTree, syncModeSelector, syncPeerPool, syncConfig, - blockCacheService, beaconSyncStrategy, metadataDb, logManager) -{ - protected override Hash256? GetPotentialPivotBlockHash() - { - // getting potentially unsafe head block hash, because optimism isn't providing finalized one until fully synced - return _beaconSyncStrategy.GetHeadBlockHash(); - } -} diff --git a/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs b/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs index 65e4812a58b..e8689eba72e 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs @@ -180,7 +180,7 @@ public Task InitSynchronization() _api.LogManager ); - _ = new OptimismPivotUpdator( + _ = new UnsafePivotUpdator( _api.BlockTree, _api.Synchronizer.SyncModeSelector, _api.SyncPeerPool, From 56cde3c2fe665b0335a3108404429db060609cc8 Mon Sep 17 00:00:00 2001 From: Marcin Sobczak Date: Fri, 11 Oct 2024 17:26:24 +0200 Subject: [PATCH 4/4] log pivot mode details in debug --- .../ParallelSync/MultiSyncModeSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs index e9eff01f553..0e0cccf8d73 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs @@ -365,7 +365,7 @@ private bool ShouldBeInUpdatingPivot() isPostMerge && stateSyncNotFinished; - if (_logger.IsTrace) + if (_logger.IsDebug) { LogDetailedSyncModeChecks("UPDATING PIVOT", (nameof(updateRequestedAndNotFinished), updateRequestedAndNotFinished),