Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust Optimism (Mainnet) TotalDifficulty calculation #7647

Merged
merged 6 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public BeaconHeadersSyncFeed(
IMergeConfig? mergeConfig,
IInvalidChainTracker invalidChainTracker,
ILogManager logManager)
: base(blockTree, syncPeerPool, syncConfig, syncReport, logManager, true) // alwaysStartHeaderSync = true => for the merge we're forcing header sync start. It doesn't matter if it is archive sync or fast sync
: base(blockTree, syncPeerPool, syncConfig, syncReport, logManager, alwaysStartHeaderSync: true) // alwaysStartHeaderSync = true => for the merge we're forcing header sync start. It doesn't matter if it is archive sync or fast sync
{
_poSSwitcher = poSSwitcher ?? throw new ArgumentNullException(nameof(poSSwitcher));
_pivot = pivot ?? throw new ArgumentNullException(nameof(pivot));
Expand Down Expand Up @@ -79,7 +79,7 @@ protected override void ResetPivot()
// First, we assume pivot
_pivotNumber = ExpectedPivotNumber;
_nextHeaderHash = ExpectedPivotHash;
_nextHeaderDiff = _poSSwitcher.FinalTotalDifficulty;
_nextHeaderTotalDifficulty = _poSSwitcher.FinalTotalDifficulty;

long startNumber = _pivotNumber;

Expand All @@ -89,7 +89,7 @@ protected override void ResetPivot()
{
startNumber = lowestInserted.Number - 1;
_nextHeaderHash = lowestInserted.ParentHash ?? Keccak.Zero;
_nextHeaderDiff = lowestInserted.TotalDifficulty - lowestInserted.Difficulty;
_nextHeaderTotalDifficulty = lowestInserted.TotalDifficulty - lowestInserted.Difficulty;
}

// the base class with starts with _lowestRequestedHeaderNumber - 1, so we offset it here.
Expand Down Expand Up @@ -168,7 +168,7 @@ protected override AddBlockResult InsertToBlockTree(BlockHeader header)
_logger.Trace(
$"Adding new header in beacon headers sync {header.ToString(BlockHeader.Format.FullHashAndNumber)}");
BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.BeaconHeaderInsert;
if (_nextHeaderDiff is null)
if (_nextHeaderTotalDifficulty is null)
{
headerOptions |= BlockTreeInsertHeaderOptions.TotalDifficultyNotNeeded;
}
Expand All @@ -188,7 +188,7 @@ protected override AddBlockResult InsertToBlockTree(BlockHeader header)
if (insertOutcome == AddBlockResult.Added || insertOutcome == AddBlockResult.AlreadyKnown)
{
_nextHeaderHash = header.ParentHash!;
_nextHeaderDiff = header.TotalDifficulty is not null && header.TotalDifficulty >= header.Difficulty
_nextHeaderTotalDifficulty = header.TotalDifficulty is not null && header.TotalDifficulty >= header.Difficulty
? header.TotalDifficulty - header.Difficulty
: null;
}
Expand Down
1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ public Task InitSynchronization()

builder.RegisterModule(new SynchronizerModule(_syncConfig));
builder.RegisterModule(new MergeSynchronizerModule());
builder.RegisterModule(new OptimismSynchronizerModule(_api.ChainSpec.Optimism, _api.SpecProvider));

IContainer container = builder.Build();

Expand Down
36 changes: 36 additions & 0 deletions src/Nethermind/Nethermind.Optimism/OptimismSynchronizerModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using Autofac;
using Nethermind.Blockchain.Synchronization;
using Nethermind.Core;
using Nethermind.Core.Specs;
using Nethermind.Specs.ChainSpecStyle;
using Nethermind.Synchronization;

namespace Nethermind.Optimism;

/// <remarks>
/// In Optimism Mainnet, the <see cref="BlockHeader.TotalDifficulty"/> gets resetted to <c>0</c> in the Bedrock block unlike other chains that went through The Merge fork.
/// Calculation is still the same: the current block's <see cref="BlockHeader.TotalDifficulty"/> is the parent's <see cref="BlockHeader.TotalDifficulty"/> plus the current block's <see cref="BlockHeader.Difficulty"/>.
/// <seealso href="https://github.com/NethermindEth/nethermind/issues/7626"/>
/// </remarks>
public sealed class OptimismSynchronizerModule(OptimismParameters parameters, ISpecProvider provider) : Module
{
private const ulong OptimismMainnetChainId = 0xA;

protected override void Load(ContainerBuilder builder)
{
if (provider.ChainId == OptimismMainnetChainId)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is not needed. Just have one OptimismSynchronizerModule for just optimism configuration, the register it later after MergeSynchronizerModule.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check is actually needed since we want to use a particular difficulty calculation strategy in Optimism Mainnet and not in, for example, Optimism Sepolia.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is unfortunate.

{
builder.AddSingleton<ITotalDifficultyStrategy>(
new FixedTotalDifficultyStrategy(
new CumulativeTotalDifficultyStrategy(),
fixesBlockNumber: parameters.BedrockBlockNumber - 1,
toTotalDifficulty: provider.TerminalTotalDifficulty ?? throw new ArgumentNullException(nameof(provider.TerminalTotalDifficulty))
)
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ public ResettableHeaderSyncFeed(
long? hangOnBlockNumberAfterInsert = null,
ManualResetEventSlim? hangLatch = null,
bool alwaysStartHeaderSync = false
) : base(blockTree, syncPeerPool, syncConfig, syncReport, logManager, alwaysStartHeaderSync)
) : base(blockTree, syncPeerPool, syncConfig, syncReport, logManager, alwaysStartHeaderSync: alwaysStartHeaderSync)
{
_hangOnBlockNumber = hangOnBlockNumber;
_hangOnBlockNumberAfterInsert = hangOnBlockNumberAfterInsert;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Collections.Generic;
using FluentAssertions;
using Nethermind.Core;
using Nethermind.Core.Test.Builders;
using Nethermind.Int256;
using NUnit.Framework;

namespace Nethermind.Synchronization.Test;

public class TotalDifficultyStrategyTests
{
[Test]
public void CumulativeTotalDifficultyStrategy_own_TotalDifficulty_is_Parent_TotalDifficulty_plus_own_difficulty()
{
ITotalDifficultyStrategy strategy = new CumulativeTotalDifficultyStrategy();

List<(BlockHeader Header, UInt256 ExpectedTotalDifficulty)> headers =
[
(Build.A.BlockHeader.WithDifficulty(10).WithTotalDifficulty(39).TestObject, 39),
(Build.A.BlockHeader.WithDifficulty(8).TestObject, 29),
(Build.A.BlockHeader.WithDifficulty(5).TestObject, 21),
(Build.A.BlockHeader.WithDifficulty(12).TestObject, 16),
(Build.A.BlockHeader.WithDifficulty(3).TestObject, 4),
(Build.A.BlockHeader.WithDifficulty(1).TestObject, 1),
];

for (int i = 0; i < headers.Count - 1; i++)
{
var header = headers[i].Header;
var parent = headers[i + 1].Header;

parent.TotalDifficulty = strategy.ParentTotalDifficulty(header);
}

foreach (var (header, expectedTotalDifficulty) in headers)
{
header.TotalDifficulty.Should().Be(expectedTotalDifficulty);
}
}

[Test]
public void FixedTotalDifficultyStrategy_fixes_a_block_header_to_specific_total_difficulty()
{
ITotalDifficultyStrategy strategy = new FixedTotalDifficultyStrategy(
new CumulativeTotalDifficultyStrategy(),
fixesBlockNumber: 3,
toTotalDifficulty: 21
);

List<(BlockHeader Header, UInt256 ExpectedTotalDifficulty)> headers =
[
(Build.A.BlockHeader.WithNumber(5).WithDifficulty(0).WithTotalDifficulty(0).TestObject, 0),
(Build.A.BlockHeader.WithNumber(4).WithDifficulty(0).TestObject, 0),
(Build.A.BlockHeader.WithNumber(3).WithDifficulty(5).TestObject, 21),
(Build.A.BlockHeader.WithNumber(2).WithDifficulty(12).TestObject, 16),
(Build.A.BlockHeader.WithNumber(1).WithDifficulty(3).TestObject, 4),
(Build.A.BlockHeader.WithNumber(0).WithDifficulty(1).TestObject, 1),
];

for (int i = 0; i < headers.Count - 1; i++)
{
var header = headers[i].Header;
var parent = headers[i + 1].Header;

parent.TotalDifficulty = strategy.ParentTotalDifficulty(header);
}

foreach (var (header, expectedTotalDifficulty) in headers)
{
header.TotalDifficulty.Should().Be(expectedTotalDifficulty);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ public class HeadersSyncFeed : ActivatedSyncFeed<HeadersSyncBatch?>
protected readonly ISyncReport _syncReport;
protected readonly IBlockTree _blockTree;
protected readonly ISyncConfig _syncConfig;
private readonly ITotalDifficultyStrategy _totalDifficultyStrategy;

private readonly object _handlerLock = new();

private readonly int _headersRequestSize = GethSyncLimits.MaxHeaderFetch;
protected long _lowestRequestedHeaderNumber;

protected Hash256 _nextHeaderHash;
protected UInt256? _nextHeaderDiff;
protected UInt256? _nextHeaderTotalDifficulty;

protected long _pivotNumber;

Expand Down Expand Up @@ -154,13 +155,15 @@ public HeadersSyncFeed(
ISyncConfig? syncConfig,
ISyncReport? syncReport,
ILogManager? logManager,
ITotalDifficultyStrategy? totalDifficultyStrategy = null,
bool alwaysStartHeaderSync = false)
{
_syncPeerPool = syncPeerPool ?? throw new ArgumentNullException(nameof(syncPeerPool));
_syncReport = syncReport ?? throw new ArgumentNullException(nameof(syncReport));
_blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree));
_syncConfig = syncConfig ?? throw new ArgumentNullException(nameof(syncConfig));
_logger = logManager?.GetClassLogger<HeadersSyncFeed>() ?? throw new ArgumentNullException(nameof(HeadersSyncFeed));
_totalDifficultyStrategy = totalDifficultyStrategy ?? new CumulativeTotalDifficultyStrategy();

if (!_syncConfig.UseGethLimitsInFastBlocks)
{
Expand Down Expand Up @@ -196,7 +199,7 @@ protected virtual void ResetPivot()
_pivotNumber = _syncConfig.PivotNumberParsed;
_lowestRequestedHeaderNumber = _pivotNumber + 1; // Because we want the pivot to be requested
_nextHeaderHash = _syncConfig.PivotHashParsed;
_nextHeaderDiff = _syncConfig.PivotTotalDifficultyParsed;
_nextHeaderTotalDifficulty = _syncConfig.PivotTotalDifficultyParsed;

// Resume logic
BlockHeader? lowestInserted = _blockTree.LowestInsertedHeader;
Expand Down Expand Up @@ -610,7 +613,7 @@ protected virtual int InsertHeaders(HeadersSyncBatch batch)
}
}

header.TotalDifficulty = _nextHeaderDiff;
header.TotalDifficulty = _nextHeaderTotalDifficulty;
AddBlockResult addBlockResult = InsertHeader(header);
if (addBlockResult == AddBlockResult.InvalidBlock)
{
Expand Down Expand Up @@ -713,8 +716,9 @@ protected virtual AddBlockResult InsertToBlockTree(BlockHeader header)
protected void SetExpectedNextHeaderToParent(BlockHeader header)
{
_nextHeaderHash = header.ParentHash!;
_nextHeaderDiff = (header.TotalDifficulty ?? 0) - header.Difficulty;
_nextHeaderTotalDifficulty = _totalDifficultyStrategy.ParentTotalDifficulty(header);
}

private bool _disposed = false;
public override void Dispose()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using Nethermind.Core;
using Nethermind.Int256;

namespace Nethermind.Synchronization;

public interface ITotalDifficultyStrategy
{
UInt256 ParentTotalDifficulty(BlockHeader header);
}

public sealed class CumulativeTotalDifficultyStrategy : ITotalDifficultyStrategy
{
public UInt256 ParentTotalDifficulty(BlockHeader header)
{
return (header.TotalDifficulty ?? 0) - header.Difficulty;
}
}

public sealed class FixedTotalDifficultyStrategy(
ITotalDifficultyStrategy strategy,
long fixesBlockNumber,
UInt256 toTotalDifficulty
) : ITotalDifficultyStrategy
{
public UInt256 ParentTotalDifficulty(BlockHeader header)
{
return header.Number > 0 && header.Number - 1 == fixesBlockNumber
? toTotalDifficulty
: strategy.ParentTotalDifficulty(header);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ private void WriteDbSyncReport()

private void WriteNotStartedReport()
{
_logger.Info($"Waiting for peers... {(DateTime.UtcNow - StartTime).Seconds}s");
_logger.Info($"Waiting for peers... {Math.Round((DateTime.UtcNow - StartTime).TotalSeconds)}s");
}

private void WriteFullSyncReport()
Expand Down
3 changes: 3 additions & 0 deletions src/Nethermind/Nethermind.Synchronization/Synchronizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ protected override void Load(ContainerBuilder builder)
.AddScoped<IPeerAllocationStrategyFactory<HeadersSyncBatch>, FastBlocksPeerAllocationStrategyFactory>()
.AddScoped<SyncDispatcher<HeadersSyncBatch>>()

// Default TotalDifficulty calculation strategy used when processing headers
.AddScoped<ITotalDifficultyStrategy, CumulativeTotalDifficultyStrategy>()

// SyncProgress resolver need one header sync batch feed, which is the fast header one.
.Register(ctx => ctx
.ResolveNamed<SyncFeedComponent<HeadersSyncBatch>>(nameof(HeadersSyncFeed))
Expand Down