Skip to content

Commit 1e5ac84

Browse files
committed
Squashed commit of the following:
commit 5a6c779 Author: Amirul Ashraf <asdacap@gmail.com> Date: Tue Mar 24 09:46:36 2026 +0800 fix(flat): periodically clear ReadOnlySnapshotBundle cache (#10922) * fix(flat): periodically clear ReadOnlySnapshotBundle cache to prevent stale readers The snapshot bundle cache was only cleared on compaction/persistence events. If persistence stalled, old entries held RefCountingPersistenceReader leases indefinitely, preventing database compaction. Add a 15-second periodic timer to force-clear stale cache entries. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add unit test for periodic bundle cache clearing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: iterate ConcurrentDictionary directly instead of copying keys Address PR review feedback: use TryRemove while iterating the ConcurrentDictionary directly, avoiding the temporary key list copy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove IsEmpty check as it acquires all bucket locks Address review feedback: ConcurrentDictionary.IsEmpty acquires all bucket locks, making it more expensive than just iterating directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Ben {chmark} Adams <thundercat@illyriad.co.uk> commit d9e819e Author: Ben {chmark} Adams <thundercat@illyriad.co.uk> Date: Mon Mar 23 22:42:46 2026 +0000 test: update pyspec fixtures to v5.4.0/v5.5.1 and adapt to forked release layout (#10931) * Adapt pyspec fixtures to forked release layout * Fix Amsterdam SSTORE gas ordering * fix: add missing usings for TestItem and InsertCode extension in Eip8037 test * Restore prestate for invalid access-list state tests * fix: add missing usings and fix Ether extension in pre-Berlin access list test * hmm * fix: detect AccessList tx type by field presence, not list emptiness JsonToEthereumTest.Convert set TxType.AccessList only when the built access list was non-empty. Pyspec fixtures with empty accessLists: [[]] were misclassified as legacy txs, so pre-Berlin rejection didn't fire and the post-state root diverged. Check whether the accessLists/accessList JSON field was present rather than whether the parsed list has entries. Rebuild regression test programmatically using the expected hash from pyspec fixture test_eip2930_tx_validity[fork_Istanbul-invalid-state_test]. * test: add Convert regression test for empty accessLists field detection commit 057441c Author: Gaurav Dhiman <newmanifold000@gmail.com> Date: Tue Mar 24 04:02:29 2026 +0530 Fix buffer leak tests to use PooledBufferLeakDetector (#10887) commit ae8e0ee Author: Tomass <155266802+zeroprooff@users.noreply.github.com> Date: Mon Mar 23 22:32:03 2026 +0000 Remove duplicate assertion in SnapshotCompactorTests (#10923) Co-authored-by: Ben {chmark} Adams <thundercat@illyriad.co.uk> commit d4214dd Author: Marc <Marchhill@users.noreply.github.com> Date: Mon Mar 23 17:59:29 2026 +0000 Gnosis Osaka (#10906) osaka gnosis config Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> commit 4228cb3 Author: Alexey Osipov <me@flcl.me> Date: Mon Mar 23 20:50:00 2026 +0300 Dispose on exception (#10921) * Dispose more * Dispose in rare case * Catch more; cleanup --------- Co-authored-by: Ben {chmark} Adams <thundercat@illyriad.co.uk> commit 871e9c7 Author: Marc <Marchhill@users.noreply.github.com> Date: Mon Mar 23 16:20:47 2026 +0000 Fix AuRaMergeEngineModuleTests (#10872)
1 parent 6a25504 commit 1e5ac84

File tree

32 files changed

+471
-281
lines changed

32 files changed

+471
-281
lines changed

.agents/rules/test-infrastructure.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,8 @@ The rule: **if production modules already wire a component, use them — don't c
7171
- Add tests to existing test files rather than creating new ones
7272
- **Do not duplicate test methods that differ only in parameters** — use `[TestCase(...)]` or `[TestCaseSource(...)]` to parameterize a single method
7373
- Before writing a new test, check if an existing test can be extended with another `[TestCase]` or use `[TestCaseSource]`
74+
75+
## DotNetty `IByteBuffer` in tests
76+
77+
- Prefer `using DisposableByteBuffer` via `.AsDisposable()` for releasing `IByteBuffer` in tests
78+
- For leak-detection tests, use `PooledBufferLeakDetector` from `Nethermind.Network.Test`

src/Nethermind/Chains/gnosis.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@
103103
"eip7623TransitionTimestamp": "0x68122dbc",
104104
"eip7702TransitionTimestamp": "0x68122dbc",
105105
"eip4844FeeCollectorTransitionTimestamp": "0x68122dbc",
106+
"eip7594TransitionTimestamp": "0x69de2dbc",
107+
"eip7823TransitionTimestamp": "0x69de2dbc",
108+
"eip7825TransitionTimestamp": "0x69de2dbc",
109+
"eip7883TransitionTimestamp": "0x69de2dbc",
110+
"eip7918TransitionTimestamp": "0x69de2dbc",
111+
"eip7934TransitionTimestamp": "0x69de2dbc",
112+
"eip7939TransitionTimestamp": "0x69de2dbc",
113+
"eip7951TransitionTimestamp": "0x69de2dbc",
106114
"depositContractAddress": "0x0B98057eA310F4d31F2a452B414647007d1645d9",
107115
"blobSchedule": [
108116
{

src/Nethermind/Ethereum.Blockchain.Pyspec.Test/Amsterdam/AmsterdamTestFixture.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public static IEnumerable<BlockchainTest> LoadTests() =>
2626
{
2727
ArchiveVersion = Constants.BalArchiveVersion,
2828
ArchiveName = Constants.BalArchiveName
29-
}, "fixtures/blockchain_tests", typeof(TSelf).GetCustomAttribute<EipWildcardAttribute>()!.Wildcard).LoadTests<BlockchainTest>();
29+
}, "fixtures/blockchain_tests/for_amsterdam", typeof(TSelf).GetCustomAttribute<EipWildcardAttribute>()!.Wildcard).LoadTests<BlockchainTest>();
3030
}
3131

3232
/// <summary>
@@ -45,5 +45,5 @@ public static IEnumerable<GeneralStateTest> LoadTests() =>
4545
{
4646
ArchiveVersion = Constants.BalArchiveVersion,
4747
ArchiveName = Constants.BalArchiveName
48-
}, "fixtures/state_tests", typeof(TSelf).GetCustomAttribute<EipWildcardAttribute>()!.Wildcard).LoadTests<GeneralStateTest>();
48+
}, "fixtures/state_tests/for_amsterdam", typeof(TSelf).GetCustomAttribute<EipWildcardAttribute>()!.Wildcard).LoadTests<GeneralStateTest>();
4949
}

src/Nethermind/Ethereum.Blockchain.Pyspec.Test/Amsterdam/Constants.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ namespace Ethereum.Blockchain.Pyspec.Test.Amsterdam;
55

66
public static class Constants
77
{
8-
public const string BalArchiveVersion = "bal@v5.4.0";
8+
public const string BalArchiveVersion = "bal@v5.5.1";
99
public const string BalArchiveName = "fixtures_bal.tar.gz";
1010
}

src/Nethermind/Ethereum.Blockchain.Pyspec.Test/Constants.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ namespace Ethereum.Blockchain.Pyspec.Test;
66
public class Constants
77
{
88
public const string ARCHIVE_URL_TEMPLATE = "https://github.com/ethereum/execution-spec-tests/releases/download/{0}/{1}";
9-
public const string DEFAULT_ARCHIVE_VERSION = "v5.0.0";
9+
public const string DEFAULT_ARCHIVE_VERSION = "v5.4.0";
1010
public const string DEFAULT_ARCHIVE_NAME = "fixtures_develop.tar.gz";
1111
}

src/Nethermind/Ethereum.Blockchain.Pyspec.Test/LoadPyspecTestsStrategy.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,41 @@ public IEnumerable<EthereumTest> Load(string testsDir, string wildcard = null)
3030
}
3131

3232
IEnumerable<string> testDirs = !string.IsNullOrEmpty(testsDir)
33-
? Directory.EnumerateDirectories(Path.Combine(testsDirectoryName, testsDir), "*", new EnumerationOptions { RecurseSubdirectories = true })
33+
? Directory.EnumerateDirectories(ResolveTestsDirectory(testsDirectoryName, testsDir), "*", new EnumerationOptions { RecurseSubdirectories = true })
3434
: Directory.EnumerateDirectories(testsDirectoryName, "*", new EnumerationOptions { RecurseSubdirectories = true });
3535
return testDirs.SelectMany(td => TestLoadStrategy.LoadTestsFromDirectory(td, wildcard, testType));
3636
}
37+
38+
private static string ResolveTestsDirectory(string testsDirectoryName, string testsDir)
39+
{
40+
string requestedDirectory = Path.Combine(testsDirectoryName, testsDir);
41+
if (Directory.Exists(requestedDirectory))
42+
{
43+
return requestedDirectory;
44+
}
45+
46+
string[] parts = testsDir.Split([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar], StringSplitOptions.RemoveEmptyEntries);
47+
bool hadForkPrefix = false;
48+
for (int i = 0; i < parts.Length; i++)
49+
{
50+
if (!parts[i].StartsWith("for_", StringComparison.Ordinal))
51+
{
52+
continue;
53+
}
54+
55+
parts[i] = parts[i]["for_".Length..];
56+
hadForkPrefix = true;
57+
}
58+
59+
if (hadForkPrefix)
60+
{
61+
string legacyDirectory = Path.Combine(testsDirectoryName, Path.Combine(parts));
62+
if (Directory.Exists(legacyDirectory))
63+
{
64+
return legacyDirectory;
65+
}
66+
}
67+
68+
return requestedDirectory;
69+
}
3770
}

src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PyspecTestFixture.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public abstract class PyspecBlockchainTestFixture<TSelf> : BlockchainTestBase
2222

2323
public static IEnumerable<BlockchainTest> LoadTests() =>
2424
new TestsSourceLoader(new LoadPyspecTestsStrategy(),
25-
$"fixtures/blockchain_tests/{TestDirectoryHelper.GetDirectoryByConvention<TSelf>("BlockchainTests")}").LoadTests<BlockchainTest>();
25+
$"fixtures/blockchain_tests/for_{TestDirectoryHelper.GetDirectoryByConvention<TSelf>("BlockchainTests")}").LoadTests<BlockchainTest>();
2626
}
2727

2828
/// <summary>
@@ -38,5 +38,5 @@ public abstract class PyspecStateTestFixture<TSelf> : GeneralStateTestBase
3838

3939
public static IEnumerable<GeneralStateTest> LoadTests() =>
4040
new TestsSourceLoader(new LoadPyspecTestsStrategy(),
41-
$"fixtures/state_tests/{TestDirectoryHelper.GetDirectoryByConvention<TSelf>("StateTests")}").LoadTests<GeneralStateTest>();
41+
$"fixtures/state_tests/for_{TestDirectoryHelper.GetDirectoryByConvention<TSelf>("StateTests")}").LoadTests<GeneralStateTest>();
4242
}

src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer)
103103
stateProvider.RecalculateStateRoot();
104104
}
105105

106+
Snapshot preExecutionSnapshot = stateProvider.TakeSnapshot(newTransactionStart: true);
107+
106108
if (test.Transaction.ChainId is null)
107109
{
108110
test.Transaction.ChainId = test.ChainId;
@@ -180,7 +182,8 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer)
180182
}
181183
else
182184
{
183-
stateProvider.Reset();
185+
stateProvider.Restore(preExecutionSnapshot);
186+
stateProvider.RecalculateStateRoot();
184187
}
185188
}
186189

src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,16 +145,21 @@ public static Transaction Convert(PostStateJson postStateJson, TransactionJson t
145145
};
146146
transaction.Hash = transaction.CalculateHash();
147147

148+
bool hasAccessListField = transactionJson.AccessLists is not null || transactionJson.AccessList is not null;
148149
AccessList.Builder builder = new();
149150
ProcessAccessList(transactionJson.AccessLists is not null
150151
? transactionJson.AccessLists[postStateJson.Indexes.Data]
151152
: transactionJson.AccessList, builder);
152153
transaction.AccessList = builder.Build();
153154

154-
if (transaction.AccessList.AsEnumerable().Count() != 0)
155+
if (hasAccessListField)
156+
{
155157
transaction.Type = TxType.AccessList;
156-
else
158+
}
159+
else if (transaction.AccessList.IsEmpty)
160+
{
157161
transaction.AccessList = null;
162+
}
158163

159164
if (transactionJson.MaxFeePerGas is not null)
160165
{

src/Nethermind/Ethereum.Transaction.Test/TransactionJsonTest.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
using Ethereum.Test.Base;
55
using FluentAssertions;
66
using Nethermind.Core;
7+
using Nethermind.Core.Crypto;
8+
using Nethermind.Core.Eip2930;
79
using Nethermind.Core.Test.Builders;
810
using Nethermind.Int256;
911
using Nethermind.Serialization.Json;
12+
using Nethermind.Specs.Forks;
1013
using NUnit.Framework;
1114

1215
namespace Ethereum.Blockchain.Test;
@@ -35,4 +38,86 @@ public void Can_load_access_lists()
3538
Nethermind.Core.Transaction tx = JsonToEthereumTest.Convert(new PostStateJson { Indexes = new IndexesJson() }, txJson);
3639
tx.AccessList.Should().NotBeNull();
3740
}
41+
42+
[Test]
43+
public void Convert_sets_AccessList_type_when_accessLists_field_present_but_empty()
44+
{
45+
const string json =
46+
"""{"accessLists": [[]], "secretKey": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": ["0x00"], "gasLimit": ["0x0186a0"], "data": ["0x"]}""";
47+
48+
EthereumJsonSerializer serializer = new();
49+
TransactionJson txJson = serializer.Deserialize<TransactionJson>(json);
50+
51+
Nethermind.Core.Transaction tx = JsonToEthereumTest.Convert(new PostStateJson { Indexes = new IndexesJson() }, txJson);
52+
53+
tx.Type.Should().Be(TxType.AccessList,
54+
"presence of accessLists field (even empty) should set Type 1");
55+
}
56+
57+
/// <summary>
58+
/// An AccessList transaction with an empty access list sent against Istanbul (pre-Berlin)
59+
/// must be rejected. The post-state root must equal the pre-state root - the invalid tx
60+
/// should not mutate state.
61+
/// Expected hash from pyspec: test_eip2930_tx_validity[fork_Istanbul-invalid-state_test]
62+
/// </summary>
63+
[Test]
64+
public void Invalid_pre_berlin_access_list_tx_with_empty_list_preserves_prestate_root()
65+
{
66+
Address sender = new("0x1ad9bc24818784172ff393bb6f89f094d4d2ca29");
67+
Address recipient = new("0x67eb8fcbef83a0662b030f8bc89a10070c167a66");
68+
69+
Nethermind.Core.Transaction transaction = Build.A.Transaction
70+
.WithType(TxType.AccessList)
71+
.WithChainId(1)
72+
.WithAccessList(AccessList.Empty)
73+
.WithGasLimit(100_000)
74+
.WithGasPrice(10)
75+
.WithNonce(UInt256.Zero)
76+
.To(recipient)
77+
.WithValue(0)
78+
.SignedAndResolved(TestItem.PrivateKeyA)
79+
.TestObject;
80+
// Override sender to match the pyspec fixture key
81+
transaction.SenderAddress = sender;
82+
83+
GeneralStateTest test = new()
84+
{
85+
Name = nameof(Invalid_pre_berlin_access_list_tx_with_empty_list_preserves_prestate_root),
86+
Category = "state",
87+
Fork = Istanbul.Instance,
88+
ForkName = Istanbul.Instance.Name,
89+
CurrentCoinbase = new Address("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"),
90+
CurrentDifficulty = new UInt256(0x020000),
91+
CurrentGasLimit = 120_000_000,
92+
CurrentNumber = 1,
93+
CurrentTimestamp = 1000,
94+
PreviousHash = Keccak.Zero,
95+
Pre = new()
96+
{
97+
[recipient] = new()
98+
{
99+
Nonce = UInt256.One,
100+
Balance = UInt256.Zero,
101+
Code = [0x60, 0x01, 0x60, 0x00, 0x55], // PUSH1 1 PUSH1 0 SSTORE
102+
Storage = new() { [UInt256.Zero] = new UInt256(0xdeadbeef).ToBigEndian() }
103+
},
104+
[sender] = new()
105+
{
106+
Nonce = UInt256.Zero,
107+
Balance = UInt256.Parse("1000000000000000000000"),
108+
Code = [],
109+
Storage = new()
110+
}
111+
},
112+
// Expected post-state root from pyspec fixture (pre-state unchanged)
113+
PostHash = new Hash256("0x43c19943b2c4a638fe07dbc954c1422032ea7c5e17d0d659f25a5324ed75f0be"),
114+
Transaction = transaction,
115+
};
116+
117+
EthereumTestResult result = RunTest(test);
118+
119+
result.StateRoot.Should().Be(test.PostHash,
120+
"invalid AccessList tx on pre-Berlin fork should not mutate state");
121+
result.Pass.Should().BeTrue();
122+
}
38123
}

0 commit comments

Comments
 (0)