diff --git a/src/Nethermind b/src/Nethermind index 9ccf900f3..875b5c58e 160000 --- a/src/Nethermind +++ b/src/Nethermind @@ -1 +1 @@ -Subproject commit 9ccf900f3715b79f2c883ce6ae04eea96d10196f +Subproject commit 875b5c58e23a85f4b2143a22518cd632b7ba1abe diff --git a/src/Nethermind.Arbitrum.Test/Arbos/Programs/StylusProgramsTests.cs b/src/Nethermind.Arbitrum.Test/Arbos/Programs/StylusProgramsTests.cs index 26dda6be5..9cded542b 100644 --- a/src/Nethermind.Arbitrum.Test/Arbos/Programs/StylusProgramsTests.cs +++ b/src/Nethermind.Arbitrum.Test/Arbos/Programs/StylusProgramsTests.cs @@ -194,7 +194,7 @@ public void CallProgram_ProgramIsNotActivated_Fails() ISpecProvider specProvider = FullChainSimulationChainSpecProvider.CreateDynamicSpecProvider(ArbosVersion.Forty); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(contract, specProvider.GenesisSpec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(contract, specProvider.GenesisSpec, out _); byte[] callData = CounterContractCallData.GetNumberCalldata(); using VmState vmState = CreateEvmState(state, caller, contract, codeInfo, callData); @@ -219,7 +219,7 @@ public void CallProgram_StylusVersionIsHigherThanPrograms_Fails() (Address caller, Address contract, BlockHeader header) = DeployTestsContract.DeployCounterContract(state, repository); ISpecProvider specProvider = FullChainSimulationChainSpecProvider.CreateDynamicSpecProvider(ArbosVersion.Forty); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(contract, specProvider.GenesisSpec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(contract, specProvider.GenesisSpec, out _); ProgramActivationResult result = programs.ActivateProgram(contract, state, store, header.Timestamp, MessageRunMode.MessageCommitMode, true); result.IsSuccess.Should().BeTrue(); @@ -251,7 +251,7 @@ public void CallProgram_ProgramExpired_Fails() (Address caller, Address contract, BlockHeader header) = DeployTestsContract.DeployCounterContract(state, repository); ISpecProvider specProvider = FullChainSimulationChainSpecProvider.CreateDynamicSpecProvider(ArbosVersion.Forty); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(contract, specProvider.GenesisSpec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(contract, specProvider.GenesisSpec, out _); ProgramActivationResult result = programs.ActivateProgram(contract, state, store, header.Timestamp, MessageRunMode.MessageCommitMode, true); result.IsSuccess.Should().BeTrue(); @@ -284,7 +284,7 @@ public void CallProgram_CorruptedCallData_Fails() (Address caller, Address contract, BlockHeader header) = DeployTestsContract.DeployCounterContract(state, repository); ISpecProvider specProvider = FullChainSimulationChainSpecProvider.CreateDynamicSpecProvider(ArbosVersion.Forty); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(contract, specProvider.GenesisSpec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(contract, specProvider.GenesisSpec, out _); ProgramActivationResult result = programs.ActivateProgram(contract, state, store, header.Timestamp, MessageRunMode.MessageCommitMode, true); result.IsSuccess.Should().BeTrue(); @@ -312,7 +312,7 @@ public void CallProgram_SetGetNumber_SuccessfullySetsAndGets() (Address caller, Address contract, BlockHeader header) = DeployTestsContract.DeployCounterContract(state, repository); ISpecProvider specProvider = FullChainSimulationChainSpecProvider.CreateDynamicSpecProvider(ArbosVersion.Forty); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(contract, specProvider.GenesisSpec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(contract, specProvider.GenesisSpec, out _); ProgramActivationResult result = programs.ActivateProgram(contract, state, store, header.Timestamp, MessageRunMode.MessageCommitMode, true); result.IsSuccess.Should().BeTrue(); @@ -351,7 +351,7 @@ public void CallProgram_IncrementNumber_SuccessfullyIncrements() (Address caller, Address contract, BlockHeader header) = DeployTestsContract.DeployCounterContract(state, repository); ISpecProvider specProvider = FullChainSimulationChainSpecProvider.CreateDynamicSpecProvider(ArbosVersion.Forty); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(contract, specProvider.GenesisSpec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(contract, specProvider.GenesisSpec, out _); ProgramActivationResult result = programs.ActivateProgram(contract, state, store, header.Timestamp, MessageRunMode.MessageCommitMode, true); result.IsSuccess.Should().BeTrue(); @@ -380,7 +380,7 @@ public void CallProgram_IncrementNumber_SuccessfullyIncrements() getNumberResult2.Value.Should().BeEquivalentTo(new UInt256(1).ToBigEndian()); } - private VmState CreateEvmState(IWorldState state, Address caller, Address contract, ICodeInfo codeInfo, byte[] callData, long gasAvailable = 1_000_000_000) + private VmState CreateEvmState(IWorldState state, Address caller, Address contract, CodeInfo codeInfo, byte[] callData, long gasAvailable = 1_000_000_000) { ExecutionEnvironment env = ExecutionEnvironment.Rent(codeInfo, caller, caller, contract, 0, 0, 0, callData); return VmState.RentTopLevel(ArbitrumGasPolicy.FromLong(gasAvailable), ExecutionType.TRANSACTION, env, new StackAccessTracker(), state.TakeSnapshot()); diff --git a/src/Nethermind.Arbitrum.Test/Arbos/Stylus/Infrastructure/WasmGasTestHelper.cs b/src/Nethermind.Arbitrum.Test/Arbos/Stylus/Infrastructure/WasmGasTestHelper.cs index d95b3af96..ce5569741 100644 --- a/src/Nethermind.Arbitrum.Test/Arbos/Stylus/Infrastructure/WasmGasTestHelper.cs +++ b/src/Nethermind.Arbitrum.Test/Arbos/Stylus/Infrastructure/WasmGasTestHelper.cs @@ -43,7 +43,7 @@ public WasmGasTestHelper(long gasAvailable = 1_000_000, IReleaseSpec? spec = nul // Create minimal execution environment _executionEnvironment = ExecutionEnvironment.Rent( - EmptyCodeInfo.Instance, + CodeInfo.Empty, Address.Zero, Address.Zero, Address.Zero, @@ -107,17 +107,3 @@ public void Dispose() _worldStateScope.Dispose(); } } - -/// -/// Empty code info for test purposes. -/// -internal sealed class EmptyCodeInfo : ICodeInfo -{ - public static readonly EmptyCodeInfo Instance = new(); - - private EmptyCodeInfo() { } - - public bool IsEmpty => true; - public ReadOnlyMemory Code => ReadOnlyMemory.Empty; - public ReadOnlySpan CodeSpan => ReadOnlySpan.Empty; -} diff --git a/src/Nethermind.Arbitrum.Test/Config/ArbitrumChainSpecEngineParametersTests.cs b/src/Nethermind.Arbitrum.Test/Config/ArbitrumChainSpecEngineParametersTests.cs index 3378747c3..f2ddafbbf 100644 --- a/src/Nethermind.Arbitrum.Test/Config/ArbitrumChainSpecEngineParametersTests.cs +++ b/src/Nethermind.Arbitrum.Test/Config/ArbitrumChainSpecEngineParametersTests.cs @@ -5,6 +5,7 @@ using FluentAssertions; using Nethermind.Arbitrum.Config; using Nethermind.Core; +using Nethermind.Logging; using Nethermind.Serialization.Json; using Nethermind.Specs.ChainSpecStyle; using NUnit.Framework; @@ -139,7 +140,7 @@ public void Create_WithNullParameters_UsesDefaultValues() private static ChainSpec LoadChainSpecFromJson(string json) { - ChainSpecLoader loader = new(new EthereumJsonSerializer()); + ChainSpecLoader loader = new(new EthereumJsonSerializer(), LimboLogs.Instance); using MemoryStream stream = new(System.Text.Encoding.UTF8.GetBytes(json)); return loader.Load(stream); diff --git a/src/Nethermind.Arbitrum.Test/Config/ChainSpecLoaderTests.cs b/src/Nethermind.Arbitrum.Test/Config/ChainSpecLoaderTests.cs index f8736a8c7..ee4993b19 100644 --- a/src/Nethermind.Arbitrum.Test/Config/ChainSpecLoaderTests.cs +++ b/src/Nethermind.Arbitrum.Test/Config/ChainSpecLoaderTests.cs @@ -23,7 +23,7 @@ public void FullChainSimulation_TestChainSpec_AlwaysMatchesFullChainSimulation() private static ChainSpec LoadChainSpec(string path) { - var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); + var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboLogs.Instance); var chainSpec = loader.LoadEmbeddedOrFromFile(path); return chainSpec; } diff --git a/src/Nethermind.Arbitrum.Test/Infrastructure/ArbitrumTestBlockchainBase.cs b/src/Nethermind.Arbitrum.Test/Infrastructure/ArbitrumTestBlockchainBase.cs index 4334c515f..f87335b14 100644 --- a/src/Nethermind.Arbitrum.Test/Infrastructure/ArbitrumTestBlockchainBase.cs +++ b/src/Nethermind.Arbitrum.Test/Infrastructure/ArbitrumTestBlockchainBase.cs @@ -37,6 +37,7 @@ using Nethermind.Specs.ChainSpecStyle; using Nethermind.State; using Nethermind.TxPool; +using NSubstitute; using BlockchainProcessorOptions = Nethermind.Consensus.Processing.BlockchainProcessor.Options; namespace Nethermind.Arbitrum.Test.Infrastructure; @@ -153,7 +154,8 @@ protected virtual ArbitrumTestBlockchainBase Build(Action? con Dependencies.BlockPreprocessorStep, StateReader, LogManager, - BlockchainProcessorOptions.Default); + BlockchainProcessorOptions.Default, + Substitute.For()); BlockchainProcessor = chainProcessor; BlockProcessingQueue = chainProcessor; diff --git a/src/Nethermind.Arbitrum.Test/Precompiles/ArbNativeTokenManagerTests.cs b/src/Nethermind.Arbitrum.Test/Precompiles/ArbNativeTokenManagerTests.cs index 8182640be..c5d6a5518 100644 --- a/src/Nethermind.Arbitrum.Test/Precompiles/ArbNativeTokenManagerTests.cs +++ b/src/Nethermind.Arbitrum.Test/Precompiles/ArbNativeTokenManagerTests.cs @@ -142,7 +142,7 @@ public void GetCachedCodeInfo_WhenQueriedFromCodeInfoRepository_ReturnsPrecompil ArbitrumCodeInfoRepository repository = new(baseRepository, arbosVersionProvider); IReleaseSpec spec = new ArbitrumReleaseSpec { ArbOsVersion = ArbosVersion.FortyOne }; - ICodeInfo codeInfo = repository.GetCachedCodeInfo( + CodeInfo codeInfo = repository.GetCachedCodeInfo( ArbNativeTokenManager.Address, followDelegation: false, spec, diff --git a/src/Nethermind.Arbitrum.Test/Precompiles/ArbOwnerPublicTests.cs b/src/Nethermind.Arbitrum.Test/Precompiles/ArbOwnerPublicTests.cs index 9cd095563..5de8cb7a1 100644 --- a/src/Nethermind.Arbitrum.Test/Precompiles/ArbOwnerPublicTests.cs +++ b/src/Nethermind.Arbitrum.Test/Precompiles/ArbOwnerPublicTests.cs @@ -101,7 +101,14 @@ public void GetAllChainOwnersViaRpc_AfterInitialization_ReturnsInitialOwner() .SignedAndResolved(FullChainSimulationAccounts.Owner) .TestObject; - ResultWrapper result = chain.ArbitrumEthRpcModule.eth_call(TransactionForRpc.FromTransaction(transaction), BlockParameter.Latest); + EIP1559TransactionForRpc tx = new(transaction, new(transaction.ChainId ?? BlockchainIds.Mainnet)) + { + MaxFeePerGas = 10.GWei(), + MaxPriorityFeePerGas = 2.GWei(), + GasPrice = null + }; + + ResultWrapper result = chain.ArbitrumEthRpcModule.eth_call(tx, BlockParameter.Latest); result.Result.Should().Be(Result.Success); object[] precompileResponse = AbiEncoder.Instance.Decode( @@ -434,7 +441,14 @@ public void GetScheduledUpgradeViaRpc_WithNoScheduledUpgrade_ReturnsZeros() .SignedAndResolved(FullChainSimulationAccounts.Owner) .TestObject; - ResultWrapper result = chain.ArbitrumEthRpcModule.eth_call(TransactionForRpc.FromTransaction(transaction), BlockParameter.Latest); + EIP1559TransactionForRpc tx = new(transaction, new(transaction.ChainId ?? BlockchainIds.Mainnet)) + { + MaxFeePerGas = 10.GWei(), + MaxPriorityFeePerGas = 2.GWei(), + GasPrice = null + }; + + ResultWrapper result = chain.ArbitrumEthRpcModule.eth_call(tx, BlockParameter.Latest); result.Result.Should().Be(Result.Success); object[] precompileResponse = AbiEncoder.Instance.Decode( diff --git a/src/Nethermind.Arbitrum.Test/Precompiles/ArbitrumCodeInfoRepositoryTests.cs b/src/Nethermind.Arbitrum.Test/Precompiles/ArbitrumCodeInfoRepositoryTests.cs index 3304b9c12..32ad58e1c 100644 --- a/src/Nethermind.Arbitrum.Test/Precompiles/ArbitrumCodeInfoRepositoryTests.cs +++ b/src/Nethermind.Arbitrum.Test/Precompiles/ArbitrumCodeInfoRepositoryTests.cs @@ -23,11 +23,11 @@ public void GetCachedCodeInfo_WithRegularAddress_DelegatesToBase() ArbitrumCodeInfoRepository repository = CreateRepository(ArbosVersion.ThirtyTwo, out ICodeInfoRepository baseRepository); Address regularAddress = new("0x1234567890123456789012345678901234567890"); ArbitrumReleaseSpec spec = CreateSpec(ArbosVersion.ThirtyTwo); - ICodeInfo expectedCodeInfo = Substitute.For(); + CodeInfo expectedCodeInfo = new(new byte[] { 0x00 }); ConfigureBaseRepository(baseRepository, regularAddress, false, spec, expectedCodeInfo, delegationAddress: null); - ICodeInfo result = repository.GetCachedCodeInfo(regularAddress, false, spec, out Address? delegationAddress); + CodeInfo result = repository.GetCachedCodeInfo(regularAddress, false, spec, out Address? delegationAddress); result.Should().BeSameAs(expectedCodeInfo); delegationAddress.Should().BeNull(); @@ -40,11 +40,11 @@ public void GetCachedCodeInfo_WithInactiveArbitrumPrecompile_DelegatesToBase() ArbitrumCodeInfoRepository repository = CreateRepository(29, out ICodeInfoRepository baseRepository); Address arbWasmAddress = ArbosAddresses.ArbWasmAddress; ArbitrumReleaseSpec spec = CreateSpec(29); - ICodeInfo expectedCodeInfo = Substitute.For(); + CodeInfo expectedCodeInfo = new(new byte[] { 0x00 }); ConfigureBaseRepository(baseRepository, arbWasmAddress, false, spec, expectedCodeInfo, delegationAddress: null); - ICodeInfo result = repository.GetCachedCodeInfo(arbWasmAddress, false, spec, out Address? delegationAddress); + CodeInfo result = repository.GetCachedCodeInfo(arbWasmAddress, false, spec, out Address? delegationAddress); ((IReleaseSpec)spec).IsPrecompile(arbWasmAddress).Should().BeFalse(); result.Should().BeSameAs(expectedCodeInfo); @@ -58,7 +58,7 @@ public void GetCachedCodeInfo_WithActiveArbitrumPrecompile_ReturnsArbitrumCodeIn Address arbSysAddress = ArbosAddresses.ArbSysAddress; ArbitrumReleaseSpec spec = CreateSpec(0); - ICodeInfo result = repository.GetCachedCodeInfo(arbSysAddress, false, spec, out Address? delegationAddress); + CodeInfo result = repository.GetCachedCodeInfo(arbSysAddress, false, spec, out Address? delegationAddress); ((IReleaseSpec)spec).IsPrecompile(arbSysAddress).Should().BeTrue(); result.Should().BeOfType(); @@ -71,11 +71,11 @@ public void GetCachedCodeInfo_WithEthereumPrecompile_DelegatesToBase() ArbitrumCodeInfoRepository repository = CreateRepository(ArbosVersion.ThirtyTwo, out ICodeInfoRepository baseRepository); Address ecRecoverAddress = new("0x0000000000000000000000000000000000000001"); ArbitrumReleaseSpec spec = CreateSpec(ArbosVersion.ThirtyTwo); - ICodeInfo expectedCodeInfo = Substitute.For(); + CodeInfo expectedCodeInfo = new(new byte[] { 0x00 }); ConfigureBaseRepository(baseRepository, ecRecoverAddress, false, spec, expectedCodeInfo, delegationAddress: null); - ICodeInfo result = repository.GetCachedCodeInfo(ecRecoverAddress, false, spec, out Address? delegationAddress); + CodeInfo result = repository.GetCachedCodeInfo(ecRecoverAddress, false, spec, out Address? delegationAddress); ((IReleaseSpec)spec).IsPrecompile(ecRecoverAddress).Should().BeTrue(); result.Should().BeSameAs(expectedCodeInfo); @@ -89,11 +89,11 @@ public void GetCachedCodeInfo_WithKzgAtVersion30_DelegatesToBase() Address kzgAddress = new("0x000000000000000000000000000000000000000a"); ArbitrumReleaseSpec spec = CreateSpec(ArbosVersion.Stylus); spec.IsEip4844Enabled = false; - ICodeInfo expectedCodeInfo = Substitute.For(); + CodeInfo expectedCodeInfo = new(new byte[] { 0x00 }); ConfigureBaseRepository(baseRepository, kzgAddress, false, spec, expectedCodeInfo, delegationAddress: null); - ICodeInfo result = repository.GetCachedCodeInfo(kzgAddress, false, spec, out Address? delegationAddress); + CodeInfo result = repository.GetCachedCodeInfo(kzgAddress, false, spec, out Address? delegationAddress); ((IReleaseSpec)spec).IsPrecompile(kzgAddress).Should().BeTrue(); result.Should().BeSameAs(expectedCodeInfo); @@ -105,11 +105,11 @@ public void GetCachedCodeInfo_WithGapAddress_DelegatesToBase() ArbitrumCodeInfoRepository repository = CreateRepository(ArbosVersion.ThirtyTwo, out ICodeInfoRepository baseRepository); Address gapAddress = new("0x000000000000000000000000000000000000006a"); ArbitrumReleaseSpec spec = CreateSpec(ArbosVersion.ThirtyTwo); - ICodeInfo expectedCodeInfo = Substitute.For(); + CodeInfo expectedCodeInfo = new(new byte[] { 0x00 }); ConfigureBaseRepository(baseRepository, gapAddress, false, spec, expectedCodeInfo, delegationAddress: null); - ICodeInfo result = repository.GetCachedCodeInfo(gapAddress, false, spec, out Address? delegationAddress); + CodeInfo result = repository.GetCachedCodeInfo(gapAddress, false, spec, out Address? delegationAddress); ((IReleaseSpec)spec).IsPrecompile(gapAddress).Should().BeFalse(); result.Should().BeSameAs(expectedCodeInfo); @@ -122,7 +122,7 @@ public void GetCachedCodeInfo_WithStylusPrecompileAtVersion30_ReturnsArbitrumCod Address arbWasmAddress = ArbosAddresses.ArbWasmAddress; ArbitrumReleaseSpec spec = CreateSpec(ArbosVersion.Stylus); - ICodeInfo result = repository.GetCachedCodeInfo(arbWasmAddress, false, spec, out Address? delegationAddress); + CodeInfo result = repository.GetCachedCodeInfo(arbWasmAddress, false, spec, out Address? delegationAddress); ((IReleaseSpec)spec).IsPrecompile(arbWasmAddress).Should().BeTrue(); result.Should().BeOfType(); @@ -137,12 +137,11 @@ public void GetCachedCodeInfo_WithEip7702DelegationToPrecompileBeforeArbOS50_Ret Address sha256Precompile = new("0x0000000000000000000000000000000000000002"); ArbitrumReleaseSpec spec = CreateSpec(ArbosVersion.Forty); spec.IsEip7702Enabled = true; - ICodeInfo precompileCode = Substitute.For(); - precompileCode.IsEmpty.Returns(false); + CodeInfo precompileCode = new(new byte[] { 0x00 }); ConfigureBaseRepository(baseRepository, eoaAddress, true, spec, precompileCode, sha256Precompile); - ICodeInfo result = repository.GetCachedCodeInfo(eoaAddress, true, spec, out Address? delegationAddress); + CodeInfo result = repository.GetCachedCodeInfo(eoaAddress, true, spec, out Address? delegationAddress); result.Should().BeSameAs(precompileCode); delegationAddress.Should().Be(sha256Precompile); @@ -156,12 +155,11 @@ public void GetCachedCodeInfo_WithEip7702DelegationToPrecompileAfterArbOS50AndFo Address sha256Precompile = new("0x0000000000000000000000000000000000000002"); ArbitrumReleaseSpec spec = CreateSpec(ArbosVersion.Fifty); spec.IsEip7702Enabled = true; - ICodeInfo precompileCode = Substitute.For(); - precompileCode.IsEmpty.Returns(false); + CodeInfo precompileCode = new(new byte[] { 0x00 }); ConfigureBaseRepository(baseRepository, eoaAddress, true, spec, precompileCode, sha256Precompile); - ICodeInfo result = repository.GetCachedCodeInfo(eoaAddress, true, spec, out Address? delegationAddress); + CodeInfo result = repository.GetCachedCodeInfo(eoaAddress, true, spec, out Address? delegationAddress); result.Should().BeSameAs(CodeInfo.Empty); delegationAddress.Should().Be(sha256Precompile); @@ -175,12 +173,11 @@ public void GetCachedCodeInfo_WithEip7702DelegationToPrecompileAfterArbOS50Witho Address sha256Precompile = new("0x0000000000000000000000000000000000000002"); ArbitrumReleaseSpec spec = CreateSpec(ArbosVersion.Fifty); spec.IsEip7702Enabled = true; - ICodeInfo delegationCodeInfo = Substitute.For(); - delegationCodeInfo.IsEmpty.Returns(false); + CodeInfo delegationCodeInfo = new(new byte[] { 0x00 }); ConfigureBaseRepository(baseRepository, eoaAddress, false, spec, delegationCodeInfo, sha256Precompile); - ICodeInfo result = repository.GetCachedCodeInfo(eoaAddress, false, spec, out Address? delegationAddress); + CodeInfo result = repository.GetCachedCodeInfo(eoaAddress, false, spec, out Address? delegationAddress); result.Should().BeSameAs(delegationCodeInfo); delegationAddress.Should().Be(sha256Precompile); @@ -194,12 +191,11 @@ public void GetCachedCodeInfo_WithEip7702DelegationToContractAfterArbOS50_Return Address contractAddress = new("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"); ArbitrumReleaseSpec spec = CreateSpec(ArbosVersion.Fifty); spec.IsEip7702Enabled = true; - ICodeInfo contractCode = Substitute.For(); - contractCode.IsEmpty.Returns(false); + CodeInfo contractCode = new(new byte[] { 0x60, 0x00 }); ConfigureBaseRepository(baseRepository, eoaAddress, true, spec, contractCode, contractAddress); - ICodeInfo result = repository.GetCachedCodeInfo(eoaAddress, true, spec, out Address? delegationAddress); + CodeInfo result = repository.GetCachedCodeInfo(eoaAddress, true, spec, out Address? delegationAddress); result.Should().BeSameAs(contractCode); delegationAddress.Should().Be(contractAddress); @@ -211,12 +207,11 @@ public void GetCachedCodeInfo_WithoutDelegationAfterArbOS50_ReturnsNormalCode() ArbitrumCodeInfoRepository repository = CreateRepository(ArbosVersion.Fifty, out ICodeInfoRepository baseRepository); Address normalAddress = new("0x1234567890123456789012345678901234567890"); ArbitrumReleaseSpec spec = CreateSpec(ArbosVersion.Fifty); - ICodeInfo normalCode = Substitute.For(); - normalCode.IsEmpty.Returns(false); + CodeInfo normalCode = new(new byte[] { 0x60, 0x00 }); ConfigureBaseRepository(baseRepository, normalAddress, true, spec, normalCode, delegationAddress: null); - ICodeInfo result = repository.GetCachedCodeInfo(normalAddress, true, spec, out Address? delegationAddress); + CodeInfo result = repository.GetCachedCodeInfo(normalAddress, true, spec, out Address? delegationAddress); result.Should().BeSameAs(normalCode); delegationAddress.Should().BeNull(); @@ -233,12 +228,11 @@ public void GetCachedCodeInfo_WithEip7702DelegationToAnyPrecompileAfterArbOS50_R Address precompile = new(precompileHex); ArbitrumReleaseSpec spec = CreateSpec(ArbosVersion.Fifty); spec.IsEip7702Enabled = true; - ICodeInfo precompileCode = Substitute.For(); - precompileCode.IsEmpty.Returns(false); + CodeInfo precompileCode = new(new byte[] { 0x60, 0x00 }); ConfigureBaseRepository(baseRepository, eoaAddress, true, spec, precompileCode, precompile); - ICodeInfo result = repository.GetCachedCodeInfo(eoaAddress, true, spec, out _); + CodeInfo result = repository.GetCachedCodeInfo(eoaAddress, true, spec, out _); result.Should().BeSameAs(CodeInfo.Empty); } @@ -254,7 +248,7 @@ private static ArbitrumCodeInfoRepository CreateRepository(ulong arbosVersion, o private static ArbitrumReleaseSpec CreateSpec(ulong arbosVersion) => new() { ArbOsVersion = arbosVersion }; private static void ConfigureBaseRepository(ICodeInfoRepository baseRepository, Address address, bool followDelegation, - IReleaseSpec spec, ICodeInfo returnCode, Address? delegationAddress) + IReleaseSpec spec, CodeInfo returnCode, Address? delegationAddress) { baseRepository.GetCachedCodeInfo(address, followDelegation, spec, out Arg.Any()) .Returns(x => diff --git a/src/Nethermind.Arbitrum.Test/Precompiles/Parser/ArbBlsParserTests.cs b/src/Nethermind.Arbitrum.Test/Precompiles/Parser/ArbBlsParserTests.cs index 2e5665e90..baa49d8d3 100644 --- a/src/Nethermind.Arbitrum.Test/Precompiles/Parser/ArbBlsParserTests.cs +++ b/src/Nethermind.Arbitrum.Test/Precompiles/Parser/ArbBlsParserTests.cs @@ -60,7 +60,7 @@ public void GetCachedCodeInfo_WhenQueriedFromCodeInfoRepository_ReturnsPrecompil ArbitrumCodeInfoRepository repository = new(baseRepository, arbosVersionProvider); IReleaseSpec spec = new ArbitrumReleaseSpec { ArbOsVersion = ArbosVersion.FortyOne }; - ICodeInfo codeInfo = repository.GetCachedCodeInfo( + CodeInfo codeInfo = repository.GetCachedCodeInfo( ArbBls.Address, followDelegation: false, spec, diff --git a/src/Nethermind.Arbitrum.Test/Rpc/ArbitrumEthRpcModuleTests.cs b/src/Nethermind.Arbitrum.Test/Rpc/ArbitrumEthRpcModuleTests.cs index 3caf441a1..5e5fd494d 100644 --- a/src/Nethermind.Arbitrum.Test/Rpc/ArbitrumEthRpcModuleTests.cs +++ b/src/Nethermind.Arbitrum.Test/Rpc/ArbitrumEthRpcModuleTests.cs @@ -110,7 +110,7 @@ public async Task EthCall_ContractCreationWithoutData_ReturnsInvalidInputError() result.Result.ResultType.Should().Be(ResultType.Failure); result.ErrorCode.Should().Be(ErrorCodes.InvalidInput); - result.Result.Error.Should().Contain("Contract creation without any data provided"); + result.Result.Error.Should().Contain("contract creation without any data provided"); } [Test] @@ -130,7 +130,7 @@ public async Task EthEstimateGas_ContractCreationWithoutData_ReturnsInvalidInput result.Result.ResultType.Should().Be(ResultType.Failure); result.ErrorCode.Should().Be(ErrorCodes.InvalidInput); - result.Result.Error.Should().Contain("Contract creation without any data provided"); + result.Result.Error.Should().Contain("contract creation without any data provided"); } [Test] @@ -216,7 +216,7 @@ public async Task EthCreateAccessList_ContractCreationWithoutData_ReturnsInvalid result.Result.ResultType.Should().Be(ResultType.Failure); result.ErrorCode.Should().Be(ErrorCodes.InvalidInput); - result.Result.Error.Should().Contain("Contract creation without any data provided"); + result.Result.Error.Should().Contain("contract creation without any data provided"); } [Test] diff --git a/src/Nethermind.Arbitrum.Test/Rpc/ArbitrumTransactionForRpcTests.cs b/src/Nethermind.Arbitrum.Test/Rpc/ArbitrumTransactionForRpcTests.cs index 457a37cf6..165820e14 100644 --- a/src/Nethermind.Arbitrum.Test/Rpc/ArbitrumTransactionForRpcTests.cs +++ b/src/Nethermind.Arbitrum.Test/Rpc/ArbitrumTransactionForRpcTests.cs @@ -8,6 +8,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; +using Nethermind.Facade.Eth; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Int256; @@ -29,8 +30,8 @@ public void ArbitrumInternalTransaction_RoundTrip_PreservesAllFields() Data = new byte[] { 0x01, 0x02, 0x03 } }; - TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, chainId: TestChainId); - Transaction reconstructed = rpcTx.ToTransaction(); + TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, new TransactionForRpcContext(TestChainId)); + Transaction reconstructed = (Transaction)rpcTx.ToTransaction(); reconstructed.Type.Should().Be(tx.Type); reconstructed.ChainId.Should().Be(TestChainId); @@ -54,7 +55,7 @@ public void ArbitrumDepositTransaction_WithBlockContext_IncludesBlockData() long blockNumber = 42; int txIndex = 5; - TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, blockHash, blockNumber, txIndex, chainId: TestChainId); + TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, new TransactionForRpcContext(TestChainId, blockHash, blockNumber, txIndex, blockTimestamp: 0, baseFee: 0)); ArbitrumDepositTransactionForRpc depositRpc = rpcTx.Should().BeOfType().Subject; depositRpc.BlockHash.Should().Be(blockHash); @@ -78,8 +79,8 @@ public void ArbitrumUnsignedTransaction_RoundTrip_PreservesGasFeeCap() Data = new byte[] { 0xaa, 0xbb } }; - TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, chainId: TestChainId); - ArbitrumUnsignedTransaction? reconstructed = rpcTx.ToTransaction() as ArbitrumUnsignedTransaction; + TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, new TransactionForRpcContext(TestChainId)); + ArbitrumUnsignedTransaction? reconstructed = (Transaction)rpcTx.ToTransaction() as ArbitrumUnsignedTransaction; reconstructed.Should().NotBeNull(); reconstructed!.GasFeeCap.Should().Be(1000.Wei()); @@ -106,8 +107,8 @@ public void ArbitrumRetryTransaction_RoundTrip_PreservesRefundFields() SubmissionFeeRefund = 10.Wei() }; - TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, chainId: TestChainId); - ArbitrumRetryTransaction? reconstructed = rpcTx.ToTransaction() as ArbitrumRetryTransaction; + TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, new TransactionForRpcContext(TestChainId)); + ArbitrumRetryTransaction? reconstructed = (Transaction)rpcTx.ToTransaction() as ArbitrumRetryTransaction; reconstructed.Should().NotBeNull(); reconstructed!.TicketId.Should().Be(_testHash); @@ -137,8 +138,8 @@ public void ArbitrumSubmitRetryableTransaction_RoundTrip_PreservesRetryData() RetryData = retryData }; - TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, chainId: TestChainId); - ArbitrumSubmitRetryableTransaction? reconstructed = rpcTx.ToTransaction() as ArbitrumSubmitRetryableTransaction; + TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, new TransactionForRpcContext(TestChainId)); + ArbitrumSubmitRetryableTransaction? reconstructed = (Transaction)rpcTx.ToTransaction() as ArbitrumSubmitRetryableTransaction; reconstructed.Should().NotBeNull(); reconstructed!.RequestId.Should().Be(_testHash); @@ -164,8 +165,8 @@ public void ArbitrumContractTransaction_RoundTrip_PreservesRequestId() Data = new byte[] { 0x00, 0x11 } }; - TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, chainId: TestChainId); - ArbitrumContractTransaction? reconstructed = rpcTx.ToTransaction() as ArbitrumContractTransaction; + TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, new TransactionForRpcContext(TestChainId)); + ArbitrumContractTransaction? reconstructed = (Transaction)rpcTx.ToTransaction() as ArbitrumContractTransaction; reconstructed.Should().NotBeNull(); reconstructed!.RequestId.Should().Be(_testHash); @@ -185,8 +186,8 @@ public void ArbitrumDepositTransaction_WithNullTo_PreservesNullValue() Value = 1.Ether() }; - TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, chainId: TestChainId); - Transaction reconstructed = rpcTx.ToTransaction(); + TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, new TransactionForRpcContext(TestChainId)); + Transaction reconstructed = (Transaction)rpcTx.ToTransaction(); reconstructed.To.Should().BeNull(); reconstructed.IsContractCreation.Should().BeTrue(); @@ -201,7 +202,7 @@ public void ArbitrumInternalTransaction_WhenSerialized_ReturnsCorrectType() Data = Array.Empty() }; - TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, chainId: TestChainId); + TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, new TransactionForRpcContext(TestChainId)); rpcTx.Type.Should().Be((TxType)ArbitrumTxType.ArbitrumInternal); rpcTx.Should().BeOfType(); @@ -226,8 +227,8 @@ public void ArbitrumRetryTransaction_WithZeroValues_PreservesZeros() SubmissionFeeRefund = UInt256.Zero }; - TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, chainId: TestChainId); - ArbitrumRetryTransaction? reconstructed = rpcTx.ToTransaction() as ArbitrumRetryTransaction; + TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, new TransactionForRpcContext(TestChainId)); + ArbitrumRetryTransaction? reconstructed = (Transaction)rpcTx.ToTransaction() as ArbitrumRetryTransaction; reconstructed.Should().NotBeNull(); reconstructed!.TicketId.Should().Be(Hash256.Zero); @@ -255,8 +256,8 @@ public void ArbitrumSubmitRetryableTransaction_WithEmptyRetryData_PreservesEmpty RetryData = Array.Empty() }; - TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, chainId: TestChainId); - ArbitrumSubmitRetryableTransaction? reconstructed = rpcTx.ToTransaction() as ArbitrumSubmitRetryableTransaction; + TransactionForRpc rpcTx = TransactionForRpc.FromTransaction(tx, new TransactionForRpcContext(TestChainId)); + ArbitrumSubmitRetryableTransaction? reconstructed = (Transaction)rpcTx.ToTransaction() as ArbitrumSubmitRetryableTransaction; reconstructed.Should().NotBeNull(); reconstructed!.RetryData.ToArray().Should().BeEmpty(); diff --git a/src/Nethermind.Arbitrum/ArbitrumPlugin.cs b/src/Nethermind.Arbitrum/ArbitrumPlugin.cs index 42f2b8cb3..0c728f1d7 100644 --- a/src/Nethermind.Arbitrum/ArbitrumPlugin.cs +++ b/src/Nethermind.Arbitrum/ArbitrumPlugin.cs @@ -21,6 +21,7 @@ using Nethermind.Consensus; using Nethermind.Consensus.Processing; using Nethermind.Consensus.Producers; +using Nethermind.Arbitrum.Processing; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Container; @@ -244,6 +245,8 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton() + .AddScoped() + // Rpcs .AddSingleton() .Bind, ArbitrumEthModuleFactory>(); diff --git a/src/Nethermind.Arbitrum/Arbos/Programs/StylusPrograms.cs b/src/Nethermind.Arbitrum/Arbos/Programs/StylusPrograms.cs index 01a8baa9a..e76279eef 100644 --- a/src/Nethermind.Arbitrum/Arbos/Programs/StylusPrograms.cs +++ b/src/Nethermind.Arbitrum/Arbos/Programs/StylusPrograms.cs @@ -8,6 +8,7 @@ using Nethermind.Arbitrum.Data.Transactions; using Nethermind.Arbitrum.Evm; using Nethermind.Arbitrum.Math; +using Nethermind.Arbitrum.Metrics; using Nethermind.Arbitrum.Stylus; using Nethermind.Arbitrum.Tracing; using Nethermind.Core; @@ -203,7 +204,10 @@ public StylusOperationResult CallProgram(IStylusVmHost vmHost, TracingIn }; using IStylusEvmApi evmApi = new StylusEvmApi(vmHost, vmHost.VmState.Env.ExecutingAccount, memoryModel); + + long startTimestamp = Stopwatch.GetTimestamp(); StylusNativeResult callResult = StylusNative.Call(localAsm.Value, vmHost.VmState.Env.InputData.ToArray(), stylusConfig, evmApi, evmData, debugMode, arbosTag, ref gasAvailable); + long elapsedMicroseconds = (long)Stopwatch.GetElapsedTime(startTimestamp).TotalMicroseconds; vmHost.VmState.Gas = ArbitrumGasPolicy.FromLong((long)gasAvailable); @@ -213,6 +217,7 @@ public StylusOperationResult CallProgram(IStylusVmHost vmHost, TracingIn ulong evmCost = GetEvmMemoryCost((ulong)resultLength); if (startingGas < evmCost) { + ArbitrumMetrics.RecordStylusExecution(elapsedMicroseconds); vmHost.VmState.Gas = ArbitrumGasPolicy.FromLong(0); return StylusOperationResult.Failure(new(StylusOperationResultType.ExecutionOutOfGas, "Run out of gas during EVM memory cost calculation", [])); } @@ -221,6 +226,8 @@ public StylusOperationResult CallProgram(IStylusVmHost vmHost, TracingIn vmHost.VmState.Gas = ArbitrumGasPolicy.FromLong((long)System.Math.Min(gasAvailable, maxGasToReturn)); } + ArbitrumMetrics.RecordStylusExecution(elapsedMicroseconds); + return callResult.IsSuccess ? StylusOperationResult.Success(callResult.Value) : StylusOperationResult.Failure( diff --git a/src/Nethermind.Arbitrum/Evm/ArbitrumVirtualMachine.cs b/src/Nethermind.Arbitrum/Evm/ArbitrumVirtualMachine.cs index 1d150814c..3ea1a3c7b 100644 --- a/src/Nethermind.Arbitrum/Evm/ArbitrumVirtualMachine.cs +++ b/src/Nethermind.Arbitrum/Evm/ArbitrumVirtualMachine.cs @@ -169,7 +169,7 @@ public StylusEvmResult StylusCall(ExecutionType kind, Address to, ReadOnlyMemory WorldState.SubtractFromBalance(caller, in transferValue, Spec); // Retrieve code information for the call and schedule background analysis if needed. - ICodeInfo codeInfo = CodeInfoRepository.GetCachedCodeInfo(to, Spec); + CodeInfo codeInfo = CodeInfoRepository.GetCachedCodeInfo(to, Spec); ReadOnlyMemory callData = input; @@ -307,7 +307,7 @@ public StylusEvmResult StylusCreate(ReadOnlyMemory initCode, in UInt256 en state.IncrementNonce(env.ExecutingAccount); // Analyze and compile the initialization code. - CodeInfoFactory.CreateInitCodeInfo(initCode, Spec, out ICodeInfo? codeinfo, out _); + CodeInfoFactory.CreateInitCodeInfo(initCode, Spec, out CodeInfo? codeinfo, out _); // Take a snapshot of the current state. This allows the state to be reverted if contract creation fails. Snapshot snapshot = state.TakeSnapshot(); @@ -433,7 +433,7 @@ protected override OpCode[] GenerateOpCodes(IReleaseSpec spec) protected override CallResult ExecutePrecompile(VmState currentState, bool isTracingActions, out Exception? failure, out string? substateError) { // If precompile is not an arbitrum specific precompile but a standard one - if (currentState.Env.CodeInfo is Nethermind.Evm.CodeAnalysis.PrecompileInfo) + if (currentState.Env.CodeInfo is not PrecompileInfo precompileInfo) return base.ExecutePrecompile(currentState, isTracingActions, out failure, out substateError); // Report the precompile action if tracing is enabled. @@ -450,7 +450,7 @@ protected override CallResult ExecutePrecompile(VmState curre } // Execute the precompile operation with the current state. - CallResult callResult = RunPrecompile(currentState); + CallResult callResult = RunPrecompile(currentState, precompileInfo); // If the precompile did not succeed without a revert, handle the failure conditions. if (!callResult.PrecompileSuccess!.Value && !callResult.ShouldRevert) @@ -473,11 +473,11 @@ protected override CallResult ExecutePrecompile(VmState curre return callResult; } - private CallResult RunPrecompile(VmState state) + private CallResult RunPrecompile(VmState state, PrecompileInfo precompileInfo) { WorldState.AddToBalanceAndCreateIfNotExists(state.Env.ExecutingAccount, state.Env.Value, Spec); - IArbitrumPrecompile precompile = ((PrecompileInfo)state.Env.CodeInfo).Precompile; + IArbitrumPrecompile precompile = precompileInfo.ArbitrumPrecompile; TracingInfo tracingInfo = new( TxTracer as IArbitrumTxTracer ?? ArbNullTxTracer.Instance, @@ -741,7 +741,7 @@ private PrecompileOutcome DefaultExceptionHandling(ArbitrumPrecompileExecutionCo private CallResult RunWasmCode(scoped ref ArbitrumGasPolicy gas) { Address actingAddress = VmState.To; - ICodeInfo codeInfo = VmState.Env.CodeInfo; + CodeInfo codeInfo = VmState.Env.CodeInfo; VmState.Gas = gas; @@ -786,7 +786,7 @@ private bool IsDebugMode() return specHelper.AllowDebugPrecompiles; } - private CallResult CreateErrorResult(StylusOperationResult output, ICodeInfo codeInfo) + private CallResult CreateErrorResult(StylusOperationResult output, CodeInfo codeInfo) { EvmExceptionType exceptionType = output.Error!.Value.OperationResultType.ToEvmExceptionType(); byte[] errorData = output.Value ?? []; diff --git a/src/Nethermind.Arbitrum/Execution/ArbitrumTransactionProcessor.cs b/src/Nethermind.Arbitrum/Execution/ArbitrumTransactionProcessor.cs index afdd541b0..b79ddb662 100644 --- a/src/Nethermind.Arbitrum/Execution/ArbitrumTransactionProcessor.cs +++ b/src/Nethermind.Arbitrum/Execution/ArbitrumTransactionProcessor.cs @@ -8,9 +8,9 @@ using Nethermind.Arbitrum.Evm; using Nethermind.Arbitrum.Execution.Transactions; using Nethermind.Arbitrum.Math; +using Nethermind.Arbitrum.Metrics; using Nethermind.Arbitrum.Precompiles; using Nethermind.Arbitrum.Tracing; -using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -127,6 +127,8 @@ protected override TransactionResult Execute(Transaction tx, ITxTracer tracer, E private void InitializeTransactionState(Transaction tx, IArbitrumTxTracer tracer) { + ArbitrumMetrics.ResetTransactionTracking(); + ExecutionEnvironment executionEnv = ExecutionEnvironment.Rent(CodeInfo.Empty, tx.SenderAddress!, tx.To!, tx.To, 0, tx.Value, tx.Value, tx.Data); diff --git a/src/Nethermind.Arbitrum/Metrics/ArbitrumMetrics.cs b/src/Nethermind.Arbitrum/Metrics/ArbitrumMetrics.cs new file mode 100644 index 000000000..9c31d6f68 --- /dev/null +++ b/src/Nethermind.Arbitrum/Metrics/ArbitrumMetrics.cs @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.ComponentModel; +using Nethermind.Core.Attributes; +using Nethermind.Core.Threading; + +namespace Nethermind.Arbitrum.Metrics; + +public class ArbitrumMetrics +{ + [CounterMetric] + [Description("Number of Stylus WASM calls executed.")] + public static long StylusCalls => _stylusCalls.GetTotalValue(); + private static readonly ZeroContentionCounter _stylusCalls = new(); + + [CounterMetric] + [Description("Number of transactions that executed Stylus WASM code.")] + public static long StylusTransactions => _stylusTransactions.GetTotalValue(); + private static readonly ZeroContentionCounter _stylusTransactions = new(); + + [CounterMetric] + [Description("Total Stylus WASM execution time in microseconds.")] + public static long StylusExecutionMicroseconds => _stylusExecutionMicroseconds.GetTotalValue(); + private static readonly ZeroContentionCounter _stylusExecutionMicroseconds = new(); + + [ThreadStatic] + private static bool _currentTxUsedStylus; + + /// + /// Records a Stylus WASM execution. Called after each native call completes. + /// + public static void RecordStylusExecution(long executionMicroseconds) + { + _stylusCalls.Increment(); + _stylusExecutionMicroseconds.Increment((int)executionMicroseconds); + + if (_currentTxUsedStylus) + return; + + _currentTxUsedStylus = true; + _stylusTransactions.Increment(); + } + + /// + /// Resets per-transaction tracking. Call at the start of each transaction. + /// + public static void ResetTransactionTracking() + { + _currentTxUsedStylus = false; + } +} diff --git a/src/Nethermind.Arbitrum/Modules/ArbitrumEthRpcModule.cs b/src/Nethermind.Arbitrum/Modules/ArbitrumEthRpcModule.cs index 55407d0a8..e0487129f 100644 --- a/src/Nethermind.Arbitrum/Modules/ArbitrumEthRpcModule.cs +++ b/src/Nethermind.Arbitrum/Modules/ArbitrumEthRpcModule.cs @@ -194,9 +194,13 @@ public override ResultWrapper Execute( return base.Execute(transactionCall, blockParameter, stateOverride, searchResult); } - protected override Transaction Prepare(TransactionForRpc call) + protected override Result Prepare(TransactionForRpc call) { - Transaction tx = call.ToTransaction(); + Result result = call.ToTransaction(validateUserInput: true); + if (result.IsError) + return result; + + Transaction tx = result.Data; tx.ChainId = _blockchainBridge.GetChainId(); return tx; } @@ -232,9 +236,12 @@ protected override ResultWrapper ExecuteTx(BlockHeader header, Transacti { CallOutput result = _blockchainBridge.Call(header, tx, stateOverride, token); - return result.Error is null - ? ResultWrapper.Success(result.OutputData.ToHexString(true)) - : TryGetInputError(result) ?? ResultWrapper.Fail("VM execution error.", ErrorCodes.ExecutionError, result.Error); + return result switch + { + { Error: null } => ResultWrapper.Success(result.OutputData.ToHexString(true)), + { InputError: true } => ResultWrapper.Fail(result.Error, ErrorCodes.InvalidInput), + _ => ResultWrapper.Fail(result.Error, ErrorCodes.ExecutionError) + }; } } diff --git a/src/Nethermind.Arbitrum/Modules/ArbitrumTransactionsForRpc.cs b/src/Nethermind.Arbitrum/Modules/ArbitrumTransactionsForRpc.cs index 916318554..03552ccc9 100644 --- a/src/Nethermind.Arbitrum/Modules/ArbitrumTransactionsForRpc.cs +++ b/src/Nethermind.Arbitrum/Modules/ArbitrumTransactionsForRpc.cs @@ -31,16 +31,16 @@ public class ArbitrumInternalTransactionForRpc : TransactionForRpc, IFromTransac [JsonConstructor] public ArbitrumInternalTransactionForRpc() { } - public ArbitrumInternalTransactionForRpc(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) - : base(transaction, txIndex, blockHash, blockNumber) + public ArbitrumInternalTransactionForRpc(Transaction transaction, in TransactionForRpcContext extraData) + : base(transaction, extraData) { - ChainId = transaction.ChainId; + ChainId = extraData.ChainId ?? transaction.ChainId; From = transaction.SenderAddress ?? Address.Zero; To = transaction.To; Input = transaction.Data.ToArray(); } - public override Transaction ToTransaction() + public override Result ToTransaction(bool validateUserInput = false) { return new ArbitrumInternalTransaction { @@ -50,13 +50,8 @@ public override Transaction ToTransaction() }; } - public static ArbitrumInternalTransactionForRpc FromTransaction(Transaction tx, TransactionConverterExtraData extraData) - { - return new ArbitrumInternalTransactionForRpc(tx, extraData.TxIndex, extraData.BlockHash, extraData.BlockNumber) - { - ChainId = extraData.ChainId ?? tx.ChainId - }; - } + public static ArbitrumInternalTransactionForRpc FromTransaction(Transaction tx, in TransactionForRpcContext extraData) + => new(tx, extraData); public override void EnsureDefaults(long? gasCap) { } @@ -85,18 +80,18 @@ public class ArbitrumDepositTransactionForRpc : TransactionForRpc, IFromTransact [JsonConstructor] public ArbitrumDepositTransactionForRpc() { } - public ArbitrumDepositTransactionForRpc(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) - : base(transaction, txIndex, blockHash, blockNumber) + public ArbitrumDepositTransactionForRpc(Transaction transaction, in TransactionForRpcContext extraData) + : base(transaction, extraData) { ArbitrumDepositTransaction? depositTx = transaction as ArbitrumDepositTransaction; RequestId = depositTx?.L1RequestId; - ChainId = transaction.ChainId; + ChainId = extraData.ChainId ?? transaction.ChainId; From = transaction.SenderAddress ?? Address.Zero; To = transaction.To; Value = transaction.Value; } - public override Transaction ToTransaction() + public override Result ToTransaction(bool validateUserInput = false) { return new ArbitrumDepositTransaction { @@ -109,13 +104,8 @@ public override Transaction ToTransaction() }; } - public static ArbitrumDepositTransactionForRpc FromTransaction(Transaction tx, TransactionConverterExtraData extraData) - { - return new ArbitrumDepositTransactionForRpc(tx, extraData.TxIndex, extraData.BlockHash, extraData.BlockNumber) - { - ChainId = extraData.ChainId ?? tx.ChainId - }; - } + public static ArbitrumDepositTransactionForRpc FromTransaction(Transaction tx, in TransactionForRpcContext extraData) + => new(tx, extraData); public override void EnsureDefaults(long? gasCap) { } @@ -149,11 +139,11 @@ public class ArbitrumUnsignedTransactionForRpc : TransactionForRpc, IFromTransac [JsonConstructor] public ArbitrumUnsignedTransactionForRpc() { } - public ArbitrumUnsignedTransactionForRpc(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) - : base(transaction, txIndex, blockHash, blockNumber) + public ArbitrumUnsignedTransactionForRpc(Transaction transaction, in TransactionForRpcContext extraData) + : base(transaction, extraData) { ArbitrumUnsignedTransaction? unsignedTx = transaction as ArbitrumUnsignedTransaction; - ChainId = transaction.ChainId; + ChainId = extraData.ChainId ?? transaction.ChainId; From = transaction.SenderAddress ?? Address.Zero; Nonce = transaction.Nonce; GasFeeCap = unsignedTx?.GasFeeCap ?? transaction.MaxFeePerGas; @@ -163,7 +153,7 @@ public ArbitrumUnsignedTransactionForRpc(Transaction transaction, int? txIndex = Input = transaction.Data.ToArray(); } - public override Transaction ToTransaction() + public override Result ToTransaction(bool validateUserInput = false) { return new ArbitrumUnsignedTransaction { @@ -181,13 +171,8 @@ public override Transaction ToTransaction() }; } - public static ArbitrumUnsignedTransactionForRpc FromTransaction(Transaction tx, TransactionConverterExtraData extraData) - { - return new ArbitrumUnsignedTransactionForRpc(tx, extraData.TxIndex, extraData.BlockHash, extraData.BlockNumber) - { - ChainId = extraData.ChainId ?? tx.ChainId - }; - } + public static ArbitrumUnsignedTransactionForRpc FromTransaction(Transaction tx, in TransactionForRpcContext extraData) + => new(tx, extraData); public override void EnsureDefaults(long? gasCap) { } @@ -233,11 +218,11 @@ public class ArbitrumRetryTransactionForRpc : TransactionForRpc, IFromTransactio [JsonConstructor] public ArbitrumRetryTransactionForRpc() { } - public ArbitrumRetryTransactionForRpc(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) - : base(transaction, txIndex, blockHash, blockNumber) + public ArbitrumRetryTransactionForRpc(Transaction transaction, in TransactionForRpcContext extraData) + : base(transaction, extraData) { ArbitrumRetryTransaction? retryTx = transaction as ArbitrumRetryTransaction; - ChainId = transaction.ChainId; + ChainId = extraData.ChainId ?? transaction.ChainId; TicketId = retryTx?.TicketId; From = transaction.SenderAddress ?? Address.Zero; Nonce = transaction.Nonce; @@ -251,7 +236,7 @@ public ArbitrumRetryTransactionForRpc(Transaction transaction, int? txIndex = nu SubmissionFeeRefund = retryTx?.SubmissionFeeRefund; } - public override Transaction ToTransaction() + public override Result ToTransaction(bool validateUserInput = false) { return new ArbitrumRetryTransaction { @@ -273,13 +258,8 @@ public override Transaction ToTransaction() }; } - public static ArbitrumRetryTransactionForRpc FromTransaction(Transaction tx, TransactionConverterExtraData extraData) - { - return new ArbitrumRetryTransactionForRpc(tx, extraData.TxIndex, extraData.BlockHash, extraData.BlockNumber) - { - ChainId = extraData.ChainId ?? tx.ChainId - }; - } + public static ArbitrumRetryTransactionForRpc FromTransaction(Transaction tx, in TransactionForRpcContext extraData) + => new(tx, extraData); public override void EnsureDefaults(long? gasCap) { } @@ -336,11 +316,11 @@ public class ArbitrumSubmitRetryableTransactionForRpc : TransactionForRpc, IFrom [JsonConstructor] public ArbitrumSubmitRetryableTransactionForRpc() { } - public ArbitrumSubmitRetryableTransactionForRpc(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) - : base(transaction, txIndex, blockHash, blockNumber) + public ArbitrumSubmitRetryableTransactionForRpc(Transaction transaction, in TransactionForRpcContext extraData) + : base(transaction, extraData) { ArbitrumSubmitRetryableTransaction? retryableTx = transaction as ArbitrumSubmitRetryableTransaction; - ChainId = transaction.ChainId; + ChainId = extraData.ChainId ?? transaction.ChainId; RequestId = retryableTx?.RequestId; From = transaction.SenderAddress ?? Address.Zero; L1BaseFee = retryableTx?.L1BaseFee; @@ -357,7 +337,7 @@ public ArbitrumSubmitRetryableTransactionForRpc(Transaction transaction, int? tx Input = transaction.Data.ToArray(); } - public override Transaction ToTransaction() + public override Result ToTransaction(bool validateUserInput = false) { return new ArbitrumSubmitRetryableTransaction { @@ -381,13 +361,8 @@ public override Transaction ToTransaction() }; } - public static ArbitrumSubmitRetryableTransactionForRpc FromTransaction(Transaction tx, TransactionConverterExtraData extraData) - { - return new ArbitrumSubmitRetryableTransactionForRpc(tx, extraData.TxIndex, extraData.BlockHash, extraData.BlockNumber) - { - ChainId = extraData.ChainId ?? tx.ChainId - }; - } + public static ArbitrumSubmitRetryableTransactionForRpc FromTransaction(Transaction tx, in TransactionForRpcContext extraData) + => new(tx, extraData); public override void EnsureDefaults(long? gasCap) { } @@ -422,11 +397,11 @@ public class ArbitrumContractTransactionForRpc : TransactionForRpc, IFromTransac [JsonConstructor] public ArbitrumContractTransactionForRpc() { } - public ArbitrumContractTransactionForRpc(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) - : base(transaction, txIndex, blockHash, blockNumber) + public ArbitrumContractTransactionForRpc(Transaction transaction, in TransactionForRpcContext extraData) + : base(transaction, extraData) { ArbitrumContractTransaction? contractTx = transaction as ArbitrumContractTransaction; - ChainId = transaction.ChainId; + ChainId = extraData.ChainId ?? transaction.ChainId; RequestId = contractTx?.RequestId; From = transaction.SenderAddress ?? Address.Zero; GasFeeCap = contractTx?.GasFeeCap ?? transaction.MaxFeePerGas; @@ -436,7 +411,7 @@ public ArbitrumContractTransactionForRpc(Transaction transaction, int? txIndex = Input = transaction.Data.ToArray(); } - public override Transaction ToTransaction() + public override Result ToTransaction(bool validateUserInput = false) { return new ArbitrumContractTransaction { @@ -454,13 +429,8 @@ public override Transaction ToTransaction() }; } - public static ArbitrumContractTransactionForRpc FromTransaction(Transaction tx, TransactionConverterExtraData extraData) - { - return new ArbitrumContractTransactionForRpc(tx, extraData.TxIndex, extraData.BlockHash, extraData.BlockNumber) - { - ChainId = extraData.ChainId ?? tx.ChainId - }; - } + public static ArbitrumContractTransactionForRpc FromTransaction(Transaction tx, in TransactionForRpcContext extraData) + => new(tx, extraData); public override void EnsureDefaults(long? gasCap) { } diff --git a/src/Nethermind.Arbitrum/Precompiles/ArbitrumCodeInfoRepository.cs b/src/Nethermind.Arbitrum/Precompiles/ArbitrumCodeInfoRepository.cs index ff8a6d966..b57db0fc5 100644 --- a/src/Nethermind.Arbitrum/Precompiles/ArbitrumCodeInfoRepository.cs +++ b/src/Nethermind.Arbitrum/Precompiles/ArbitrumCodeInfoRepository.cs @@ -14,11 +14,11 @@ namespace Nethermind.Arbitrum.Precompiles; public class ArbitrumCodeInfoRepository(ICodeInfoRepository codeInfoRepository, IArbosVersionProvider arbosVersionProvider) : ICodeInfoRepository { - private readonly Dictionary _arbitrumPrecompiles = InitializePrecompiledContracts(); + private readonly Dictionary _arbitrumPrecompiles = InitializePrecompiledContracts(); - private static Dictionary InitializePrecompiledContracts() + private static Dictionary InitializePrecompiledContracts() { - return new Dictionary + return new Dictionary { [ArbInfoParser.Address] = new PrecompileInfo(ArbInfoParser.Instance), [ArbRetryableTxParser.Address] = new PrecompileInfo(ArbRetryableTxParser.Instance), @@ -40,14 +40,14 @@ private static Dictionary InitializePrecompiledContracts() }; } - public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) + public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { // Check spec FIRST to respect version-based precompile activation // This ensures inactive precompiles are treated as regular accounts for gas charging if (!vmSpec.IsPrecompile(codeSource)) { // Not a precompile according to spec - do regular code lookup - ICodeInfo result = codeInfoRepository.GetCachedCodeInfo(codeSource, followDelegation, vmSpec, out delegationAddress); + CodeInfo result = codeInfoRepository.GetCachedCodeInfo(codeSource, followDelegation, vmSpec, out delegationAddress); // EIP-7702 precompile delegation fix (ArbOS 50+) // When following delegation to a precompile, return empty code instead of precompile code (0xFE) @@ -66,7 +66,7 @@ delegationAddress is not null && // It's a precompile according to spec // Check if it's an Arbitrum precompile we handle delegationAddress = null; - return _arbitrumPrecompiles.TryGetValue(codeSource, out ICodeInfo? arbResult) + return _arbitrumPrecompiles.TryGetValue(codeSource, out CodeInfo? arbResult) ? arbResult : // Must be Ethereum precompile - delegate to base repository diff --git a/src/Nethermind.Arbitrum/Precompiles/ArbitrumPrecompileExecutionContext.cs b/src/Nethermind.Arbitrum/Precompiles/ArbitrumPrecompileExecutionContext.cs index 04b97294f..71b0e0e8d 100644 --- a/src/Nethermind.Arbitrum/Precompiles/ArbitrumPrecompileExecutionContext.cs +++ b/src/Nethermind.Arbitrum/Precompiles/ArbitrumPrecompileExecutionContext.cs @@ -87,7 +87,7 @@ public void Burn(ulong amount) public void BurnOut() { GasLeft = 0; - Metrics.EvmExceptions++; + Nethermind.Evm.Metrics.EvmExceptions++; throw ArbitrumPrecompileException.CreateOutOfGasException(); } diff --git a/src/Nethermind.Arbitrum/Precompiles/PrecompileInfo.cs b/src/Nethermind.Arbitrum/Precompiles/PrecompileInfo.cs index 0121d174a..3d5e9ce6e 100644 --- a/src/Nethermind.Arbitrum/Precompiles/PrecompileInfo.cs +++ b/src/Nethermind.Arbitrum/Precompiles/PrecompileInfo.cs @@ -1,15 +1,36 @@ +using System; +using Nethermind.Core; +using Nethermind.Core.Specs; using Nethermind.Evm; using Nethermind.Evm.CodeAnalysis; +using Nethermind.Evm.Precompiles; namespace Nethermind.Arbitrum.Precompiles; -public sealed class PrecompileInfo(IArbitrumPrecompile precompile) : ICodeInfo +public sealed class PrecompileInfo : CodeInfo { private static readonly byte[] PrecompileCode = [(byte)Instruction.INVALID]; - public ReadOnlyMemory Code => PrecompileCode; - ReadOnlySpan ICodeInfo.CodeSpan => Code.Span; - public IArbitrumPrecompile Precompile { get; } = precompile; + private static readonly IPrecompile PrecompileStub = new ArbitrumPrecompileStub(); - public bool IsPrecompile => true; - public bool IsEmpty => false; + public PrecompileInfo(IArbitrumPrecompile precompile) + : base(PrecompileStub, version: 0, new ReadOnlyMemory(PrecompileCode)) + { + ArbitrumPrecompile = precompile; + } + + public IArbitrumPrecompile ArbitrumPrecompile { get; } + + /// + /// Stateless stub that satisfies 's requirement + /// so that returns true. + /// Actual execution is handled by which dispatches + /// to directly. + /// + private sealed class ArbitrumPrecompileStub : IPrecompile + { + public long BaseGasCost(IReleaseSpec releaseSpec) => 0; + public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0; + public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => + throw new InvalidOperationException("Arbitrum precompiles are executed through ArbitrumVirtualMachine, not through IPrecompile.Run"); + } } diff --git a/src/Nethermind.Arbitrum/Processing/ArbitrumProcessingStats.cs b/src/Nethermind.Arbitrum/Processing/ArbitrumProcessingStats.cs new file mode 100644 index 000000000..626177167 --- /dev/null +++ b/src/Nethermind.Arbitrum/Processing/ArbitrumProcessingStats.cs @@ -0,0 +1,407 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics; +using System.Threading; +using Microsoft.Extensions.ObjectPool; +using Nethermind.Arbitrum.Metrics; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.State; +using BlockchainMetrics = Nethermind.Blockchain.Metrics; +using EvmMetrics = Nethermind.Evm.Metrics; + +namespace Nethermind.Arbitrum.Processing; + +/// +/// Arbitrum-specific processing statistics and Stylus WASM execution metrics. +/// +public class ArbitrumProcessingStats : IProcessingStats +{ + private static readonly DefaultObjectPool _dataPool = new(new BlockDataPolicy(), 16); + private readonly Action _executeFromThreadPool; + + public event EventHandler? NewProcessingStatistics; + + private readonly IStateReader _stateReader; + private readonly ILogger _logger; + private readonly Stopwatch _runStopwatch = new(); + private readonly Lock _reportLock = new(); + + // Start values captured before processing + private long _startOpCodes; + private long _startSLoadOps; + private long _startSStoreOps; + private long _startCallOps; + private long _startEmptyCalls; + private long _startCachedContractsUsed; + private long _startContractsAnalyzed; + private long _startCreateOps; + private long _startSelfDestructOps; + + // Chunk accumulators (reset after each report) + private double _chunkMGas; + private long _chunkProcessingMicroseconds; + private long _chunkTx; + private long _chunkBlocks; + private long _opCodes; + private long _callOps; + private long _emptyCalls; + private long _sLoadOps; + private long _sStoreOps; + private long _selfDestructOps; + private long _createOps; + private long _contractsAnalyzed; + private long _cachedContractsUsed; + + // Timing + private long _lastElapsedRunningMicroseconds; + private long _lastReportMs; + + // Stylus tracking + private long _lastStylusCalls; + private long _lastStylusExecutionMicroseconds; + + // ANSI color codes + private const string ResetColor = "\u001b[37m"; + private const string WhiteText = "\u001b[97m"; + private const string YellowText = "\u001b[93m"; + private const string OrangeText = "\u001b[38;5;208m"; + private const string RedText = "\u001b[38;5;196m"; + private const string GreenText = "\u001b[92m"; + private const string DarkGreenText = "\u001b[32m"; + private const string DarkCyanText = "\u001b[36m"; + private const string BlueText = "\u001b[94m"; + private const string MagentaText = "\u001b[95m"; + + // Threshold for showing splash emoji (stylus calls > 20% of total EVM calls) + private const double StylusSignificanceThreshold = 0.2; + + public ArbitrumProcessingStats(IStateReader stateReader, ILogManager logManager) + { + _executeFromThreadPool = ExecuteFromThreadPool; + _stateReader = stateReader; + _logger = logManager.GetClassLogger(); + +#if DEBUG + _logger.SetDebugMode(); +#endif + } + + public void Start() + { + if (!_runStopwatch.IsRunning) + { + _lastReportMs = Environment.TickCount64; + _runStopwatch.Start(); + } + } + + public void CaptureStartStats() + { + _startSLoadOps = EvmMetrics.ThreadLocalSLoadOpcode; + _startSStoreOps = EvmMetrics.ThreadLocalSStoreOpcode; + _startCallOps = EvmMetrics.ThreadLocalCalls; + _startEmptyCalls = EvmMetrics.ThreadLocalEmptyCalls; + _startContractsAnalyzed = EvmMetrics.ThreadLocalContractsAnalysed; + _startCachedContractsUsed = EvmMetrics.GetThreadLocalCodeDbCache(); + _startCreateOps = EvmMetrics.ThreadLocalCreates; + _startSelfDestructOps = EvmMetrics.ThreadLocalSelfDestructs; + _startOpCodes = EvmMetrics.ThreadLocalOpCodes; + } + + public void UpdateStats(Block? block, BlockHeader? baseBlock, long blockProcessingTimeInMicros) + { + if (block is null) + return; + + BlockData blockData = _dataPool.Get(); + blockData.Block = block; + blockData.BaseBlock = baseBlock; + blockData.RunningMicroseconds = _runStopwatch.ElapsedMicroseconds(); + blockData.RunMicroseconds = _runStopwatch.ElapsedMicroseconds() - _lastElapsedRunningMicroseconds; + blockData.StartOpCodes = _startOpCodes; + blockData.StartSLoadOps = _startSLoadOps; + blockData.StartSStoreOps = _startSStoreOps; + blockData.StartCallOps = _startCallOps; + blockData.StartEmptyCalls = _startEmptyCalls; + blockData.StartContractsAnalyzed = _startContractsAnalyzed; + blockData.StartCachedContractsUsed = _startCachedContractsUsed; + blockData.StartCreateOps = _startCreateOps; + blockData.StartSelfDestructOps = _startSelfDestructOps; + blockData.ProcessingMicroseconds = blockProcessingTimeInMicros; + blockData.CurrentOpCodes = EvmMetrics.ThreadLocalOpCodes; + blockData.CurrentSLoadOps = EvmMetrics.ThreadLocalSLoadOpcode; + blockData.CurrentSStoreOps = EvmMetrics.ThreadLocalSStoreOpcode; + blockData.CurrentCallOps = EvmMetrics.ThreadLocalCalls; + blockData.CurrentEmptyCalls = EvmMetrics.ThreadLocalEmptyCalls; + blockData.CurrentContractsAnalyzed = EvmMetrics.ThreadLocalContractsAnalysed; + blockData.CurrentCachedContractsUsed = EvmMetrics.GetThreadLocalCodeDbCache(); + blockData.CurrentCreatesOps = EvmMetrics.ThreadLocalCreates; + blockData.CurrentSelfDestructOps = EvmMetrics.ThreadLocalSelfDestructs; + + ThreadPool.UnsafeQueueUserWorkItem(_executeFromThreadPool, blockData, preferLocal: false); + } + + private void ExecuteFromThreadPool(BlockData data) + { + try + { + lock (_reportLock) + { + GenerateReport(data); + } + } + catch (Exception ex) + { + if (_logger.IsError) + _logger.Error("Error when generating processing statistics", ex); + } + finally + { + _dataPool.Return(data); + } + } + + private void GenerateReport(BlockData data) + { + Block? block = data.Block; + if (block is null) + return; + + long blockNumber = block.Number; + double chunkMGas = (_chunkMGas += block.GasUsed / 1_000_000.0); + + // Update Prometheus metrics + double mgas = block.GasUsed / 1_000_000.0; + double timeSec = data.ProcessingMicroseconds / 1_000_000.0; + BlockchainMetrics.BlockMGasPerSec.Observe(mgas / timeSec); + BlockchainMetrics.BlockProcessingTimeMicros.Observe(data.ProcessingMicroseconds); + + BlockchainMetrics.Mgas += block.GasUsed / 1_000_000.0; + Transaction[] txs = block.Transactions; + double chunkMicroseconds = (_chunkProcessingMicroseconds += data.ProcessingMicroseconds); + double chunkTx = (_chunkTx += txs.Length); + + long chunkBlocks = ++_chunkBlocks; + + BlockchainMetrics.Blocks = blockNumber; + BlockchainMetrics.BlockchainHeight = blockNumber; + BlockchainMetrics.Transactions += txs.Length; + BlockchainMetrics.TotalDifficulty = block.TotalDifficulty ?? UInt256.Zero; + BlockchainMetrics.LastDifficulty = block.Difficulty; + BlockchainMetrics.GasUsed = block.GasUsed; + BlockchainMetrics.GasLimit = block.GasLimit; + + // Accumulate EVM operation counts + long chunkOpCodes = (_opCodes += data.CurrentOpCodes - data.StartOpCodes); + long chunkCalls = (_callOps += data.CurrentCallOps - data.StartCallOps); + long chunkEmptyCalls = (_emptyCalls += data.CurrentEmptyCalls - data.StartEmptyCalls); + long chunkSload = (_sLoadOps += data.CurrentSLoadOps - data.StartSLoadOps); + long chunkSstore = (_sStoreOps += data.CurrentSStoreOps - data.StartSStoreOps); + long chunkSelfDestructs = (_selfDestructOps += data.CurrentSelfDestructOps - data.StartSelfDestructOps); + long chunkCreates = (_createOps += data.CurrentCreatesOps - data.StartCreateOps); + long contractsAnalysed = (_contractsAnalyzed += data.CurrentContractsAnalyzed - data.StartContractsAnalyzed); + long cachedContractsUsed = (_cachedContractsUsed += data.CurrentCachedContractsUsed - data.StartCachedContractsUsed); + + // Skip logging during init/genesis when state isn't fully available + if (data.BaseBlock is null || !_stateReader.HasStateForBlock(data.BaseBlock) || + block.StateRoot is null || !_stateReader.HasStateForBlock(block.Header)) + { + return; + } + + // Throttle logging to once per second (or always in debug mode) + long reportMs = Environment.TickCount64; + if (reportMs - _lastReportMs <= 1000 && !_logger.IsDebug) + { + return; + } + _lastReportMs = reportMs; + + // Capture Stylus metrics before resetting + long currentStylusCalls = ArbitrumMetrics.StylusCalls; + long currentStylusMicros = ArbitrumMetrics.StylusExecutionMicroseconds; + + long stylusCallsDelta = currentStylusCalls - _lastStylusCalls; + long stylusMicrosDelta = currentStylusMicros - _lastStylusExecutionMicroseconds; + + _lastStylusCalls = currentStylusCalls; + _lastStylusExecutionMicroseconds = currentStylusMicros; + + // Reset chunk accumulators + _chunkBlocks = 0; + _chunkMGas = 0; + _chunkTx = 0; + _chunkProcessingMicroseconds = 0; + _opCodes = 0; + _callOps = 0; + _emptyCalls = 0; + _sLoadOps = 0; + _sStoreOps = 0; + _selfDestructOps = 0; + _createOps = 0; + _contractsAnalyzed = 0; + _cachedContractsUsed = 0; + + // Calculate throughput metrics + double mgasPerSecond = chunkMicroseconds == 0 ? -1 : chunkMGas / chunkMicroseconds * 1_000_000.0; + if (chunkMicroseconds != 0 && chunkMGas != 0) + { + BlockchainMetrics.MgasPerSec = mgasPerSecond; + } + + double txps = chunkMicroseconds == 0 ? -1 : chunkTx / chunkMicroseconds * 1_000_000.0; + double bps = chunkMicroseconds == 0 ? -1 : chunkBlocks / chunkMicroseconds * 1_000_000.0; + double chunkMs = chunkMicroseconds == 0 ? -1 : chunkMicroseconds / 1000.0; + double runMs = data.RunMicroseconds == 0 ? -1 : data.RunMicroseconds / 1000.0; + + // Get gas prices via public method + var gasPrices = EvmMetrics.GetBlockGasPrices(); + + // Fire statistics event for monitoring consumers + NewProcessingStatistics?.Invoke(this, new BlockStatistics + { + BlockCount = chunkBlocks, + BlockFrom = block.Number - chunkBlocks + 1, + BlockTo = block.Number, + ProcessingMs = chunkMs, + SlotMs = runMs, + MGasPerSecond = mgasPerSecond, + MinGas = gasPrices?.Min ?? 0, + MedianGas = gasPrices?.EstMedian ?? 0, + AveGas = gasPrices?.Ave ?? 0, + MaxGas = gasPrices?.Max ?? 0, + GasLimit = block.GasLimit + }); + + _lastElapsedRunningMicroseconds = data.RunningMicroseconds; + + if (!_logger.IsInfo) + return; + + string gasPrice = gasPrices is { } g + ? $"⛽ Gas gwei: {g.Min:N3} .. {WhiteText}{System.Math.Max(g.Min, g.EstMedian):N3}{ResetColor} ({g.Ave:N3}) .. {g.Max:N3}" + : ""; + + if (chunkBlocks > 1) + { + _logger.Info($"Processed {block.Number - chunkBlocks + 1,10}...{block.Number,9} | {chunkMs,10:N1} ms | elapsed {runMs,15:N0} ms | {gasPrice}"); + } + else + { + string chunkColor = chunkMs switch + { + < 200 => GreenText, + < 300 => DarkGreenText, + < 500 => WhiteText, + < 1000 => YellowText, + < 2000 => OrangeText, + _ => RedText + }; + _logger.Info($"Processed {block.Number,10} | {chunkColor}{chunkMs,10:N1}{ResetColor} ms | elapsed {runMs,15:N0} ms | {gasPrice}"); + } + + // Log block details + string mgasPerSecondColor = (mgasPerSecond / (block.GasLimit / 1_000_000.0)) switch + { + > 3 => GreenText, + > 2.5f => DarkGreenText, + > 2 => WhiteText, + > 1.5f => ResetColor, + > 1 => YellowText, + > 0.5f => OrangeText, + _ => RedText + }; + string sstoreColor = chunkBlocks > 1 ? "" : chunkSstore switch + { + > 3500 => RedText, + > 2500 => OrangeText, + > 2000 => YellowText, + > 1500 => WhiteText, + > 900 when chunkCalls > 900 => WhiteText, + _ => "" + }; + string callsColor = chunkBlocks > 1 ? "" : chunkCalls switch + { + > 3500 => RedText, + > 2500 => OrangeText, + > 2000 => YellowText, + > 1500 => WhiteText, + > 900 when chunkSstore > 900 => WhiteText, + _ => "" + }; + string createsColor = chunkBlocks > 1 ? "" : chunkCreates switch + { + > 300 => RedText, + > 200 => OrangeText, + > 150 => YellowText, + > 75 => WhiteText, + _ => "" + }; + + // Build Stylus section for Block line + double stylusMs = stylusCallsDelta > 0 ? stylusMicrosDelta / 1000.0 : 0; + bool isSignificant = stylusCallsDelta > 0 && (chunkCalls == 0 || stylusCallsDelta > chunkCalls * StylusSignificanceThreshold); + string splash = isSignificant ? " 🌊" : " "; // space+emoji OR 3 spaces for alignment + string stylusSection = $" | {MagentaText}🦀 stylus {stylusCallsDelta,3:N0}{ResetColor} ({stylusMs,5:F1}ms){splash}"; + + _logger.Info($" Block{(chunkBlocks > 1 ? $"s x{chunkBlocks,-9:N0} " : " ")}{(chunkBlocks == 1 ? (chunkMGas / (block.GasLimit / 16_000_000.0)) switch { > 15 => RedText, > 14 => OrangeText, > 13 => YellowText, > 10 => DarkGreenText, > 7 => GreenText, > 6 => DarkGreenText, > 5 => WhiteText, > 4 => ResetColor, > 3 => DarkCyanText, _ => BlueText } : "")} {chunkMGas,8:F2}{ResetColor} MGas | {chunkTx,8:N0} txs{stylusSection} | calls {callsColor}{chunkCalls,10:N0}{ResetColor} ({chunkEmptyCalls,3:N0}) | sload {chunkSload,7:N0} | sstore {sstoreColor}{chunkSstore,6:N0}{ResetColor} | create {createsColor}{chunkCreates,3:N0}{ResetColor}{(chunkSelfDestructs > 0 ? $"({-chunkSelfDestructs,3:N0})" : "")}"); + + // Log throughput + long recoveryQueue = BlockchainMetrics.RecoveryQueueSize; + long processingQueue = BlockchainMetrics.ProcessingQueueSize; + string blocksPerSec = $" {bps,14:F2} Blk/s "; + + if (recoveryQueue > 0 || processingQueue > 0) + { + _logger.Info($" Block throughput {mgasPerSecondColor}{mgasPerSecond,11:F2}{ResetColor} MGas/s{(mgasPerSecond > 1000 ? "🔥" : " ")}| {txps,10:N1} tps |{blocksPerSec}| recover {recoveryQueue,5:N0} | process {processingQueue,5:N0} | ops {chunkOpCodes,9:N0}"); + } + else + { + _logger.Info($" Block throughput {mgasPerSecondColor}{mgasPerSecond,11:F2}{ResetColor} MGas/s{(mgasPerSecond > 1000 ? "🔥" : " ")}| {txps,10:N1} tps |{blocksPerSec}| exec code{ResetColor} cache {cachedContractsUsed,6:N0} |{ResetColor} new {contractsAnalysed,9:N0} | ops {chunkOpCodes,9:N0}"); + } + } + + private class BlockDataPolicy : IPooledObjectPolicy + { + public BlockData Create() => new BlockData(); + public bool Return(BlockData data) + { + data.Block = null; + data.BaseBlock = null; + return true; + } + } + + private class BlockData + { + public Block? Block; + public BlockHeader? BaseBlock; + public long CurrentOpCodes; + public long CurrentSLoadOps; + public long CurrentSStoreOps; + public long CurrentCallOps; + public long CurrentEmptyCalls; + public long CurrentContractsAnalyzed; + public long CurrentCachedContractsUsed; + public long CurrentCreatesOps; + public long CurrentSelfDestructOps; + public long ProcessingMicroseconds; + public long RunningMicroseconds; + public long RunMicroseconds; + public long StartOpCodes; + public long StartSelfDestructOps; + public long StartCreateOps; + public long StartContractsAnalyzed; + public long StartCachedContractsUsed; + public long StartEmptyCalls; + public long StartCallOps; + public long StartSStoreOps; + public long StartSLoadOps; + } +}