diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaPlugin.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaPlugin.cs index dda8b0fcfca..fcfa90dc37c 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 77a994ef9da..be35cac5805 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 { get; set; } - 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 9c5fb152c11..b921a5ea67d 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuraMainProcessingModule.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/AuraMainProcessingModule.cs @@ -26,9 +26,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, IReceiptStorage receiptStorage) + private IAuRaValidator CreateAuRaValidator(AuRaNethermindApi api, IWorldState worldState, ITransactionProcessor transactionProcessor, IReceiptStorage receiptStorage, IAuRaBlockFinalizationManager finalizationManager) { IAuRaValidator validator = new AuRaValidatorFactory( @@ -39,7 +39,7 @@ private IAuRaValidator CreateAuRaValidator(AuRaNethermindApi api, IWorldState wo envFactory.Create(), 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 5adf3966e0b..cbb844bafe9 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Autofac; 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; @@ -14,13 +14,12 @@ using Nethermind.Core; using Nethermind.Init.Steps; using Nethermind.Logging; -using Nethermind.State.Repositories; using Nethermind.TxPool; using Nethermind.TxPool.Comparison; namespace Nethermind.Consensus.AuRa.InitializationSteps; -public class InitializeBlockchainAuRa(AuRaNethermindApi api, IChainHeadInfoProvider chainHeadInfoProvider, ITxGossipPolicy txGossipPolicy, IChainLevelInfoRepository chainLevelInfoRepository) +public class InitializeBlockchainAuRa(AuRaNethermindApi api, IChainHeadInfoProvider chainHeadInfoProvider, ITxGossipPolicy txGossipPolicy) : InitializeBlockchain(api, chainHeadInfoProvider, txGossipPolicy) { protected AuRaNethermindApi Api => api; @@ -28,14 +27,6 @@ public class InitializeBlockchainAuRa(AuRaNethermindApi api, IChainHeadInfoProvi protected override async Task InitBlockchain() { - AuRaChainSpecEngineParameters chainSpecAuRa = api.ChainSpec.EngineChainSpecParametersProvider.GetChainSpecParameters(); - api.AuRaFinalizationManager = new AuRaBlockFinalizationManager( - api.BlockTree!, - chainLevelInfoRepository, - api.ValidatorStore!, - api.LogManager, - chainSpecAuRa.TwoThirdsMajorityTransition); - await base.InitBlockchain(); WireFinalizationBranchProcessor(); @@ -50,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.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs index 1d736e722a0..c3dc28655aa 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs @@ -190,13 +190,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.Test/AuRaTerminalBlockDisposerTests.cs b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaTerminalBlockDisposerTests.cs index cb49993ab01..909dd43529e 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 deb7ea4bd47..11446bf480e 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; @@ -37,7 +36,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; @@ -51,7 +49,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) => @@ -60,15 +57,6 @@ 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!); - _api.DisposeStack.Push(disposer); - } - public override IModule Module => new AuRaMergeModule(); } @@ -107,6 +95,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 91a0304acb2..34076f7ea15 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa/InitializeBlockchainAuRaMerge.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa/InitializeBlockchainAuRaMerge.cs @@ -1,10 +1,10 @@ // 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; -using Nethermind.State.Repositories; using Nethermind.TxPool; namespace Nethermind.Merge.AuRa; @@ -14,9 +14,22 @@ namespace Nethermind.Merge.AuRa; /// startup walk in . Pre-merge heads (archive sync /// from genesis) still wire so validator-set transitions fire. /// -public class InitializeBlockchainAuRaMerge(AuRaNethermindApi api, IChainHeadInfoProvider chainHeadInfoProvider, ITxGossipPolicy txGossipPolicy, IChainLevelInfoRepository chainLevelInfoRepository) - : InitializeBlockchainAuRa(api, chainHeadInfoProvider, txGossipPolicy, chainLevelInfoRepository) +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 3db9005f4ea..a156384d845 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -189,19 +189,11 @@ public Task InitNetworkProtocol() if (MergeEnabled) { ArgumentNullException.ThrowIfNull(_api.SpecProvider); - - InitializeMergeFinalization(); } return Task.CompletedTask; } - /// - /// 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(); diff --git a/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs b/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs index 4119ec703a7..1edb83e5179 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; @@ -212,11 +210,6 @@ public async Task Smoke_CanResolveAllSteps((string file, ConfigProvider configPr api.NodeKey = new InsecureProtectedPrivateKey(TestItem.PrivateKeyA); api.BlockProducerRunner = Substitute.For(); - if (api is AuRaNethermindApi auRaNethermindApi) - { - auRaNethermindApi.AuRaFinalizationManager = Substitute.For(); - } - try { IEthereumStepsLoader stepsLoader = runner.LifetimeScope.Resolve();