refactor: bundle EIP-7928 BAL per-tx processor construction into BalTxProcessorFactory - #12760
refactor: bundle EIP-7928 BAL per-tx processor construction into BalTxProcessorFactory#12760hudem1 wants to merge 1 commit into
Conversation
…rFactory (step 2) Addresses LukaszRozmej's step-2 review. BlockAccessListManager's blockHashProvider, specProvider, codeInfoRepositoryFactory, transactionProcessorFactory and the tx-adapter factory were only ever used together to build one processor. Bundle them into a new BalTxProcessorFactory.Create(worldState, parallel) => (processor, adapter); the manager drops five ctor params for one, and the pool managers / leaf shrink to (stateProvider, logManager, factory). BalTxProcessorFactory's adapter/processor factories default to Execute / EthereumGasPolicy for manual construction (stateless envs, tests); container resolution fills them from the scope, preserving the single-axis wiring from the previous commit. Witness envs now just resolve the registered factory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @hudem1's task in 10m 30s —— View job Review:
|
| File | Now-unused |
|---|---|
Nethermind.Consensus/Processing/BlockAccessListManager.TxProcessorPool.cs:15 |
Nethermind.Core.Specs |
Nethermind.Consensus/Stateless/WitnessCapturingBlockProcessingEnv.cs:13,16 |
Nethermind.Core.Specs, Nethermind.Evm.TransactionProcessing |
Nethermind.Consensus/Stateless/WitnessGeneratingBlockProcessingEnvFactory.cs:16,20 |
Nethermind.Core.Specs, Nethermind.Evm.TransactionProcessing |
Nethermind.Blockchain.Test/BlockAccessListSequentialValidationTests.cs:16 |
Nethermind.Evm.TransactionProcessing |
Nethermind.Blockchain.Test/BlockAccessLists/BlockAccessListManagerTests.cs:16 |
Nethermind.Evm.TransactionProcessing |
Nethermind.Blockchain.Test/Eip8037BlockGasIntegrationTests.cs:15 |
Nethermind.Evm.TransactionProcessing |
Nethermind.Blockchain.Test/BlockhashProviderTests.cs:24 |
Nethermind.Evm.TransactionProcessing |
Verified by enumerating every public type in Nethermind.Core.Specs / Nethermind.Evm.TransactionProcessing and grepping each file for all of them (plus their extension-method receivers) — zero hits remain. In the four test files the last consumer was the ExecuteTransactionProcessorAdapter lambda this PR deleted; in the two witness envs it was ISpecProvider / TransactionProcessorAdapterFactory / ITransactionProcessorFactory; in the pool file it was ISpecProvider. Nethermind.Evm is still needed everywhere (BlockExecutionContext, NoopCodeCache, IBlockhashProvider), and BlockAccessListManager.cs still needs Nethermind.Core.Specs (IReleaseSpec at line 124) — don't strip those.
I could not run dotnet build in this job (command not permitted), so this is a static verification rather than a compiler confirmation — but it is the mechanism that failed CI for exactly this pattern before.
Low
2. adapterFactory demoted from required to silently-defaulted — BalTxProcessorFactory.cs:28 (inline). Behaviour is preserved today, but a missing registration now yields a quiet Execute fallback instead of a resolution error, on the exact axis step 1 unified. Suggest making it required and passing it at the four manual sites (which also drops the CreateExecuteAdapter duplicate of BlockProcessingModule.CreateExecuteAdapter). processorFactory was already optional on the base branch, so leaving that one alone is fine.
3. Test duplication — the same 4-arg new BalTxProcessorFactory(Substitute.For<IBlockhashProvider>(), <spec>, LimboLogs.Instance, static worldState => new EthereumCodeInfoRepository(worldState)) now appears 8× across 7 files, differing only in the spec provider (inline). A one-line helper keeps the next ctor change to a single edit.
4. Create has no XML doc; class could be sealed — BalTxProcessorFactory.cs:34 (inline). The parallel flag in particular is not self-documenting at the call site.
Also worth a thought (not a finding): BlockAccessListManager's public ctor signature changes shape, which is source-breaking for any out-of-tree plugin constructing it directly. Fine for an internal component mid-refactor-series, but the PR is labelled refactoring-only, so flagging it for visibility.
What I verified as behaviour-preserving
- Construction order/inputs identical.
VirtualMachineis still built once per processor;codeInfoRepositoryFactorystill receives theTracedAccessWorldState(Create(WorldState, parallel)→codeInfoRepositoryFactory(worldState)), andBlobBaseFeeCalculator.Instance/specProvider/parallelare threaded unchanged. - Autofac optional-parameter binding.
AddScoped<T>()usesRegisterType<T>(), soConstructorParameterBindingprefers a registered service and only falls back to the default when nothing can supply the value. Both optional params are therefore injected in every real scope. - All adapter-override scopes still reached.
BalTxProcessorFactoryisInstancePerLifetimeScope, so it is re-created per child scope and picks up that scope'sTransactionProcessorAdapterFactory: BuildUp (GlobalWorldStateBlockProducerEnvFactory.cs:31), Trace (ProofModuleFactory,TraceModuleFactory), and simulate'sSimulateRequestState-derived one (SimulateReadOnlyBlocksProcessingEnvFactory.cs:70) — relevant given this stack's base branch. - AuRa and simulate processor factories still honoured.
AuRaPluginregistersITransactionProcessorFactory → AuRaTransactionProcessorFactory, and simulate decorates it withSkipSenderCodeCheckTransactionProcessorFactory; both resolve through the new factory instead of the manager, in the same scope as before. - Witness scopes equivalent.
ctx.Resolve<BalTxProcessorFactory>()resolves against the child scope'sIComponentContext, so the overriddenIWorldState/IBlockhashCache/NoopCodeCachestill shape the result exactly as the previous explicitctx.Resolve<...>()list did. - No missed construction site. Every
new BlockAccessListManager(...)(2 witness scopes,StatelessBlockProcessingEnv, 8 test sites) and the single DI registration are updated;.AddScoped<BalTxProcessorFactory>()sits inBlockProcessingModule, the only module registeringIBlockAccessListManager. - Lifetimes/threading. Scoped factory over a singleton
ITransactionProcessorFactory— no captive dependency;Createonly reads readonly fields and allocates, so concurrentNewProcessor()calls from the pool are as safe as before. No new allocations on any hot path (pool warm-up only).
Verdict: the refactor is sound; please clear the unused usings before merge (CI-blocking) and either address or explicitly wave off finding 2.
· branch refactor/bal-tx-processor-factory
| ISpecProvider specProvider, | ||
| ILogManager logManager, | ||
| CodeInfoRepositoryFactory codeInfoRepositoryFactory, | ||
| TransactionProcessorAdapterFactory? adapterFactory = null, |
There was a problem hiding this comment.
Low–Medium — adapterFactory goes from required to silently-defaulted.
On the base branch TransactionProcessorAdapterFactory was a required BlockAccessListManager ctor param, so a scope missing that registration failed loudly at resolution. Making it = null with a ?? CreateExecuteAdapter fallback converts that into a silent behavioural default.
That matters because the adapter is exactly the axis step 1 (#12721) unified: block production registers CreateBuildUpAdapter (GlobalWorldStateBlockProducerEnvFactory.cs:31), trace/proof register CreateTraceAdapter, and simulate registers a SimulateRequestState-derived one. Today all of those scopes sit on top of BlockProcessingModule, so Autofac's optional-parameter binding does inject them and behaviour is preserved — I verified that. But if a future scope ever builds a BAL manager without that registration, the pool's per-worker adapters would quietly fall back to Execute while the scoped ITransactionProcessorAdapter stays on BuildUp/Trace — a silent two-axis divergence in a consensus-critical component instead of an exception.
Suggestion: keep processorFactory optional (it already was), but make adapterFactory required and pass it explicitly at the four manual sites (StatelessBlockProcessingEnv + the test helper). That also removes the CreateExecuteAdapter duplicate of BlockProcessingModule.CreateExecuteAdapter.
| private readonly TransactionProcessorAdapterFactory _adapterFactory = adapterFactory ?? CreateExecuteAdapter; | ||
| private readonly ITransactionProcessorFactory _processorFactory = processorFactory ?? new TransactionProcessorFactory<EthereumGasPolicy>(); | ||
|
|
||
| public (ITransactionProcessor Processor, ITransactionProcessorAdapter Adapter) Create(IWorldState worldState, bool parallel) |
There was a problem hiding this comment.
Low — Create is the type's only public member and has no XML doc; per AGENTS.md public members want <summary> (+ <param> for the non-obvious parallel flag, which selects the BAL-backed world state / parallel processor path). Also worth sealeding the class — nothing derives from it, and the repo prefers composition over inheritance.
| new BalTxProcessorFactory(Substitute.For<IBlockhashProvider>(), new TestSingleReleaseSpecProvider(Amsterdam.Instance), LimboLogs.Instance, | ||
| static worldState => new EthereumCodeInfoRepository(worldState)), |
There was a problem hiding this comment.
Low — test duplication. This exact 4-argument BalTxProcessorFactory construction is now repeated 8× across BlockProcessorTests (×6), Eip8037BlockGasIntegrationTests (×2), BlockAccessListSequentialValidationTests, BlockhashProviderTests, BlockAccessListManagerTests, ReorgTests, AuraBlockProcessorTests — differing only in the spec provider. AGENTS.md / test-infrastructure.md ask for shared setup to be factored out; a one-liner helper (e.g. static BalTxProcessorFactory TestBalFactory(ISpecProvider specProvider) => new(Substitute.For<IBlockhashProvider>(), specProvider, LimboLogs.Instance, static worldState => new EthereumCodeInfoRepository(worldState));) would shrink every call site and keep the eight in sync when the ctor changes again.
Follow-up to #12721 (LukaszRozmej's step 2). Stacked on #12721 — rebase onto
masteronce that merges.Changes
BlockAccessListManager'sblockHashProvider,specProvider,codeInfoRepositoryFactory,transactionProcessorFactory, and the tx-adapter factory were only ever used together — inTxProcessorWithWorldState's ctor — to build one processor next to aVirtualMachine. This bundles that into a newBalTxProcessorFactory:BlockAccessListManagerdrops five ctor params for one (BalTxProcessorFactory); the pool managers and the pooled leaf shrink to(stateProvider, logManager, factory).BalTxProcessorFactory's adapter/processor factories default toExecuteTransactionProcessorAdapter/TransactionProcessorFactory<EthereumGasPolicy>for the manual construction sites (stateless env, test harnesses); container resolution fills them from the scope, so the single-axis wiring from fix(simulate): route the EIP-7928 BAL path through the simulate tx adapter (#12692) #12721 is preserved and the scoped adapter / BAL-pool adapters still agree.BalTxProcessorFactoryinstead of threading the individual collaborators.Net −15 lines despite the new type — the bundling removes more plumbing than it adds.
Types of changes
Testing
No behaviour change (pure construction re-shaping). Green locally: Blockchain BAL/BlockProcessor/Producer/Eip8037/Reorg/Blockhash 269, JsonRpc simulate/trace/proof/debug 402, Merge.Plugin payload/production 885; full solution builds clean.
Documentation