diff --git a/src/Nethermind b/src/Nethermind index 8ee14cffe..aebd09c35 160000 --- a/src/Nethermind +++ b/src/Nethermind @@ -1 +1 @@ -Subproject commit 8ee14cffee727ccbf0bf730e2732e830bc16124e +Subproject commit aebd09c35f2d54a870d13ad93617aa36ed23a46e diff --git a/src/Nethermind.Arbitrum.Test/Infrastructure/FullChainSimulationChainSpecProvider.cs b/src/Nethermind.Arbitrum.Test/Infrastructure/FullChainSimulationChainSpecProvider.cs index 4dcfad684..ac4032a1e 100644 --- a/src/Nethermind.Arbitrum.Test/Infrastructure/FullChainSimulationChainSpecProvider.cs +++ b/src/Nethermind.Arbitrum.Test/Infrastructure/FullChainSimulationChainSpecProvider.cs @@ -10,9 +10,9 @@ namespace Nethermind.Arbitrum.Test.Infrastructure; public static class FullChainSimulationChainSpecProvider { - public static ChainSpec Create() + public static ChainSpec Create(ulong initialArbOsVersion = 32) { - var chainSpec = new ChainSpec + ChainSpec chainSpec = new() { Name = "Arbitrum Full Chain Simulation", DataDir = "arbitrum-local", @@ -65,7 +65,7 @@ public static ChainSpec Create() TerminalTotalDifficulty = UInt256.Parse("0x3c6568f12e8000"), Parameters = CreateChainParameters(), - EngineChainSpecParametersProvider = CreateEngineProvider(), + EngineChainSpecParametersProvider = CreateEngineProvider(initialArbOsVersion), Allocations = CreateAllocations() }; @@ -140,15 +140,17 @@ private static ChainParameters CreateChainParameters() Eip7002ContractAddress = new Address("0x00000961ef480eb55e80d19ad83579a64c007002"), Eip7251ContractAddress = new Address("0x0000bbddc7ce488642fb579f8b00f3a590007251"), Eip2935ContractAddress = new Address("0x0000f90827f1c53a10cb7a02335b175320002935"), + + Eip2935RingBufferSize = 393168 }; } - private static IChainSpecParametersProvider CreateEngineProvider() + private static IChainSpecParametersProvider CreateEngineProvider(ulong initialArbOsVersion = 32) { return new TestChainSpecParametersProvider(new ArbitrumChainSpecEngineParameters { Enabled = true, - InitialArbOSVersion = 32, + InitialArbOSVersion = initialArbOsVersion, InitialChainOwner = new Address("0x5E1497dD1f08C87b2d8FE23e9AAB6c1De833D927"), GenesisBlockNum = 0, EnableArbOS = true, diff --git a/src/Nethermind.Arbitrum.Test/Infrastructure/FullChainSimulationSpecProvider.cs b/src/Nethermind.Arbitrum.Test/Infrastructure/FullChainSimulationSpecProvider.cs index 370c8d354..ad9a4e8d4 100644 --- a/src/Nethermind.Arbitrum.Test/Infrastructure/FullChainSimulationSpecProvider.cs +++ b/src/Nethermind.Arbitrum.Test/Infrastructure/FullChainSimulationSpecProvider.cs @@ -42,6 +42,8 @@ public FullChainSimulationReleaseSpec() IsEip4844Enabled = false; // Disable blobs gas calculation IsEip4895Enabled = false; // Disable withdrawals IsEip3541Enabled = false; // Disable contract code validation + + Eip2935RingBufferSize = 393168; } /// diff --git a/src/Nethermind.Arbitrum.Test/Rpc/ArbitrumEthRpcModuleTests.cs b/src/Nethermind.Arbitrum.Test/Rpc/ArbitrumEthRpcModuleTests.cs index 66d20f571..d0785204b 100644 --- a/src/Nethermind.Arbitrum.Test/Rpc/ArbitrumEthRpcModuleTests.cs +++ b/src/Nethermind.Arbitrum.Test/Rpc/ArbitrumEthRpcModuleTests.cs @@ -3,10 +3,12 @@ using FluentAssertions; using Nethermind.Abi; +using Nethermind.Arbitrum.Arbos; using Nethermind.Arbitrum.Data; using Nethermind.Arbitrum.Test.Infrastructure; using Nethermind.Blockchain.Find; using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; @@ -15,6 +17,7 @@ using Nethermind.Int256; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Data; +using Nethermind.Specs.ChainSpecStyle; namespace Nethermind.Arbitrum.Test.Rpc; @@ -30,9 +33,10 @@ public class ArbitrumEthRpcModuleTests [SetUp] public void Setup() { - _chain = ArbitrumRpcTestBlockchain.CreateDefault(); + ChainSpec chainSpec = FullChainSimulationChainSpecProvider.Create(40); + _chain = ArbitrumRpcTestBlockchain.CreateDefault(null, chainSpec); - var initMessage = FullChainSimulationInitMessage.CreateDigestInitMessage(92); + DigestInitMessage initMessage = FullChainSimulationInitMessage.CreateDigestInitMessage(92, 40); _chain.ArbitrumRpcModule.DigestInitMessage(initMessage); _ethereumEcdsa = new EthereumEcdsa(_chain.SpecProvider.ChainId); @@ -275,6 +279,47 @@ public async Task EthEstimateGas_WhenInvalidCallData_ReturnsExecutionError() result.Result.ResultType.Should().Be(ResultType.Failure); } + [Test] + public async Task EthGetStorageAt_HistoricalBlockHashes_ReturnsCorrectHashesForAll300Blocks() + { + // Produce 310 blocks to exceed a test threshold + const int targetBlocks = 310; + for (int i = 0; i < targetBlocks; i++) + await ProduceBlockWithBaseFee(1.Wei()); + + // Get the current head block + Block? head = _chain.BlockTree.Head; + head.Should().NotBeNull(); + head!.Number.Should().BeGreaterThan(300); + + // Forward loop from block 0 to head, matching Nitro's test logic + // See: arbitrum-nitro/system_tests/historical_block_hash_test.go + for (ulong i = 0; i < (ulong)head.Number; i++) + { + // Calculate the storage index for this block number + UInt256 storageIndex = new(i % (ulong)_chain.ChainSpec.Parameters.Eip2935RingBufferSize); + + // Query history storage contract via RPC + ResultWrapper result = _chain.ArbitrumEthRpcModule.eth_getStorageAt( + Eip2935Constants.BlockHashHistoryAddress, + storageIndex, + BlockParameter.Latest); + + result.Result.ResultType.Should().Be(ResultType.Success, + $"Block {i}: storage query should succeed"); + + // Get expected block by number + Block? expectedBlock = _chain.BlockTree.FindBlock((long)i); + expectedBlock.Should().NotBeNull($"Block {i} should exist"); + + // Verify stored hash matches block i's hash + result.Data.Should().NotBeNullOrEmpty($"Block {i}: storage should contain hash"); + Hash256 storedHash = new(result.Data!); + storedHash.Should().Be(expectedBlock!.Hash!, + $"Block {i}: stored hash should match actual block hash"); + } + } + private async Task ProduceBlockWithBaseFee(UInt256 baseFee) { TestEthDeposit deposit = new( diff --git a/src/Nethermind.Arbitrum/Arbos/ArbosState.cs b/src/Nethermind.Arbitrum/Arbos/ArbosState.cs index 375a37cad..fe64488a7 100644 --- a/src/Nethermind.Arbitrum/Arbos/ArbosState.cs +++ b/src/Nethermind.Arbitrum/Arbos/ArbosState.cs @@ -173,6 +173,11 @@ public void UpgradeArbosVersion(ulong targetVersion, bool isFirstTime, IWorldSta break; case 40: // ArbosVersion_40 + // EIP-2935: Add support for historical block hashes + worldState.CreateAccountIfNotExists(Eip2935Constants.BlockHashHistoryAddress, UInt256.Zero, UInt256.One); + worldState.InsertCode(Eip2935Constants.BlockHashHistoryAddress, Precompiles.HistoryStorageCodeHash, + Precompiles.HistoryStorageCodeArbitrum, + genesisSpec, true); StylusParams stylusParamsV40 = Programs.GetParams(); stylusParamsV40.UpgradeToArbosVersion(nextArbosVersion); stylusParamsV40.Save(); diff --git a/src/Nethermind.Arbitrum/Arbos/Precompiles.cs b/src/Nethermind.Arbitrum/Arbos/Precompiles.cs index d94d2d03d..ec2c1c9a4 100644 --- a/src/Nethermind.Arbitrum/Arbos/Precompiles.cs +++ b/src/Nethermind.Arbitrum/Arbos/Precompiles.cs @@ -1,6 +1,7 @@ using System.Collections.Frozen; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Evm; namespace Nethermind.Arbitrum.Arbos; @@ -10,6 +11,18 @@ public static class Precompiles public static readonly byte[] InvalidCode = [(byte)Instruction.INVALID]; public static readonly Hash256 InvalidCodeHash = Keccak.Compute(InvalidCode); + // EIP-2935 - Serve historical block hashes from state (Arbitrum version) + // Differs from the original EIP-2935 in two aspects: + // 1. Buffer size is 393,168 blocks instead of 8191 + // 2. Uses arb_block_num (L2 block number) instead of number (L1 block number) + // See: https://github.com/OffchainLabs/sys-asm/blob/main/src/execution_hash/main.eas + // See: https://github.com/OffchainLabs/go-ethereum/blob/57fe4b732d4e640e696da40773f2dacba97e722b/params/protocol_params.go#L221 + public static readonly byte[] HistoryStorageCodeArbitrum = + Bytes.FromHexString( + "0x3373fffffffffffffffffffffffffffffffffffffffe1460605760203603605c575f3563a3b1b31d5f5260205f6004601c60645afa15605c575f51600181038211605c57816205ffd0910311605c576205ffd09006545f5260205ff35b5f5ffd5b5f356205ffd0600163a3b1b31d5f5260205f6004601c60645afa15605c575f5103065500"); + + public static readonly Hash256 HistoryStorageCodeHash = Keccak.Compute(HistoryStorageCodeArbitrum); + public static readonly IReadOnlyDictionary PrecompileMinArbOSVersions = new Dictionary { [ArbosAddresses.ArbosAddress] = 0, diff --git a/src/Nethermind.Arbitrum/Config/ArbitrumDynamicSpecProvider.cs b/src/Nethermind.Arbitrum/Config/ArbitrumDynamicSpecProvider.cs index 8bbbb18c1..c11ee4df0 100644 --- a/src/Nethermind.Arbitrum/Config/ArbitrumDynamicSpecProvider.cs +++ b/src/Nethermind.Arbitrum/Config/ArbitrumDynamicSpecProvider.cs @@ -54,6 +54,13 @@ private static void ApplyArbitrumOverrides(ReleaseSpec spec, ulong arbosVersion) spec.IsEip7702Enabled = pragueEnabled; spec.IsEip2537Enabled = pragueEnabled; + // EIP-2935: Historical block hash storage (ArbOS v40+) + // Arbitrum uses a larger ring buffer (393,168 blocks vs Ethereum's 8,191) + // This provides ~1 day of history at Arbitrum's 0.22s block time + bool eip2935Enabled = arbosVersion >= ArbosVersion.Forty; + spec.IsEip2935Enabled = eip2935Enabled; + spec.IsEip7709Enabled = eip2935Enabled; // BLOCKHASH opcode reads from state + // Disable contract code validation as Arbitrum stores Stylus bytecode spec.IsEip3541Enabled = false; } diff --git a/src/Nethermind.Arbitrum/Execution/ArbitrumTransactionProcessor.cs b/src/Nethermind.Arbitrum/Execution/ArbitrumTransactionProcessor.cs index fdefc9ef0..0faa27c21 100644 --- a/src/Nethermind.Arbitrum/Execution/ArbitrumTransactionProcessor.cs +++ b/src/Nethermind.Arbitrum/Execution/ArbitrumTransactionProcessor.cs @@ -12,7 +12,6 @@ using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Core.Eip2930; using Nethermind.Core.Specs; using Nethermind.Evm; using Nethermind.Evm.Tracing; @@ -419,7 +418,6 @@ private ArbitrumTransactionProcessorResult ProcessArbitrumInternalTransaction( if (_arbosState!.CurrentArbosVersion >= ArbosVersion.ParentBlockHashSupport) { - ProcessParentBlockHash(prevHash, _tracingInfo!.Tracer); } Dictionary callArguments = @@ -713,25 +711,6 @@ private ArbitrumTransactionProcessorResult ProcessArbitrumRetryTransaction( return new(true, TransactionResult.Ok); } - private void ProcessParentBlockHash(ValueHash256 prevHash, ITxTracer tracer) - { - AccessList.Builder builder = new AccessList.Builder() - .AddAddress(Eip2935Constants.BlockHashHistoryAddress); - - Transaction newTransaction = new() - { - SenderAddress = Address.SystemUser, - GasLimit = 30_000_000, - GasPrice = UInt256.Zero, - DecodedMaxFeePerGas = UInt256.Zero, - To = Eip2935Constants.BlockHashHistoryAddress, - AccessList = builder.Build(), - Data = prevHash.Bytes.ToArray() - }; - - base.Execute(newTransaction, tracer, ExecutionOptions.Commit); - } - private static void TryReapOneRetryable( ArbosState arbosState, ulong currentTimestamp, diff --git a/src/Nethermind.Arbitrum/Properties/chainspec/arbitrum-local.json b/src/Nethermind.Arbitrum/Properties/chainspec/arbitrum-local.json index b74e36d2a..3c8e4a0ec 100644 --- a/src/Nethermind.Arbitrum/Properties/chainspec/arbitrum-local.json +++ b/src/Nethermind.Arbitrum/Properties/chainspec/arbitrum-local.json @@ -59,6 +59,7 @@ "eip6780TransitionTimestamp": "0x65B97D60", "eip2537TransitionTimestamp": "0x67C7FD60", "eip2935TransitionTimestamp": "0x67C7FD60", + "eip2935RingBufferSize": "0x5FFD0", "eip7702TransitionTimestamp": "0x67C7FD60", "minHistoryRetentionEpochs": "0x0", "eip7934MaxRlpBlockSize": "0x0" diff --git a/src/Nethermind.Arbitrum/Properties/chainspec/arbitrum-sepolia.json b/src/Nethermind.Arbitrum/Properties/chainspec/arbitrum-sepolia.json index 365dc87f2..d745cbcde 100644 --- a/src/Nethermind.Arbitrum/Properties/chainspec/arbitrum-sepolia.json +++ b/src/Nethermind.Arbitrum/Properties/chainspec/arbitrum-sepolia.json @@ -58,6 +58,7 @@ "eip6780TransitionTimestamp": "0x65B97D60", "eip2537TransitionTimestamp": "0x67C7FD60", "eip2935TransitionTimestamp": "0x67C7FD60", + "eip2935RingBufferSize": "0x5FFD0", "eip7702TransitionTimestamp": "0x67C7FD60" }, "genesis": { diff --git a/src/Nethermind.Arbitrum/Properties/chainspec/arbitrum-system-test.json b/src/Nethermind.Arbitrum/Properties/chainspec/arbitrum-system-test.json index 186f88796..33ad6ca68 100644 --- a/src/Nethermind.Arbitrum/Properties/chainspec/arbitrum-system-test.json +++ b/src/Nethermind.Arbitrum/Properties/chainspec/arbitrum-system-test.json @@ -59,6 +59,7 @@ "eip6780TransitionTimestamp": "0x65B97D60", "eip2537TransitionTimestamp": "0x67C7FD60", "eip2935TransitionTimestamp": "0x67C7FD60", + "eip2935RingBufferSize": "0x5FFD0", "eip7702TransitionTimestamp": "0x67C7FD60", "minHistoryRetentionEpochs": "0x0", "eip7934MaxRlpBlockSize": "0x0" @@ -67,7 +68,7 @@ "seal": { "ethereum": { "nonce": "0x1", - "mixHash": "0x0000000000000000000000000000000028000000000000000000000000000000" + "mixHash": "0x0000000000000000000000000000000000000000000000280000000000000000" } }, "number": "0x0", @@ -82,10 +83,10 @@ "nodes": [], "accounts": { "26E554a8acF9003b83495c7f45F06edCB803d4e3": { - "balance": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7" + "balance": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7" }, "aF24Ca6c2831f4d4F629418b50C227DF0885613A": { - "balance": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7" + "balance": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7" } } }