From 12f10d5190df04c330513ba5234592e65c6bf9e1 Mon Sep 17 00:00:00 2001 From: Amirul Ashraf Date: Wed, 17 Jun 2026 18:44:20 +0800 Subject: [PATCH 1/3] Move IAuRaBlockFinalizationManager to DI Register IAuRaBlockFinalizationManager as a container singleton in AuRaModule instead of constructing it manually in InitializeBlockchainAuRa and serving it to DI consumers via the FallbackToFieldFromApi source. The component has no construction cycle (only the deferred SetMainBlockBranchProcessor wiring is cyclic, and that stays in WireFinalizationBranchProcessor). AuRaNethermindApi.AuRaFinalizationManager becomes a computed getter resolving from the container (a settable property alongside a registration is rejected by FallbackToFieldFromApi). As a normal singleton Autofac now disposes it on shutdown; Dispose is idempotent so the early dispose from AuRaTerminalBlockDisposer remains safe. Tests that injected a substitute via the property now register it through DI. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Nethermind.Consensus.AuRa/AuRaPlugin.cs | 4 ++++ .../InitializationSteps/AuRaNethermindApi.cs | 2 +- .../InitializationSteps/InitializeBlockchainAuRa.cs | 11 +---------- .../AuRaMergeEngineModuleTests.cs | 3 ++- .../Nethermind.Merge.AuRa/AuRaMergePlugin.cs | 6 +----- .../Nethermind.Runner.Test/EthereumRunnerTests.cs | 5 ----- 6 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaPlugin.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaPlugin.cs index dda8b0fcfca3..fcfa90dc37cd 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaPlugin.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaPlugin.cs @@ -28,6 +28,7 @@ using Nethermind.Logging; using Nethermind.Serialization.Rlp; using Nethermind.Specs.ChainSpecStyle; +using Nethermind.State.Repositories; using Nethermind.Synchronization; [assembly: InternalsVisibleTo("Nethermind.Merge.AuRa")] @@ -90,6 +91,9 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddScoped() // Note: for main block processor this is not the case .AddScoped() + .AddSingleton( + (blockTree, chainLevelInfoRepository, validatorStore, logManager, param) => + new AuRaBlockFinalizationManager(blockTree, chainLevelInfoRepository, validatorStore, logManager, param.TwoThirdsMajorityTransition)) .AddSingleton() .AddSingleton() diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaNethermindApi.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaNethermindApi.cs index 77a994ef9dab..1d854e891094 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaNethermindApi.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaNethermindApi.cs @@ -18,7 +18,7 @@ namespace Nethermind.Consensus.AuRa.InitializationSteps { public class AuRaNethermindApi(NethermindApi.Dependencies dependencies) : NethermindApi(dependencies) { - public IAuRaBlockFinalizationManager? AuRaFinalizationManager { get; set; } + public IAuRaBlockFinalizationManager AuRaFinalizationManager => Context.Resolve(); public TxAuRaFilterBuilders TxAuRaFilterBuilders => Context.Resolve(); public IValidatorStore ValidatorStore => Context.Resolve(); diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs index b14e93a7acfe..9dc96be230ae 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Nethermind.Api; using Nethermind.Blockchain.Data; -using Nethermind.Consensus.AuRa.Config; using Nethermind.Consensus.AuRa.Contracts; using Nethermind.Consensus.AuRa.Contracts.DataStore; using Nethermind.Consensus.AuRa.Transactions; @@ -27,14 +26,6 @@ public class InitializeBlockchainAuRa(AuRaNethermindApi api, IChainHeadInfoProvi protected override async Task InitBlockchain() { - AuRaChainSpecEngineParameters chainSpecAuRa = api.ChainSpec.EngineChainSpecParametersProvider.GetChainSpecParameters(); - api.AuRaFinalizationManager = new AuRaBlockFinalizationManager( - api.BlockTree!, - api.ChainLevelInfoRepository!, - api.ValidatorStore!, - api.LogManager, - chainSpecAuRa.TwoThirdsMajorityTransition); - await base.InitBlockchain(); WireFinalizationBranchProcessor(); @@ -49,7 +40,7 @@ protected override async Task InitBlockchain() /// Got cyclic dependency. AuRaBlockFinalizationManager -> IAuraValidator -> AuraBlockProcessor -> AuraBlockFinalizationManager. /// protected virtual void WireFinalizationBranchProcessor() => - api.AuRaFinalizationManager!.SetMainBlockBranchProcessor(api.MainProcessingContext!.BranchProcessor!); + api.AuRaFinalizationManager.SetMainBlockBranchProcessor(api.MainProcessingContext!.BranchProcessor!); private IComparer CreateTxPoolTxComparer(TxPriorityContract? txPriorityContract, TxPriorityContract.LocalDataSource? localDataSource) { diff --git a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs index c06e31250447..45897ac7414b 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs @@ -192,13 +192,14 @@ protected override ContainerBuilder ConfigureContainer(ContainerBuilder builder, .AddSingleton((blockProducer, mergeConfig) => new BlockImprovementContextFactory(blockProducer, TimeSpan.FromSeconds(mergeConfig.SecondsPerSlot))) + .AddSingleton(Substitute.For()) + .AddDecorator((_, api) => { // Yes getting from `TestBlockchain` itself, since steps are not run // and some of these are not from DI. you know... chicken and egg, but don't forget about the rooster. api.TxPool = TxPool; api.TransactionComparerProvider = TransactionComparerProvider; - api.AuRaFinalizationManager = Substitute.For(); return api; }); diff --git a/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergePlugin.cs b/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergePlugin.cs index d66e86961909..993f5b04ad3e 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergePlugin.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Threading.Tasks; using Autofac; using Autofac.Core; @@ -60,10 +59,7 @@ originalFilter is MinGasPriceContractTxFilter ? originalFilter protected override void InitializeMergeFinalization() { - IAuRaBlockFinalizationManager auRa = _auraApi!.AuRaFinalizationManager - ?? throw new ArgumentNullException(nameof(_auraApi.AuRaFinalizationManager), - "Cannot construct AuRaTerminalBlockDisposer when AuRaFinalizationManager is null!"); - AuRaTerminalBlockDisposer disposer = new(auRa, _poSSwitcher, _api.BlockTree!); + AuRaTerminalBlockDisposer disposer = new(_auraApi!.AuRaFinalizationManager, _poSSwitcher, _api.BlockTree!); _api.DisposeStack.Push(disposer); } diff --git a/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs b/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs index b3fa2002a6ce..13befa95b653 100644 --- a/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs @@ -215,11 +215,6 @@ public async Task Smoke_CanResolveAllSteps((string file, ConfigProvider configPr api.BackgroundTaskScheduler = Substitute.For(); api.NonceManager = Substitute.For(); - if (api is AuRaNethermindApi auRaNethermindApi) - { - auRaNethermindApi.AuRaFinalizationManager = Substitute.For(); - } - try { IEthereumStepsLoader stepsLoader = runner.LifetimeScope.Resolve(); From 5025932e80280d4176aa2e89f56717d5352a3d53 Mon Sep 17 00:00:00 2001 From: Amirul Ashraf Date: Wed, 17 Jun 2026 20:15:11 +0800 Subject: [PATCH 2/3] Remove InitializeMergeFinalization hook; wire AuRaTerminalBlockDisposer via DI With IAuRaBlockFinalizationManager now a DI singleton, AuRaTerminalBlockDisposer's three constructor dependencies (IAuRaBlockFinalizationManager, IPoSSwitcher, IBlockTree) are all DI-resolvable, so the manual InitializeMergeFinalization lifecycle hook is pure ceremony. Register AuRaTerminalBlockDisposer as a singleton in AuRaMergeModule and resolve it in the InitializeBlockchainAuRaMerge step (after WireFinalizationBranchProcessor) to trigger its constructor side-effect. This step runs after the head is loaded and IPoSSwitcher is initialized, but before the block processor starts, so the once-only TerminalBlockReached signal cannot be missed. Autofac owns the disposer's lifetime and disposal, equivalent to the removed DisposeStack.Push; Dispose is idempotent. Remove the now-unused InitializeMergeFinalization override and _auraApi field from AuRaMergePlugin, and the base virtual hook + call site from MergePlugin (no other overriders exist). Add a DI-resolution regression test. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../AuRaTerminalBlockDisposerTests.cs | 23 +++++++++++++++++++ .../Nethermind.Merge.AuRa/AuRaMergePlugin.cs | 12 ++++------ .../InitializeBlockchainAuRaMerge.cs | 14 +++++++++++ .../Nethermind.Merge.Plugin/MergePlugin.cs | 7 ------ 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaTerminalBlockDisposerTests.cs b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaTerminalBlockDisposerTests.cs index cb49993ab013..909dd43529e1 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaTerminalBlockDisposerTests.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaTerminalBlockDisposerTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Autofac; using Nethermind.Blockchain; using Nethermind.Consensus; using Nethermind.Consensus.AuRa; @@ -69,6 +70,28 @@ public void Terminal_block_handler_unsubscribes_itself() _auRaFinalizationManager.DidNotReceive().Dispose(); } + [Test] + public void Registered_as_singleton_and_resolving_triggers_disposal_when_head_post_merge() + { + // The disposer is wired through DI (AuRaMergeModule + InitializeBlockchainAuRaMerge) rather than + // hand-constructed: registering it as a singleton and resolving it must trigger the same ctor + // side-effect (immediate disposal on a post-merge head) and yield a single instance. + SetHead(postMerge: true); + + ContainerBuilder builder = new(); + builder + .AddSingleton(_auRaFinalizationManager) + .AddSingleton(_poSSwitcher) + .AddSingleton(_blockTree) + .AddSingleton(); + using IContainer container = builder.Build(); + + AuRaTerminalBlockDisposer disposer = container.Resolve(); + + Assert.That(container.Resolve(), Is.SameAs(disposer)); + _auRaFinalizationManager.Received(1).Dispose(); + } + [Test] public void Fresh_archive_with_FinalTotalDifficulty_in_config_does_not_dispose_pre_merge_aura() { diff --git a/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergePlugin.cs b/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergePlugin.cs index 993f5b04ad3e..1211cb0558ff 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergePlugin.cs @@ -34,7 +34,6 @@ namespace Nethermind.Merge.AuRa /// IMPORTANT: this plugin should always come before MergePlugin public class AuRaMergePlugin(ChainSpec chainSpec, IMergeConfig mergeConfig) : MergePlugin(chainSpec, mergeConfig) { - private AuRaNethermindApi? _auraApi; private readonly IMergeConfig _mergeConfig = mergeConfig; private readonly ChainSpec _chainSpec = chainSpec; @@ -48,7 +47,6 @@ public override async Task Init(INethermindApi nethermindApi) if (MergeEnabled) { await base.Init(nethermindApi); - _auraApi = (AuRaNethermindApi)nethermindApi; // this runs before all init steps that use tx filters TxAuRaFilterBuilders.CreateFilter = (originalFilter, fallbackFilter) => @@ -57,12 +55,6 @@ originalFilter is MinGasPriceContractTxFilter ? originalFilter } } - protected override void InitializeMergeFinalization() - { - AuRaTerminalBlockDisposer disposer = new(_auraApi!.AuRaFinalizationManager, _poSSwitcher, _api.BlockTree!); - _api.DisposeStack.Push(disposer); - } - public override IModule Module => new AuRaMergeModule(); } @@ -98,6 +90,10 @@ protected override void Load(ContainerBuilder builder) => builder .AddDecorator() .AddDecorator() + // Disposes the AuRa finalization manager at the merge transition. Resolved eagerly in + // InitializeBlockchainAuRaMerge for its constructor side-effect; Autofac owns disposal. + .AddSingleton() + // Merge-aware override: skips wiring the branch processor on post-merge chains so // the AuRa finalization manager's startup catch-up walk never runs. .AddStep(typeof(InitializeBlockchainAuRaMerge)) diff --git a/src/Nethermind/Nethermind.Merge.AuRa/InitializeBlockchainAuRaMerge.cs b/src/Nethermind/Nethermind.Merge.AuRa/InitializeBlockchainAuRaMerge.cs index ee642e96eb27..34076f7ea156 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa/InitializeBlockchainAuRaMerge.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa/InitializeBlockchainAuRaMerge.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Threading.Tasks; using Autofac; using Nethermind.Consensus; using Nethermind.Consensus.AuRa.InitializationSteps; @@ -16,6 +17,19 @@ namespace Nethermind.Merge.AuRa; public class InitializeBlockchainAuRaMerge(AuRaNethermindApi api, IChainHeadInfoProvider chainHeadInfoProvider, ITxGossipPolicy txGossipPolicy) : InitializeBlockchainAuRa(api, chainHeadInfoProvider, txGossipPolicy) { + protected override async Task InitBlockchain() + { + await base.InitBlockchain(); + + // Construct the terminal-block disposer now (after WireFinalizationBranchProcessor ran in base): + // it either disposes the finalization manager immediately (head already post-merge) or subscribes + // to IPoSSwitcher.TerminalBlockReached. Resolving here triggers that constructor side-effect. + // Safe at this step — the head is loaded, IPoSSwitcher is initialized, and the block processor has + // not started, so the once-only TerminalBlockReached signal cannot be missed. Autofac owns its + // lifetime and disposal. + Api.Context.Resolve(); + } + protected override void WireFinalizationBranchProcessor() { if (!Api.Context.Resolve().IsHeadPostMerge(Api.BlockTree!)) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 5a03dc4774df..f175b4690e88 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -196,7 +196,6 @@ public Task InitNetworkProtocol() _mergeBlockProductionPolicy = new MergeBlockProductionPolicy(_api.BlockProductionPolicy); _api.BlockProductionPolicy = _mergeBlockProductionPolicy; - InitializeMergeFinalization(); if (_poSSwitcher.TransitionFinished) { @@ -222,12 +221,6 @@ private void AddPostMergeNetworkProtocols() _api.ProtocolsManager!.AddSupportedCapability(new(Protocol.Eth, 71)); } - /// - /// Hook for derived plugins (e.g. AuRaMergePlugin) to set up merge-transition lifecycle - /// (e.g. disposing AuRa's finalization manager at terminal block). Default: no-op. - /// - protected virtual void InitializeMergeFinalization() { } - public bool MustInitialize { get => true; } public virtual IModule Module => new MergePluginModule(); From 4f598da1f0dd47385cf912f867bbc20b6ad43b59 Mon Sep 17 00:00:00 2001 From: Amirul Ashraf Date: Wed, 17 Jun 2026 20:58:06 +0800 Subject: [PATCH 3/3] Drop AuRaFinalizationManager api member; fix lint Remove the now-redundant AuRaNethermindApi.AuRaFinalizationManager accessor: its only remaining consumers resolve IAuRaBlockFinalizationManager directly from DI instead (AuraMainProcessingModule via a factory parameter, InitializeBlockchainAuRa via Api.Context.Resolve), completing the move off the api facade. Also remove two unused usings in EthereumRunnerTests left over from the earlier finalization-manager DI move, which the code-lint (IDE0005) check flagged. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../InitializationSteps/AuRaNethermindApi.cs | 2 -- .../InitializationSteps/AuraMainProcessingModule.cs | 6 +++--- .../InitializationSteps/InitializeBlockchainAuRa.cs | 3 ++- .../Nethermind.Runner.Test/EthereumRunnerTests.cs | 2 -- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaNethermindApi.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaNethermindApi.cs index 1d854e891094..be35cac5805d 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaNethermindApi.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuRaNethermindApi.cs @@ -18,8 +18,6 @@ namespace Nethermind.Consensus.AuRa.InitializationSteps { public class AuRaNethermindApi(NethermindApi.Dependencies dependencies) : NethermindApi(dependencies) { - public IAuRaBlockFinalizationManager AuRaFinalizationManager => Context.Resolve(); - public TxAuRaFilterBuilders TxAuRaFilterBuilders => Context.Resolve(); public IValidatorStore ValidatorStore => Context.Resolve(); public AuraStatefulComponents AuraStatefulComponents => Context.Resolve(); diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuraMainProcessingModule.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuraMainProcessingModule.cs index 78546b7c1a35..96e623a03641 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuraMainProcessingModule.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuraMainProcessingModule.cs @@ -25,9 +25,9 @@ public class AuraMainProcessingModule( AuRaChainSpecEngineParameters chainSpecAuRa ) : Module, IMainProcessingModule { - protected override void Load(ContainerBuilder builder) => builder.AddSingleton(CreateAuRaValidator); + protected override void Load(ContainerBuilder builder) => builder.AddSingleton(CreateAuRaValidator); - private IAuRaValidator CreateAuRaValidator(AuRaNethermindApi api, IWorldState worldState, ITransactionProcessor transactionProcessor) + private IAuRaValidator CreateAuRaValidator(AuRaNethermindApi api, IWorldState worldState, ITransactionProcessor transactionProcessor, IAuRaBlockFinalizationManager finalizationManager) { IAuRaValidator validator = new AuRaValidatorFactory( @@ -38,7 +38,7 @@ private IAuRaValidator CreateAuRaValidator(AuRaNethermindApi api, IWorldState wo envFactory.Create(), api.ReceiptStorage, api.ValidatorStore, - api.AuRaFinalizationManager, + finalizationManager, new TxPoolSender(api.TxPool, new TxSealer(api.EngineSigner, api.Timestamper), api.NonceManager, api.EthereumEcdsa), api.TxPool, api.Config(), diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs index 9dc96be230ae..cbb844bafe94 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Autofac; using Nethermind.Api; using Nethermind.Blockchain.Data; using Nethermind.Consensus.AuRa.Contracts; @@ -40,7 +41,7 @@ protected override async Task InitBlockchain() /// Got cyclic dependency. AuRaBlockFinalizationManager -> IAuraValidator -> AuraBlockProcessor -> AuraBlockFinalizationManager. /// protected virtual void WireFinalizationBranchProcessor() => - api.AuRaFinalizationManager.SetMainBlockBranchProcessor(api.MainProcessingContext!.BranchProcessor!); + api.Context.Resolve().SetMainBlockBranchProcessor(api.MainProcessingContext!.BranchProcessor!); private IComparer CreateTxPoolTxComparer(TxPriorityContract? txPriorityContract, TxPriorityContract.LocalDataSource? localDataSource) { diff --git a/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs b/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs index 13befa95b653..ea10240572b8 100644 --- a/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs @@ -21,8 +21,6 @@ using Nethermind.Blockchain.Synchronization; using Nethermind.Config; using Nethermind.Consensus; -using Nethermind.Consensus.AuRa; -using Nethermind.Consensus.AuRa.InitializationSteps; using Nethermind.Consensus.AuRa.Validators; using Nethermind.Consensus.Clique; using Nethermind.Consensus.Processing;