Skip to content
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 @@ -40,6 +40,20 @@ public void TestAddFirst()
Assert.That(ctx.Resolve<Item[]>().Select(item => item.Name), Is.EqualTo(["1", "2", "3"]));
}

[Test]
public void TestRemove()
{
// RemoveOrderedComponents drops every component of the given concrete type, keeping the rest in order
using IContainer ctx = new ContainerBuilder()
.AddLast<IItem>(_ => new Item("1"))
.AddLast<IItem>(_ => new OtherItem("2"))
.AddLast<IItem>(_ => new Item("3"))
.RemoveOrderedComponents<IItem, Item>()
.Build();

Assert.That(ctx.Resolve<IItem[]>().Select(item => item.Name), Is.EqualTo(["2"]));
}

[Test]
public void TestDisallowIndividualRegistration()
{
Expand Down Expand Up @@ -112,6 +126,7 @@ protected override void Load(ContainerBuilder builder) =>
}
private interface IItem { string Name { get; } }
private record Item(string Name) : IItem;
private record OtherItem(string Name) : IItem;
private class CompositeItem(IItem[] items) : IItem
{
public IItem[] Items { get; } = items;
Expand Down
23 changes: 0 additions & 23 deletions src/Nethermind/Nethermind.Core.Test/Modules/PseudoNetworkModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@
// SPDX-License-Identifier: LGPL-3.0-only

using Autofac;
using Nethermind.Blockchain.Synchronization;
using Nethermind.Consensus;
using Nethermind.Logging;
using Nethermind.Network;
using Nethermind.Network.Config;
using Nethermind.Network.Contract.P2P;
using Nethermind.Stats.Model;

namespace Nethermind.Core.Test.Modules;

Expand All @@ -21,25 +17,6 @@ protected override void Load(ContainerBuilder builder)
builder
.AddSingleton<IGossipPolicy>(Policy.FullGossip)

// TODO: LastNStateRootTracker

.AddAdvance<ProtocolsManager>(cfg =>
{
cfg
.As<IProtocolsManager>()
.SingleInstance()
.OnActivating((m) =>
{
ProtocolsManager protocolManager = m.Instance;
ISyncConfig syncConfig = m.Context.Resolve<ISyncConfig>();

if (syncConfig.SnapServingEnabled == true || syncConfig.SnapSync)
{
protocolManager.AddSupportedCapability(new Capability(Protocol.Snap, 1));
}
});
})

// Some config migration
.AddDecorator<INetworkConfig>((ctx, networkConfig) =>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Collections.Generic;

namespace Nethermind.Core.Container;

public class OrderedComponents<T>
{
private IList<T> _components = [];
private readonly List<T> _components = [];
public IEnumerable<T> Components => _components;

public void AddLast(T item) => _components.Add(item);

public void AddFirst(T item) => _components.Insert(0, item);

public void RemoveAll(Predicate<T> match) => _components.RemoveAll(match);

public void Clear() => _components.Clear();
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,23 @@ public static ContainerBuilder AddCompositeOrderedComponents<T, TComposite>(this
return builder;
}

/// <summary>
/// Remove all previously registered ordered components of type <typeparamref name="TImpl"/> for <typeparamref name="T"/>.
/// Useful when a plugin replaces a default component instead of layering on top of it
/// (e.g., XDC dropping the default eth/68 capability resolver).
/// </summary>
/// <remarks>
/// Like <see cref="ClearOrderedComponents{T}"/>, this relies on the removing decorator being registered
/// after the component it targets, which holds when a plugin module loads after the core module that
/// registered the default.
/// </remarks>
public static ContainerBuilder RemoveOrderedComponents<T, TImpl>(this ContainerBuilder builder) where TImpl : T =>
builder.AddDecorator<OrderedComponents<T>>((_, orderedComponents) =>
{
orderedComponents.RemoveAll(static item => item is TImpl);
return orderedComponents;
});

/// <summary>
/// Clear all previously registered ordered components for <typeparamref name="T"/>.
/// Useful when a plugin needs to disable all ordered policies (e.g., Hive).
Expand Down
3 changes: 2 additions & 1 deletion src/Nethermind/Nethermind.Init/Modules/NetworkModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ protected override void Load(ContainerBuilder builder)
.AddSingleton<IMessagePad, Handshake.Eip8MessagePad>()
.AddSingleton<IProtocolValidator, ProtocolValidator>()
.AddSingleton<IProtocolsManager, ProtocolsManager>()
.AddSingleton<SnapCapabilitySwitcher>()
.AddFirst<IP2PCapabilityResolver, DefaultP2PCapabilityResolver>()
.AddLast<IP2PCapabilityResolver, SnapP2PCapabilityResolver>()

// Handshake
.AddMessageSerializer<Handshake.AuthEip8Message, Handshake.AuthEip8MessageSerializer>()
Expand Down
20 changes: 3 additions & 17 deletions src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
using Nethermind.Logging;
using Nethermind.Network;
using Nethermind.Network.Config;
using Nethermind.Network.Contract.P2P;
using Nethermind.Network.Discovery.Discv4;
using Nethermind.Network.Rlpx;
using Nethermind.Stats.Model;
using Nethermind.Synchronization;
using Nethermind.Synchronization.Peers;

Expand Down Expand Up @@ -58,7 +56,6 @@ public class InitializeNetwork : IStep
private readonly IEnode _enode;
private readonly INethermindPlugin[] _plugins;
private readonly Lazy<IProtocolsManager> _protocolsManager;
private readonly Lazy<SnapCapabilitySwitcher> _snapCapabilitySwitcher;

private readonly NodeSourceToDiscV4Feeder _enrDiscoveryAppFeeder;
private readonly ISyncConfig _syncConfig;
Expand All @@ -83,7 +80,6 @@ public InitializeNetwork(
IEnode enode,
INethermindPlugin[] plugins,
Lazy<IProtocolsManager> protocolsManager,
Lazy<SnapCapabilitySwitcher> snapCapabilitySwitcher,
INetworkConfig networkConfig,
ISyncConfig syncConfig,
IInitConfig initConfig,
Expand All @@ -104,7 +100,6 @@ ILogManager logManager
_enode = enode;
_plugins = plugins;
_protocolsManager = protocolsManager;
_snapCapabilitySwitcher = snapCapabilitySwitcher;
_networkConfig = networkConfig;
_syncConfig = syncConfig;
_initConfig = initConfig;
Expand Down Expand Up @@ -151,12 +146,6 @@ await InitPeer().ContinueWith(initPeerTask =>
}
});

if (_syncConfig.SnapSync && _syncConfig.SnapServingEnabled != true)
{
_snapCapabilitySwitcher.Value.EnableSnapCapabilityUntilSynced();
}
else if (_logger.IsDebug) _logger.Debug("Skipped enabling snap capability");

if (cancellationToken.IsCancellationRequested)
{
return;
Expand Down Expand Up @@ -258,12 +247,9 @@ private Task StartSync()

protected virtual async Task InitPeer()
{
IProtocolsManager protocolsManager = _protocolsManager.Value;
// Force creation so the protocols manager subscribes to session events before the RLPx listener starts.
_ = _protocolsManager.Value;

if (_syncConfig.SnapServingEnabled == true)
{
protocolsManager.AddSupportedCapability(new Capability(Protocol.Snap, 1));
}
if (!_networkConfig.DisableDiscV4DnsFeeder)
{
// Feed some nodes into discoveryApp in case all bootnodes is faulty.
Expand All @@ -275,7 +261,7 @@ protected virtual async Task InitPeer()
await plugin.InitNetworkProtocol();
}

// Capabilities must be finalized before the RLPx listener accepts peers. Otherwise
// Capabilities must be resolved before the RLPx listener accepts peers. Otherwise
// early sessions can negotiate only the default ETH version and never upgrade.
await _rlpxPeer.Init();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ await base.Build(builder =>
Substitute.For<IProtocolValidator>(),
Substitute.For<INetworkStorage>(),
Array.Empty<IProtocolHandlerFactory>(),
[new DefaultP2PCapabilityResolver()],
LimboLogs.Instance
);

Expand Down
4 changes: 4 additions & 0 deletions src/Nethermind/Nethermind.Merge.AuRa/AuRaMergePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Nethermind.Consensus.Withdrawals;
using Nethermind.Config;
using Nethermind.Core;
using Nethermind.Core.Container;
using Nethermind.Core.Specs;
using Nethermind.Evm.TransactionProcessing;
using Nethermind.Logging;
Expand All @@ -25,6 +26,7 @@
using Nethermind.Merge.Plugin;
using Nethermind.Merge.Plugin.BlockProduction;
using Nethermind.Merge.Plugin.Handlers;
using Nethermind.Network;
using Nethermind.Specs.ChainSpecStyle;

namespace Nethermind.Merge.AuRa
Expand Down Expand Up @@ -80,6 +82,8 @@ public class AuRaMergeModule : Module
protected override void Load(ContainerBuilder builder) => builder
.AddModule(new BaseMergePluginModule())

.AddLast<IP2PCapabilityResolver, MergeP2PCapabilityResolver>()

// Aura (non merge) use `BlockProducerStarter` directly.
.AddSingleton<IBlockProducerTxSourceFactory, AuRaMergeBlockProducerTxSourceFactory>()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Collections.Generic;
using Nethermind.Consensus;
using Nethermind.Network.Contract.P2P;
using Nethermind.Stats.Model;
using NSubstitute;
using NUnit.Framework;

namespace Nethermind.Merge.Plugin.Test;

[Parallelizable(ParallelScope.All)]
public class MergeP2PCapabilityResolverTests
{
[TestCase(false, false, false, TestName = "Pre-merge: not advertised")]
[TestCase(true, false, true, TestName = "Transition finished (post-merge restart): advertised")]
[TestCase(false, true, true, TestName = "Live merge: terminal block reached before transition finished: advertised")]
[TestCase(true, true, true, TestName = "Both: advertised")]
public void Resolve_advertises_post_merge_eth_capabilities_once_post_merge(bool transitionFinished, bool hasReachedTerminalBlock, bool expected)
{
IPoSSwitcher poSSwitcher = Substitute.For<IPoSSwitcher>();
poSSwitcher.TransitionFinished.Returns(transitionFinished);
poSSwitcher.HasEverReachedTerminalBlock().Returns(hasReachedTerminalBlock);
using MergeP2PCapabilityResolver resolver = new(poSSwitcher);

HashSet<Capability> capabilities = [];
resolver.Resolve(capabilities);

Assert.That(capabilities.Contains(new Capability(Protocol.Eth, 69)), Is.EqualTo(expected));
Assert.That(capabilities.Contains(new Capability(Protocol.Eth, 70)), Is.EqualTo(expected));
Assert.That(capabilities.Contains(new Capability(Protocol.Eth, 71)), Is.EqualTo(expected));
}

[Test]
public void Raises_Changed_when_terminal_block_reached()
{
IPoSSwitcher poSSwitcher = Substitute.For<IPoSSwitcher>();
using MergeP2PCapabilityResolver resolver = new(poSSwitcher);

bool changed = false;
resolver.Changed += () => changed = true;

poSSwitcher.TerminalBlockReached += Raise.Event();

Assert.That(changed, Is.True);
}
}
53 changes: 0 additions & 53 deletions src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@
using Nethermind.Logging;
using Nethermind.Merge.Plugin.BlockProduction;
using Nethermind.Network;
using Nethermind.Network.Contract.P2P;
using Nethermind.Runner.Ethereum.Modules;
using Nethermind.Runner.Test.Ethereum;
using Nethermind.Serialization.Json;
using Nethermind.Specs.ChainSpecStyle;
using Nethermind.Specs.Test.ChainSpecStyle;
using Nethermind.Stats.Model;
using NUnit.Framework;
using NSubstitute;

Expand Down Expand Up @@ -198,48 +196,6 @@ public async Task Initializes_correctly()
Assert.That(blockProducer, Is.InstanceOf<MergeBlockProducer>());
}

[Test]
public async Task InitNetworkProtocol_adds_post_merge_eth_capabilities_when_transition_finished()
{
IPoSSwitcher poSSwitcher = Substitute.For<IPoSSwitcher>();
poSSwitcher.TransitionFinished.Returns(true);

await using IContainer container = BuildContainer(configure: builder => builder
.RegisterInstance(poSSwitcher)
.As<IPoSSwitcher>());
INethermindApi api = container.Resolve<INethermindApi>();
await _consensusPlugin!.Init(api);
await _plugin.Init(api);

api.ProtocolsManager!.ClearReceivedCalls();
await _plugin.InitNetworkProtocol();

AssertPostMergeEthCapabilitiesAdded(api);
}

[Test]
public async Task InitNetworkProtocol_delays_post_merge_eth_capabilities_until_terminal_block()
{
IPoSSwitcher poSSwitcher = Substitute.For<IPoSSwitcher>();
poSSwitcher.TransitionFinished.Returns(false);

await using IContainer container = BuildContainer(configure: builder => builder
.RegisterInstance(poSSwitcher)
.As<IPoSSwitcher>());
INethermindApi api = container.Resolve<INethermindApi>();
await _consensusPlugin!.Init(api);
await _plugin.Init(api);

api.ProtocolsManager!.ClearReceivedCalls();
await _plugin.InitNetworkProtocol();

api.ProtocolsManager!.DidNotReceive().AddSupportedCapability(Arg.Any<Capability>());

poSSwitcher.TerminalBlockReached += Raise.Event();

AssertPostMergeEthCapabilitiesAdded(api);
}

[Test]
public async Task Init_registers_gas_limit_calculator_for_testing_rpc_module()
{
Expand Down Expand Up @@ -301,13 +257,4 @@ public async Task InitDisableJsonRpcUrlWithNoEngineUrl()
Assert.That(jsonRpcConfig.EnabledModules, Is.Empty);
Assert.That(jsonRpcConfig.AdditionalRpcUrls, Is.EqualTo(new[] { "http://localhost:8551|http;ws|net;eth;subscribe;web3;engine;client" }));
}

private static void AssertPostMergeEthCapabilitiesAdded(INethermindApi api)
{
IProtocolsManager protocolsManager = api.ProtocolsManager!;

protocolsManager.Received(1).AddSupportedCapability(new Capability(Protocol.Eth, 69));
protocolsManager.Received(1).AddSupportedCapability(new Capability(Protocol.Eth, 70));
protocolsManager.Received(1).AddSupportedCapability(new Capability(Protocol.Eth, 71));
}
}
Loading
Loading