diff --git a/src/Nethermind/Nethermind.Blockchain/ReceiptCanonicalityMonitor.cs b/src/Nethermind/Nethermind.Blockchain/ReceiptCanonicalityMonitor.cs
index 4d6b55283d95..eea17931dabe 100644
--- a/src/Nethermind/Nethermind.Blockchain/ReceiptCanonicalityMonitor.cs
+++ b/src/Nethermind/Nethermind.Blockchain/ReceiptCanonicalityMonitor.cs
@@ -25,7 +25,7 @@ public ReceiptCanonicalityMonitor(IReceiptStorage? receiptStorage, ILogManager?
{
_receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage));
_logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager));
- _receiptStorage.ReceiptsInserted += OnBlockAddedToMain;
+ _receiptStorage.NewCanonicalReceipts += OnBlockAddedToMain;
}
private void OnBlockAddedToMain(object sender, BlockReplacementEventArgs e)
@@ -55,7 +55,7 @@ private void TriggerReceiptInsertedEvent(Block newBlock, Block? previousBlock)
public void Dispose()
{
- _receiptStorage.ReceiptsInserted -= OnBlockAddedToMain;
+ _receiptStorage.NewCanonicalReceipts -= OnBlockAddedToMain;
}
}
}
diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptStorage.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptStorage.cs
index 65633a1fa7ad..dd7caa7366b7 100644
--- a/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptStorage.cs
+++ b/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptStorage.cs
@@ -19,8 +19,16 @@ public interface IReceiptStorage : IReceiptFinder
void RemoveReceipts(Block block);
///
- /// Receipts for a block are inserted
+ /// Receipts for canonical chain changed.
///
- event EventHandler ReceiptsInserted;
+ event EventHandler? NewCanonicalReceipts;
+
+ ///
+ /// Receipts for any block are inserted.
+ ///
+ ///
+ /// This is invoked for both canonical and non-canonical blocks.
+ ///
+ event EventHandler? ReceiptsInserted;
}
}
diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/InMemoryReceiptStorage.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/InMemoryReceiptStorage.cs
index 1e3eec074a49..a8824cb66a4c 100644
--- a/src/Nethermind/Nethermind.Blockchain/Receipts/InMemoryReceiptStorage.cs
+++ b/src/Nethermind/Nethermind.Blockchain/Receipts/InMemoryReceiptStorage.cs
@@ -18,7 +18,8 @@ public class InMemoryReceiptStorage : IReceiptStorage
private readonly ConcurrentDictionary _transactions = new();
#pragma warning disable CS0067
- public event EventHandler ReceiptsInserted;
+ public event EventHandler? NewCanonicalReceipts;
+ public event EventHandler? ReceiptsInserted;
#pragma warning restore CS0067
public InMemoryReceiptStorage(bool allowReceiptIterator = true, IBlockTree? blockTree = null)
@@ -32,7 +33,7 @@ public InMemoryReceiptStorage(bool allowReceiptIterator = true, IBlockTree? bloc
private void BlockTree_BlockAddedToMain(object? sender, BlockReplacementEventArgs e)
{
EnsureCanonical(e.Block);
- ReceiptsInserted?.Invoke(this, e);
+ NewCanonicalReceipts?.Invoke(this, e);
}
public Hash256 FindBlockHash(Hash256 txHash)
@@ -73,6 +74,8 @@ public void Insert(Block block, TxReceipt[] txReceipts, IReleaseSpec spec, bool
{
EnsureCanonical(block);
}
+
+ ReceiptsInserted?.Invoke(this, new(block.Header, txReceipts));
}
public bool HasBlock(long blockNumber, Hash256 hash)
diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/NullReceiptStorage.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/NullReceiptStorage.cs
index 1c06f8b3d21e..7ed7bb5637ae 100644
--- a/src/Nethermind/Nethermind.Blockchain/Receipts/NullReceiptStorage.cs
+++ b/src/Nethermind/Nethermind.Blockchain/Receipts/NullReceiptStorage.cs
@@ -13,7 +13,8 @@ public class NullReceiptStorage : IReceiptStorage
public static NullReceiptStorage Instance { get; } = new();
#pragma warning disable CS0067
- public event EventHandler ReceiptsInserted;
+ public event EventHandler? NewCanonicalReceipts;
+ public event EventHandler? ReceiptsInserted;
#pragma warning restore CS0067
public Hash256? FindBlockHash(Hash256 hash) => null;
diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs
index 5d5715278943..5f2cbd740052 100644
--- a/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs
+++ b/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs
@@ -38,7 +38,8 @@ public class PersistentReceiptStorage : IReceiptStorage
private const int CacheSize = 64;
private readonly LruCache _receiptsCache = new(CacheSize, CacheSize, "receipts");
- public event EventHandler ReceiptsInserted;
+ public event EventHandler? NewCanonicalReceipts;
+ public event EventHandler? ReceiptsInserted;
public PersistentReceiptStorage(
IColumnsDb receiptsDb,
@@ -73,7 +74,7 @@ public PersistentReceiptStorage(
private void BlockTreeOnBlockAddedToMain(object? sender, BlockReplacementEventArgs e)
{
EnsureCanonical(e.Block);
- ReceiptsInserted?.Invoke(this, e);
+ NewCanonicalReceipts?.Invoke(this, e);
// Dont block main loop
Task.Run(() =>
@@ -288,6 +289,8 @@ public void Insert(Block block, TxReceipt[]? txReceipts, IReleaseSpec spec, bool
{
EnsureCanonical(block, lastBlockNumber);
}
+
+ ReceiptsInserted?.Invoke(this, new(block.Header, txReceipts));
}
public long MigratedBlockNumber
diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/RandomExtensions.cs b/src/Nethermind/Nethermind.Core.Test/Builders/RandomExtensions.cs
new file mode 100644
index 000000000000..17135ff14fa3
--- /dev/null
+++ b/src/Nethermind/Nethermind.Core.Test/Builders/RandomExtensions.cs
@@ -0,0 +1,15 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Collections.Generic;
+using Nethermind.Crypto;
+
+namespace Nethermind.Core.Test.Builders;
+
+public static class RandomExtensions
+{
+ public static T NextFrom(this Random random, IReadOnlyList values) => values[random.Next(values.Count)];
+
+ public static T NextFrom(this ICryptoRandom random, IReadOnlyList values) => values[random.NextInt(values.Count)];
+}
diff --git a/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs
index d1413676fa33..5888c30c65e1 100644
--- a/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs
+++ b/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs
@@ -8,6 +8,8 @@
using System.Text.Json;
using FluentAssertions;
using Nethermind.Core.Extensions;
+using Nethermind.Core.Test.Builders;
+using Nethermind.Crypto;
using Nethermind.Serialization.Json;
using NUnit.Framework;
@@ -294,6 +296,22 @@ public void Fuzz_RandomHex_SegmentationInvariant()
}
}
+ [Test]
+ public void Test_DictionaryKey()
+ {
+ var random = new CryptoRandom();
+ var dictionary = new Dictionary
+ {
+ { Bytes.FromHexString("0x0"), null },
+ { Bytes.FromHexString("0x1"), random.NextInt(int.MaxValue) },
+ { Build.An.Address.TestObject.Bytes, random.NextInt(int.MaxValue) },
+ { random.GenerateRandomBytes(10), random.NextInt(int.MaxValue) },
+ { random.GenerateRandomBytes(32), random.NextInt(int.MaxValue) },
+ };
+
+ TestConverter(dictionary, new ByteArrayConverter());
+ }
+
private static ReadOnlySequence JsonForLiteral(string literal) =>
MakeSequence(Encoding.UTF8.GetBytes(literal));
diff --git a/src/Nethermind/Nethermind.Core.Test/Json/ConverterTestBase.cs b/src/Nethermind/Nethermind.Core.Test/Json/ConverterTestBase.cs
index 7f38126cda19..8725b3750dba 100644
--- a/src/Nethermind/Nethermind.Core.Test/Json/ConverterTestBase.cs
+++ b/src/Nethermind/Nethermind.Core.Test/Json/ConverterTestBase.cs
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-3.0-only
using System;
+using System.Collections;
using System.Text.Json;
using System.Text.Json.Serialization;
using NUnit.Framework;
@@ -10,7 +11,10 @@ namespace Nethermind.Core.Test.Json;
public class ConverterTestBase
{
- protected void TestConverter(T? item, Func equalityComparer, JsonConverter converter)
+ protected void TestConverter(T? item, Func equalityComparer, JsonConverter converter) =>
+ TestConverter(item, converter, equalityComparer);
+
+ protected void TestConverter(TItem? item, JsonConverter converter, Func? equalityComparer = null)
{
var options = new JsonSerializerOptions
{
@@ -22,10 +26,15 @@ protected void TestConverter(T? item, Func equalityComparer, JsonCon
string result = JsonSerializer.Serialize(item, options);
- T? deserialized = JsonSerializer.Deserialize(result, options);
+ TItem? deserialized = JsonSerializer.Deserialize(result, options);
#pragma warning disable CS8604
- Assert.That(equalityComparer(item, deserialized), Is.True);
+ if (equalityComparer is not null)
+ Assert.That(equalityComparer(item, deserialized), Is.True);
+ else if (item is IEnumerable itemE && deserialized is IEnumerable deserializedE)
+ Assert.That(deserializedE, Is.EquivalentTo(itemE));
+ else
+ Assert.That(deserialized, Is.EqualTo(item));
#pragma warning restore CS8604
}
}
diff --git a/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs b/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs
index e036bc568d54..ddb32f7f8bf4 100644
--- a/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs
+++ b/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only
+using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
@@ -13,4 +14,22 @@ public static void Increment(this Dictionary dictionary, TKey k
ref int res = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out bool _);
res++;
}
+
+ public static TValue GetOrAdd(this Dictionary dictionary,
+ TKey key, Func factory,
+ out bool exists)
+ where TKey : notnull
+ {
+ ref TValue? existing = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out exists);
+
+ if (!exists)
+ existing = factory(key);
+
+ return existing!;
+ }
+
+ public static TValue GetOrAdd(this Dictionary dictionary,
+ TKey key, Func factory)
+ where TKey : notnull =>
+ GetOrAdd(dictionary, key, factory, out _);
}
diff --git a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs
index 128268d8243b..e6a5f23545bf 100644
--- a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs
+++ b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs
@@ -262,7 +262,7 @@ public static byte[] PadLeft(this ReadOnlySpan bytes, int length, byte pad
return result;
}
- public static byte[] PadRight(this byte[] bytes, int length)
+ public static byte[] PadRight(this byte[] bytes, int length, byte padding = 0)
{
if (bytes.Length == length)
{
@@ -276,6 +276,12 @@ public static byte[] PadRight(this byte[] bytes, int length)
byte[] result = new byte[length];
Buffer.BlockCopy(bytes, 0, result, 0, bytes.Length);
+
+ if (padding != 0)
+ {
+ result.AsSpan(bytes.Length, length - bytes.Length).Fill(padding);
+ }
+
return result;
}
diff --git a/src/Nethermind/Nethermind.Core/Extensions/SizeExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/SizeExtensions.cs
index ba773225fb46..2ae4af43e7e8 100644
--- a/src/Nethermind/Nethermind.Core/Extensions/SizeExtensions.cs
+++ b/src/Nethermind/Nethermind.Core/Extensions/SizeExtensions.cs
@@ -67,7 +67,7 @@ public static long KiB(this int @this)
return ((long)@this).KiB();
}
- public static string SizeToString(this long @this, bool useSi = false, int precision = 1)
+ public static string SizeToString(this long @this, bool useSi = false, bool addSpace = false, int precision = 1)
{
string[] suf = useSi ? ["B", "KB", "MB", "GB", "TB"] : ["B", "KiB", "MiB", "GiB", "TiB"];
if (@this == 0)
@@ -77,7 +77,7 @@ public static string SizeToString(this long @this, bool useSi = false, int preci
long bytes = Math.Abs(@this);
int place = Math.Min(suf.Length - 1, Convert.ToInt32(Math.Floor(Math.Log(bytes, useSi ? 1000 : 1024))));
double num = Math.Round(bytes / Math.Pow(useSi ? 1000 : 1024, place), precision);
- return (Math.Sign(@this) * num).ToString() + suf[place];
+ return string.Concat(Math.Sign(@this) * num, addSpace ? " " : "", suf[place]);
}
}
}
diff --git a/src/Nethermind/Nethermind.Core/Extensions/WaitHandleExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/WaitHandleExtensions.cs
index fd0616dca645..5090c78856f1 100644
--- a/src/Nethermind/Nethermind.Core/Extensions/WaitHandleExtensions.cs
+++ b/src/Nethermind/Nethermind.Core/Extensions/WaitHandleExtensions.cs
@@ -23,8 +23,12 @@ public static async Task WaitOneAsync(this WaitHandle handle, int millisec
millisecondsTimeout,
true);
tokenRegistration = cancellationToken.Register(
- static state => ((TaskCompletionSource)state!).TrySetCanceled(),
- tcs);
+ static state =>
+ {
+ var (tcs, ct) = ((TaskCompletionSource tcs, CancellationToken ct))state!;
+ tcs.TrySetCanceled(ct);
+ },
+ (tcs, cancellationToken));
return await tcs.Task;
}
diff --git a/src/Nethermind/Nethermind.Core/FakeWriteBatch.cs b/src/Nethermind/Nethermind.Core/FakeWriteBatch.cs
index ce27831eb399..1fcf153998a1 100644
--- a/src/Nethermind/Nethermind.Core/FakeWriteBatch.cs
+++ b/src/Nethermind/Nethermind.Core/FakeWriteBatch.cs
@@ -31,5 +31,12 @@ public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteF
{
_storePretendingToSupportBatches.Set(key, value, flags);
}
+
+ public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None)
+ {
+ throw new NotSupportedException("Merging is not supported by this implementation.");
+ }
+
+ public void Clear() { }
}
}
diff --git a/src/Nethermind/Nethermind.Core/IKeyValueStore.cs b/src/Nethermind/Nethermind.Core/IKeyValueStore.cs
index e613308267ed..52a6b97b317c 100644
--- a/src/Nethermind/Nethermind.Core/IKeyValueStore.cs
+++ b/src/Nethermind/Nethermind.Core/IKeyValueStore.cs
@@ -69,6 +69,11 @@ public interface IWriteOnlyKeyValueStore
void Remove(ReadOnlySpan key) => Set(key, null);
}
+ public interface IMergeableKeyValueStore : IWriteOnlyKeyValueStore
+ {
+ void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None);
+ }
+
public interface ISortedKeyValueStore : IKeyValueStore
{
byte[]? FirstKey { get; }
@@ -82,6 +87,7 @@ public interface ISortedKeyValueStore : IKeyValueStore
///
public interface ISortedView : IDisposable
{
+ public bool StartBefore(ReadOnlySpan value);
public bool MoveNext();
public ReadOnlySpan CurrentKey { get; }
public ReadOnlySpan CurrentValue { get; }
diff --git a/src/Nethermind/Nethermind.Core/IWriteBatch.cs b/src/Nethermind/Nethermind.Core/IWriteBatch.cs
index 909f184541c0..852dc132caa9 100644
--- a/src/Nethermind/Nethermind.Core/IWriteBatch.cs
+++ b/src/Nethermind/Nethermind.Core/IWriteBatch.cs
@@ -5,5 +5,8 @@
namespace Nethermind.Core
{
- public interface IWriteBatch : IDisposable, IWriteOnlyKeyValueStore;
+ public interface IWriteBatch : IDisposable, IWriteOnlyKeyValueStore, IMergeableKeyValueStore
+ {
+ void Clear();
+ }
}
diff --git a/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs b/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs
index 664fbb1eccda..27b535d096f2 100644
--- a/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs
+++ b/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs
@@ -250,4 +250,19 @@ public static void Convert(
if (array is not null)
ArrayPool.Shared.Return(array);
}
+
+ public override byte[] ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ byte[]? result = Convert(ref reader);
+
+ if (result is null)
+ ThrowInvalidOperationException();
+
+ return result;
+ }
+
+ public override void WriteAsPropertyName(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options)
+ {
+ Convert(writer, value, static (w, h) => w.WritePropertyName(h), skipLeadingZeros: false, addQuotations: false, addHexPrefix: true);
+ }
}
diff --git a/src/Nethermind/Nethermind.Core/ProgressLogger.cs b/src/Nethermind/Nethermind.Core/ProgressLogger.cs
index f674106dab0d..589bb60f90c4 100644
--- a/src/Nethermind/Nethermind.Core/ProgressLogger.cs
+++ b/src/Nethermind/Nethermind.Core/ProgressLogger.cs
@@ -47,9 +47,9 @@ public void IncrementSkipped(int skipped = 1)
Interlocked.Add(ref _skipped, skipped);
}
- public void SetMeasuringPoint()
+ public void SetMeasuringPoint(bool resetCompletion = true)
{
- UtcEndTime = null;
+ if (resetCompletion) UtcEndTime = null;
if (UtcStartTime is not null)
{
@@ -169,7 +169,7 @@ public void LogProgress()
_lastReportState = reportState;
_logger.Info(reportString);
}
- SetMeasuringPoint();
+ SetMeasuringPoint(resetCompletion: false);
}
private string DefaultFormatter()
diff --git a/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs b/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs
index 9ee5829dc20f..a6c80ed33fc7 100644
--- a/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs
+++ b/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs
@@ -40,6 +40,18 @@ public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags
ReturnWriteBatch(currentBatch);
}
+ public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None)
+ {
+ IWriteBatch currentBatch = RentWriteBatch();
+ currentBatch.Merge(key, value, flags);
+ ReturnWriteBatch(currentBatch);
+ }
+
+ public void Clear()
+ {
+ throw new NotSupportedException($"{nameof(ConcurrentWriteBatcher)} can not be cancelled.");
+ }
+
public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None)
{
IWriteBatch currentBatch = RentWriteBatch();
diff --git a/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs b/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs
index ece6bb682979..f1271ebe84e2 100644
--- a/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs
+++ b/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs
@@ -11,7 +11,7 @@
namespace Nethermind.Db.Rocks;
-public class ColumnDb : IDb, ISortedKeyValueStore
+public class ColumnDb : IDb, ISortedKeyValueStore, IMergeableKeyValueStore
{
private readonly RocksDb _rocksDb;
internal readonly DbOnTheRocks _mainDb;
@@ -57,6 +57,11 @@ public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags
_mainDb.SetWithColumnFamily(key, _columnFamily, value, writeFlags);
}
+ public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None)
+ {
+ _mainDb.MergeWithColumnFamily(key, _columnFamily, value, writeFlags);
+ }
+
public KeyValuePair[] this[byte[][] keys] =>
_rocksDb.MultiGet(keys, keys.Select(k => _columnFamily).ToArray());
@@ -99,6 +104,11 @@ public void Dispose()
_underlyingWriteBatch.Dispose();
}
+ public void Clear()
+ {
+ _underlyingWriteBatch.Clear();
+ }
+
public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None)
{
if (value is null)
@@ -115,6 +125,11 @@ public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags
{
_underlyingWriteBatch.Set(key, value, _columnDb._columnFamily, flags);
}
+
+ public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None)
+ {
+ _underlyingWriteBatch.Merge(key, value, _columnDb._columnFamily, flags);
+ }
}
public void Remove(ReadOnlySpan key)
@@ -130,7 +145,7 @@ public bool KeyExists(ReadOnlySpan key)
public void Flush(bool onlyWal)
{
- _mainDb.Flush(onlyWal);
+ _mainDb.FlushWithColumnFamily(_columnFamily);
}
public void Compact()
diff --git a/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs b/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs
index 02300a2d7e62..d5a240d1e968 100644
--- a/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs
+++ b/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs
@@ -61,9 +61,9 @@ private static IReadOnlyList GetEnumKeys(IReadOnlyList keys)
return keys;
}
- protected override void BuildOptions(IRocksDbConfig dbConfig, Options options, IntPtr? sharedCache)
+ protected override void BuildOptions(IRocksDbConfig dbConfig, Options options, IntPtr? sharedCache, IMergeOperator? mergeOperator)
{
- base.BuildOptions(dbConfig, options, sharedCache);
+ base.BuildOptions(dbConfig, options, sharedCache, mergeOperator);
options.SetCreateMissingColumnFamilies();
}
@@ -130,9 +130,19 @@ public void Dispose()
_writeBatch.Dispose();
}
+ public void Clear()
+ {
+ _writeBatch._writeBatch.Clear();
+ }
+
public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None)
{
_writeBatch._writeBatch.Set(key, value, _column._columnFamily, flags);
}
+
+ public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None)
+ {
+ _writeBatch._writeBatch.Merge(key, value, _column._columnFamily, flags);
+ }
}
}
diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/PerTableDbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/PerTableDbConfig.cs
index 6524901e3578..0efa1fe7553d 100644
--- a/src/Nethermind/Nethermind.Db.Rocks/Config/PerTableDbConfig.cs
+++ b/src/Nethermind/Nethermind.Db.Rocks/Config/PerTableDbConfig.cs
@@ -38,7 +38,7 @@ private void EnsureConfigIsAvailable(string propertyName)
foreach (var prefix in _prefixes)
{
string prefixed = string.Concat(prefix, propertyName);
- if (type.GetProperty(prefixed, BindingFlags.Public | BindingFlags.Instance) is null)
+ if (GetProperty(type, prefixed) is null)
{
throw new InvalidConfigurationException($"Configuration {propertyName} not available with prefix {prefix}. Add {prefix}{propertyName} to {nameof(IDbConfig)}.", -1);
}
@@ -90,13 +90,13 @@ private static string ReadRocksdbOptions(IDbConfig dbConfig, string propertyName
Type type = dbConfig.GetType();
PropertyInfo? propertyInfo;
- string val = (string)type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance)!.GetValue(dbConfig)!;
+ string val = (string)GetProperty(type, propertyName)!.GetValue(dbConfig)!;
foreach (var prefix in prefixes)
{
string prefixed = string.Concat(prefix, propertyName);
- propertyInfo = type.GetProperty(prefixed, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
+ propertyInfo = GetProperty(type, prefixed);
if (propertyInfo is not null)
{
string? valObj = (string?)propertyInfo.GetValue(dbConfig);
@@ -122,7 +122,7 @@ private static string ReadRocksdbOptions(IDbConfig dbConfig, string propertyName
{
string prefixed = string.Concat(prefix, propertyName);
- propertyInfo = type.GetProperty(prefixed, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
+ propertyInfo = GetProperty(type, prefixed);
if (propertyInfo is not null)
{
if (propertyInfo.PropertyType.CanBeAssignedNull())
@@ -147,7 +147,7 @@ private static string ReadRocksdbOptions(IDbConfig dbConfig, string propertyName
}
// Use generic one even if its available
- propertyInfo = type.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
+ propertyInfo = GetProperty(type, propertyName);
return (T?)propertyInfo?.GetValue(dbConfig);
}
catch (Exception e)
@@ -156,4 +156,6 @@ private static string ReadRocksdbOptions(IDbConfig dbConfig, string propertyName
}
}
+ private static PropertyInfo? GetProperty(Type type, string name) =>
+ type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
}
diff --git a/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs b/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs
index 474ae0d50da1..a69d62475f0c 100644
--- a/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs
+++ b/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs
@@ -29,7 +29,7 @@
namespace Nethermind.Db.Rocks;
-public partial class DbOnTheRocks : IDb, ITunableDb, IReadOnlyNativeKeyValueStore, ISortedKeyValueStore
+public partial class DbOnTheRocks : IDb, ITunableDb, IReadOnlyNativeKeyValueStore, ISortedKeyValueStore, IMergeableKeyValueStore
{
protected ILogger _logger;
@@ -66,6 +66,10 @@ public partial class DbOnTheRocks : IDb, ITunableDb, IReadOnlyNativeKeyValueStor
private readonly DbSettings _settings;
+ // Need to keep options from GC in case of merge operator applied, as they are used in callback
+ // ReSharper disable once CollectionNeverQueried.Local
+ private readonly List _doNotGcOptions = [];
+
private readonly IRocksDbConfig _perTableDbConfig;
private ulong _maxBytesForLevelBase;
private ulong _targetFileSizeBase;
@@ -143,7 +147,7 @@ private RocksDb Init(string basePath, string dbPath, IDbConfig dbConfig, ILogMan
// ReSharper disable once VirtualMemberCallInConstructor
if (_logger.IsDebug) _logger.Debug($"Building options for {Name} DB");
DbOptions = new DbOptions();
- BuildOptions(_perTableDbConfig, DbOptions, sharedCache);
+ BuildOptions(_perTableDbConfig, DbOptions, sharedCache, _settings.MergeOperator);
ColumnFamilies? columnFamilies = null;
if (columnNames is not null)
@@ -155,7 +159,8 @@ private RocksDb Init(string basePath, string dbPath, IDbConfig dbConfig, ILogMan
ColumnFamilyOptions options = new();
IRocksDbConfig columnConfig = _rocksDbConfigFactory.GetForDatabase(Name, columnFamily);
- BuildOptions(columnConfig, options, sharedCache);
+ IMergeOperator? mergeOperator = _settings.ColumnsMergeOperators?.GetValueOrDefault(enumColumnName);
+ BuildOptions(columnConfig, options, sharedCache, mergeOperator);
// "default" is a special column name with rocksdb, which is what previously not specifying column goes to
if (columnFamily == "Default") columnFamily = "default";
@@ -447,7 +452,7 @@ public static IDictionary ExtractOptions(string dbOptions)
return asDict;
}
- protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options options, IntPtr? sharedCache) where T : Options
+ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options options, IntPtr? sharedCache, IMergeOperator? mergeOperator) where T : Options
{
// This section is about the table factory.. and block cache apparently.
// This effect the format of the SST files and usually require resync to take effect.
@@ -577,6 +582,13 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio
}
}
+ if (mergeOperator is not null)
+ {
+ options.SetMergeOperator(new MergeOperatorAdapter(mergeOperator));
+ lock (_doNotGcOptions)
+ _doNotGcOptions.Add(options);
+ }
+
#endregion
#region read-write options
@@ -893,6 +905,40 @@ public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags
SetWithColumnFamily(key, null, value, writeFlags);
}
+ public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None)
+ {
+ ObjectDisposedException.ThrowIf(_isDisposing, this);
+
+ UpdateWriteMetrics();
+
+ try
+ {
+ _db.Merge(key, value, null, WriteFlagsToWriteOptions(flags));
+ }
+ catch (RocksDbSharpException e)
+ {
+ CreateMarkerIfCorrupt(e);
+ throw;
+ }
+ }
+
+ internal void MergeWithColumnFamily(ReadOnlySpan key, ColumnFamilyHandle? cf, ReadOnlySpan value, WriteFlags flags = WriteFlags.None)
+ {
+ ObjectDisposedException.ThrowIf(_isDisposing, this);
+
+ UpdateWriteMetrics();
+
+ try
+ {
+ _db.Merge(key, value, cf, WriteFlagsToWriteOptions(flags));
+ }
+ catch (RocksDbSharpException e)
+ {
+ CreateMarkerIfCorrupt(e);
+ throw;
+ }
+ }
+
public void DangerousReleaseMemory(in ReadOnlySpan span)
{
if (!span.IsNullOrEmpty())
@@ -1222,6 +1268,13 @@ private static void ReturnWriteBatch(WriteBatch batch)
_reusableWriteBatch = batch;
}
+ public void Clear()
+ {
+ ObjectDisposedException.ThrowIf(_dbOnTheRocks._isDisposed, _dbOnTheRocks);
+
+ _rocksBatch.Clear();
+ }
+
public void Dispose()
{
ObjectDisposedException.ThrowIf(_dbOnTheRocks._isDisposed, _dbOnTheRocks);
@@ -1280,6 +1333,21 @@ public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags
Set(key, value, null, flags);
}
+ public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None)
+ {
+ Merge(key, value, null, flags);
+ }
+
+ public void Merge(ReadOnlySpan key, ReadOnlySpan value, ColumnFamilyHandle? cf = null, WriteFlags flags = WriteFlags.None)
+ {
+ ObjectDisposedException.ThrowIf(_isDisposed, this);
+
+ _rocksBatch.Merge(key, value, cf);
+ _writeFlags = flags;
+
+ if ((flags & WriteFlags.DisableWAL) != 0) FlushOnTooManyWrites();
+ }
+
private void FlushOnTooManyWrites()
{
if (Interlocked.Increment(ref _writeCount) % MaxWritesOnNoWal != 0) return;
@@ -1306,6 +1374,13 @@ public void Flush(bool onlyWal = false)
InnerFlush(onlyWal);
}
+ public void FlushWithColumnFamily(ColumnFamilyHandle familyHandle)
+ {
+ ObjectDisposedException.ThrowIf(_isDisposing, this);
+
+ InnerFlush(familyHandle);
+ }
+
public virtual void Compact()
{
_db.CompactRange(Keccak.Zero.BytesToArray(), Keccak.MaxValue.BytesToArray());
@@ -1328,6 +1403,18 @@ private void InnerFlush(bool onlyWal)
}
}
+ private void InnerFlush(ColumnFamilyHandle columnFamilyHandle)
+ {
+ try
+ {
+ _rocksDbNative.rocksdb_flush_cf(_db.Handle, FlushOptions.DefaultFlushOptions.Handle, columnFamilyHandle.Handle);
+ }
+ catch (RocksDbSharpException e)
+ {
+ CreateMarkerIfCorrupt(e);
+ }
+ }
+
public void Clear()
{
Dispose();
@@ -1776,10 +1863,10 @@ private sealed class ManagedIterators : ThreadLocal
public void ClearIterators()
{
if (_disposed) return;
- if (Values is null) return;
- foreach (IteratorHolder iterator in Values)
+ if (Values is not { } values) return;
+ foreach (IteratorHolder iterator in values)
{
- iterator.Dispose();
+ iterator?.Dispose();
}
}
diff --git a/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs b/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs
new file mode 100644
index 000000000000..14bf9efc98f8
--- /dev/null
+++ b/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs
@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Nethermind.Core.Collections;
+using RocksDbSharp;
+
+namespace Nethermind.Db.Rocks;
+
+// Also see RocksDbSharp.MergeOperatorImpl
+internal class MergeOperatorAdapter(IMergeOperator inner) : MergeOperator
+{
+ public string Name => inner.Name;
+
+ // TODO: fix and return array ptr instead of copying to unmanaged memory?
+ private static unsafe IntPtr GetResult(ArrayPoolList? data, out IntPtr resultLength, out IntPtr success)
+ {
+ if (data is null)
+ {
+ success = Convert.ToInt32(false);
+ resultLength = IntPtr.Zero;
+ return IntPtr.Zero;
+ }
+
+ using (data)
+ {
+ void* resultPtr = NativeMemory.Alloc((uint)data.Count);
+ var result = new Span(resultPtr, data.Count);
+ data.AsSpan().CopyTo(result);
+
+ resultLength = result.Length;
+
+ // Fixing RocksDbSharp invalid callback signature, TODO: submit an issue/PR
+ Unsafe.SkipInit(out success);
+ Unsafe.As(ref success) = 1;
+
+ return (IntPtr)resultPtr;
+ }
+ }
+
+ unsafe IntPtr MergeOperator.PartialMerge(
+ IntPtr keyPtr,
+ UIntPtr keyLength,
+ IntPtr operandsList,
+ IntPtr operandsListLength,
+ int numOperands,
+ out IntPtr successPtr,
+ out IntPtr resultLength)
+ {
+ var key = new Span((void*)keyPtr, (int)keyLength);
+ var enumerator = new RocksDbMergeEnumerator(new((void*)operandsList, numOperands), new((void*)operandsListLength, numOperands));
+
+ ArrayPoolList? result = inner.PartialMerge(key, enumerator);
+ return GetResult(result, out resultLength, out successPtr);
+ }
+
+ unsafe IntPtr MergeOperator.FullMerge(
+ IntPtr keyPtr,
+ UIntPtr keyLength,
+ IntPtr existingValuePtr,
+ UIntPtr existingValueLength,
+ IntPtr operandsList,
+ IntPtr operandsListLength,
+ int numOperands,
+ out IntPtr successPtr,
+ out IntPtr resultLength)
+ {
+ var key = new ReadOnlySpan((void*)keyPtr, (int)keyLength);
+ bool hasExistingValue = existingValuePtr != IntPtr.Zero;
+ Span existingValue = hasExistingValue ? new((void*)existingValuePtr, (int)existingValueLength) : Span.Empty;
+ var enumerator = new RocksDbMergeEnumerator(existingValue, hasExistingValue, new((void*)operandsList, numOperands), new((void*)operandsListLength, numOperands));
+
+ ArrayPoolList? result = inner.FullMerge(key, enumerator);
+ return GetResult(result, out resultLength, out successPtr);
+ }
+
+ unsafe void MergeOperator.DeleteValue(IntPtr value, UIntPtr valueLength) => NativeMemory.Free((void*)value);
+}
diff --git a/src/Nethermind/Nethermind.Db.Rocks/RocksdbSortedView.cs b/src/Nethermind/Nethermind.Db.Rocks/RocksdbSortedView.cs
index e617000d5320..a64fe85d5b49 100644
--- a/src/Nethermind/Nethermind.Db.Rocks/RocksdbSortedView.cs
+++ b/src/Nethermind/Nethermind.Db.Rocks/RocksdbSortedView.cs
@@ -22,6 +22,15 @@ public void Dispose()
_iterator.Dispose();
}
+ public bool StartBefore(ReadOnlySpan value)
+ {
+ if (_started)
+ throw new InvalidOperationException($"{nameof(StartBefore)} can only be called before starting iteration.");
+
+ _iterator.SeekForPrev(value);
+ return _started = _iterator.Valid();
+ }
+
public bool MoveNext()
{
if (!_started)
diff --git a/src/Nethermind/Nethermind.Db/CompressingDb.cs b/src/Nethermind/Nethermind.Db/CompressingDb.cs
index d8284f2049e9..a03e69ff1036 100644
--- a/src/Nethermind/Nethermind.Db/CompressingDb.cs
+++ b/src/Nethermind/Nethermind.Db/CompressingDb.cs
@@ -44,6 +44,8 @@ private class WriteBatch : IWriteBatch
public void Dispose() => _wrapped.Dispose();
+ public void Clear() => _wrapped.Clear();
+
public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None)
=> _wrapped.Set(key, Compress(value), flags);
@@ -52,6 +54,11 @@ public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags
_wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags);
}
+ public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None)
+ {
+ throw new InvalidOperationException("EOA compressing DB does not support merging");
+ }
+
public bool PreferWriteByArray => _wrapped.PreferWriteByArray;
public byte[]? this[ReadOnlySpan key]
diff --git a/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs b/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs
index fb2c98942716..f79c010c9889 100755
--- a/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs
+++ b/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs
@@ -103,6 +103,12 @@ private void Duplicate(IWriteOnlyKeyValueStore db, ReadOnlySpan key, ReadO
_updateDuplicateWriteMetrics?.Invoke();
}
+ private void DuplicateMerge(IMergeableKeyValueStore db, ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags)
+ {
+ db.Merge(key, value, flags);
+ _updateDuplicateWriteMetrics?.Invoke();
+ }
+
// we also need to duplicate writes that are in batches
public IWriteBatch StartWriteBatch() =>
_pruningContext is null
@@ -317,11 +323,23 @@ public void Dispose()
_clonedWriteBatch.Dispose();
}
+ public void Clear()
+ {
+ _writeBatch.Clear();
+ _clonedWriteBatch.Clear();
+ }
+
public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None)
{
_writeBatch.Set(key, value, flags);
_db.Duplicate(_clonedWriteBatch, key, value, flags);
}
+
+ public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None)
+ {
+ _writeBatch.Merge(key, value, flags);
+ _db.DuplicateMerge(_clonedWriteBatch, key, value, flags);
+ }
}
public void Tune(ITunableDb.TuneType type)
diff --git a/src/Nethermind/Nethermind.Db/IMergeOperator.cs b/src/Nethermind/Nethermind.Db/IMergeOperator.cs
new file mode 100644
index 000000000000..7df2946bd71f
--- /dev/null
+++ b/src/Nethermind/Nethermind.Db/IMergeOperator.cs
@@ -0,0 +1,14 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using Nethermind.Core.Collections;
+
+namespace Nethermind.Db;
+
+public interface IMergeOperator
+{
+ string Name { get; }
+ ArrayPoolList? FullMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator);
+ ArrayPoolList? PartialMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator);
+}
diff --git a/src/Nethermind/Nethermind.Db/InMemoryWriteBatch.cs b/src/Nethermind/Nethermind.Db/InMemoryWriteBatch.cs
index 7c43ec39039c..c8054ac8b6d2 100644
--- a/src/Nethermind/Nethermind.Db/InMemoryWriteBatch.cs
+++ b/src/Nethermind/Nethermind.Db/InMemoryWriteBatch.cs
@@ -29,10 +29,20 @@ public void Dispose()
GC.SuppressFinalize(this);
}
+ public void Clear()
+ {
+ _currentItems.Clear();
+ }
+
public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None)
{
_currentItems[key.ToArray()] = value;
_writeFlags = flags;
}
+
+ public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None)
+ {
+ throw new NotSupportedException("Merging is not supported by this implementation.");
+ }
}
}
diff --git a/src/Nethermind/Nethermind.Db/RocksDbMergeEnumerator.cs b/src/Nethermind/Nethermind.Db/RocksDbMergeEnumerator.cs
new file mode 100644
index 000000000000..fa2da2060b30
--- /dev/null
+++ b/src/Nethermind/Nethermind.Db/RocksDbMergeEnumerator.cs
@@ -0,0 +1,51 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+
+namespace Nethermind.Db;
+
+///
+/// RocksDB enumerator for values of merge operation.
+///
+// Interface was not used because of ref struct limitations.
+public readonly ref struct RocksDbMergeEnumerator(ReadOnlySpan operandsList, ReadOnlySpan operandsListLength)
+{
+ private readonly ReadOnlySpan _operandsList = operandsList;
+ private readonly ReadOnlySpan _operandsListLength = operandsListLength;
+
+ public Span ExistingValue { get; }
+ public bool HasExistingValue { get; }
+ public int OperandsCount => _operandsList.Length;
+ public int TotalCount => OperandsCount + (HasExistingValue ? 1 : 0);
+
+ public RocksDbMergeEnumerator(
+ Span existingValue, bool hasExistingValue,
+ ReadOnlySpan operandsList, ReadOnlySpan operandsListLength
+ ) : this(operandsList, operandsListLength)
+ {
+ ExistingValue = existingValue;
+ HasExistingValue = hasExistingValue;
+ }
+
+ public Span GetExistingValue()
+ {
+ return HasExistingValue ? ExistingValue : default;
+ }
+
+ public unsafe Span GetOperand(int index)
+ {
+ return new((void*)_operandsList[index], (int)_operandsListLength[index]);
+ }
+
+ public Span Get(int index)
+ {
+ if (index == 0 && HasExistingValue)
+ return ExistingValue;
+
+ if (HasExistingValue)
+ index -= 1;
+
+ return GetOperand(index);
+ }
+}
diff --git a/src/Nethermind/Nethermind.Db/RocksDbSettings.cs b/src/Nethermind/Nethermind.Db/RocksDbSettings.cs
index f3d9eebd4e67..d3ee2b073f39 100644
--- a/src/Nethermind/Nethermind.Db/RocksDbSettings.cs
+++ b/src/Nethermind/Nethermind.Db/RocksDbSettings.cs
@@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only
+using System;
+using System.Collections.Generic;
+
namespace Nethermind.Db
{
public class DbSettings
@@ -17,6 +20,9 @@ public DbSettings(string name, string path)
public bool DeleteOnStart { get; set; }
public bool CanDeleteFolder { get; set; } = true;
+ public IMergeOperator? MergeOperator { get; set; }
+ public Dictionary? ColumnsMergeOperators { get; set; }
+
public DbSettings Clone(string name, string path)
{
DbSettings settings = (DbSettings)MemberwiseClone();
diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs
index e05324b2d805..64c5f12b8676 100644
--- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs
+++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs
@@ -128,7 +128,7 @@ private JsonRpcResult GetBlockAddedToMainResult(BlockReplacementEventArgs blockR
}));
_blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs);
- _receiptStorage.ReceiptsInserted += Raise.EventWith(new object(), blockReplacementEventArgs);
+ _receiptStorage.NewCanonicalReceipts += Raise.EventWith(new object(), blockReplacementEventArgs);
manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(1000)).Should().Be(shouldReceiveResult);
subscriptionId = newHeadSubscription.Id;
@@ -148,7 +148,7 @@ private List GetLogsSubscriptionResult(Filter filter, BlockReplac
}));
_blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockEventArgs);
- _receiptStorage.ReceiptsInserted += Raise.EventWith(new object(), blockEventArgs);
+ _receiptStorage.NewCanonicalReceipts += Raise.EventWith(new object(), blockEventArgs);
semaphoreSlim.Wait(TimeSpan.FromMilliseconds(500));
subscriptionId = logsSubscription.Id;
@@ -769,7 +769,7 @@ public void LogsSubscription_should_not_send_logs_of_new_txs_on_ReceiptsInserted
BlockReplacementEventArgs blockEventArgs = new(block);
_blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockEventArgs);
- _receiptStorage.ReceiptsInserted += Raise.EventWith(new object(), blockEventArgs);
+ _receiptStorage.NewCanonicalReceipts += Raise.EventWith(new object(), blockEventArgs);
manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200));
jsonRpcResults.Count.Should().Be(1);
@@ -949,7 +949,7 @@ public async Task MultipleSubscriptions_concurrent_fast_messages(int messages)
{
BlockReplacementEventArgs eventArgs = new(Build.A.Block.TestObject);
blockTree.BlockAddedToMain += Raise.EventWith(eventArgs);
- _receiptStorage.ReceiptsInserted += Raise.EventWith(new object(), eventArgs);
+ _receiptStorage.NewCanonicalReceipts += Raise.EventWith(new object(), eventArgs);
}
});
@@ -1204,7 +1204,7 @@ public void LogsSubscription_can_send_logs_with_removed_txs_when_inserted()
}));
_blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockEventArgs);
- _receiptStorage.ReceiptsInserted += Raise.EventWith(new object(), blockEventArgs);
+ _receiptStorage.NewCanonicalReceipts += Raise.EventWith(new object(), blockEventArgs);
manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(2000));
jsonRpcResults.Count.Should().Be(1);
diff --git a/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/Migrations/ReceiptMigrationTests.cs b/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/Migrations/ReceiptMigrationTests.cs
index 02a695382dfd..6fb33121c458 100644
--- a/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/Migrations/ReceiptMigrationTests.cs
+++ b/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/Migrations/ReceiptMigrationTests.cs
@@ -160,7 +160,8 @@ public void RemoveReceipts(Block block)
}
#pragma warning disable CS0067
- public event EventHandler ReceiptsInserted;
+ public event EventHandler NewCanonicalReceipts;
+ public event EventHandler ReceiptsInserted;
#pragma warning restore CS0067
}
}
diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs
index f5267e2b0d22..ded8904a96ce 100644
--- a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs
+++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Nethermind.Core;
@@ -601,6 +602,17 @@ public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteF
{
_inBatched[key.ToArray()] = value;
}
+
+ public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None)
+ {
+ var keyArr = key.ToArray();
+ _inBatched[keyArr] = (_inBatched.GetValueOrDefault(keyArr) ?? []).Concat(value.ToArray()).ToArray();
+ }
+
+ public void Clear()
+ {
+ _inBatched.Clear();
+ }
}
}