From cda0325c429e1d0690f3cf5788e57390727eb422 Mon Sep 17 00:00:00 2001 From: Wi1l-B0t <201105916+Wi1l-B0t@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:13:40 +0800 Subject: [PATCH] Fix: The start is the inclusive max key if Backward scan --- .../LevelDBStore/IO/Data/LevelDB/Helper.cs | 2 +- .../LevelDBStore/Plugins/Storage/Snapshot.cs | 51 ++++------------- plugins/LevelDBStore/Plugins/Storage/Store.cs | 57 +++++-------------- .../RocksDBStore/Plugins/Storage/Snapshot.cs | 49 +++++----------- plugins/RocksDBStore/Plugins/Storage/Store.cs | 48 +++++----------- tests/Neo.Plugins.Storage.Tests/StoreTest.cs | 51 +++++++++++------ 6 files changed, 89 insertions(+), 169 deletions(-) diff --git a/plugins/LevelDBStore/IO/Data/LevelDB/Helper.cs b/plugins/LevelDBStore/IO/Data/LevelDB/Helper.cs index c79d2726a..d919939dc 100644 --- a/plugins/LevelDBStore/IO/Data/LevelDB/Helper.cs +++ b/plugins/LevelDBStore/IO/Data/LevelDB/Helper.cs @@ -16,7 +16,7 @@ namespace Neo.IO.Data.LevelDB; public static class Helper { - public static IEnumerable<(byte[], byte[])> Seek(this DB db, ReadOptions options, byte[]? keyOrPrefix, SeekDirection direction) + public static IEnumerable<(byte[] Key, byte[] Value)> Seek(this DB db, ReadOptions options, byte[]? keyOrPrefix, SeekDirection direction) { keyOrPrefix ??= []; diff --git a/plugins/LevelDBStore/Plugins/Storage/Snapshot.cs b/plugins/LevelDBStore/Plugins/Storage/Snapshot.cs index 6c39f1202..652b5a685 100644 --- a/plugins/LevelDBStore/Plugins/Storage/Snapshot.cs +++ b/plugins/LevelDBStore/Plugins/Storage/Snapshot.cs @@ -79,51 +79,22 @@ public void Dispose() ArgumentNullException.ThrowIfNull(start); ArgumentNullException.ThrowIfNull(end); - if (start.AsSpan().SequenceCompareTo(end) >= 0) - yield break; - - using var iterator = _db.CreateIterator(_readOptions); - - if (direction == SeekDirection.Forward) + var order = start.AsSpan().SequenceCompareTo(end); + if ((direction == SeekDirection.Forward && order >= 0) || + (direction == SeekDirection.Backward && order <= 0)) { - iterator.Seek(start); - - while (iterator.Valid()) - { - var key = iterator.Key(); - if (key is null) break; - - if (key.AsSpan().SequenceCompareTo(end) >= 0) - break; - - yield return (key, iterator.Value()!); - iterator.Next(); - } + yield break; } - else - { - iterator.Seek(end); - if (!iterator.Valid()) - { - iterator.SeekToLast(); - } - else - { - iterator.Prev(); - } - - while (iterator.Valid()) + foreach (var item in _db.Seek(_readOptions, start, direction)) + { + order = item.Key.AsSpan().SequenceCompareTo(end); + if ((direction == SeekDirection.Forward && order >= 0) || + (direction == SeekDirection.Backward && order <= 0)) { - var key = iterator.Key(); - if (key is null) break; - - if (key.AsSpan().SequenceCompareTo(start) < 0) - break; - - yield return (key, iterator.Value()!); - iterator.Prev(); + yield break; // end is the exclusive max key if forward, or the exclusive min key if backward } + yield return item; } } diff --git a/plugins/LevelDBStore/Plugins/Storage/Store.cs b/plugins/LevelDBStore/Plugins/Storage/Store.cs index 51e3a73d8..a72552f4e 100644 --- a/plugins/LevelDBStore/Plugins/Storage/Store.cs +++ b/plugins/LevelDBStore/Plugins/Storage/Store.cs @@ -83,57 +83,26 @@ public bool TryGet(byte[] key, [NotNullWhen(true)] out byte[]? value) ArgumentNullException.ThrowIfNull(start); ArgumentNullException.ThrowIfNull(end); - if (start.AsSpan().SequenceCompareTo(end) >= 0) - yield break; - - using var iterator = _db.CreateIterator(ReadOptions.Default); - - if (direction == SeekDirection.Forward) + var order = start.AsSpan().SequenceCompareTo(end); + if ((direction == SeekDirection.Forward && order >= 0) || + (direction == SeekDirection.Backward && order <= 0)) { - iterator.Seek(start); - - while (iterator.Valid()) - { - var key = iterator.Key(); - if (key is null) break; - - if (key.AsSpan().SequenceCompareTo(end) >= 0) - break; - - yield return (key, iterator.Value()!); - iterator.Next(); - } + yield break; } - else - { - iterator.Seek(end); - if (!iterator.Valid()) - { - iterator.SeekToLast(); - } - else - { - iterator.Prev(); - } - - while (iterator.Valid()) + foreach (var item in _db.Seek(ReadOptions.Default, start, direction)) + { + order = item.Key.AsSpan().SequenceCompareTo(end); + if ((direction == SeekDirection.Forward && order >= 0) || + (direction == SeekDirection.Backward && order <= 0)) { - var key = iterator.Key(); - if (key is null) break; - - if (key.AsSpan().SequenceCompareTo(start) < 0) - break; - - yield return (key, iterator.Value()!); - iterator.Prev(); + yield break; // end is the exclusive max key if forward, or the exclusive min key if backward } + yield return item; } } - public IEnumerator> GetEnumerator() => - _db.GetEnumerator(); + public IEnumerator> GetEnumerator() => _db.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => - GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/plugins/RocksDBStore/Plugins/Storage/Snapshot.cs b/plugins/RocksDBStore/Plugins/Storage/Snapshot.cs index 57686a5a4..af73b0892 100644 --- a/plugins/RocksDBStore/Plugins/Storage/Snapshot.cs +++ b/plugins/RocksDBStore/Plugins/Storage/Snapshot.cs @@ -62,15 +62,17 @@ public void Put(byte[] key, byte[] value) public IEnumerable<(byte[] Key, byte[] Value)> Find(byte[]? keyOrPrefix, SeekDirection direction) { keyOrPrefix ??= []; - using var it = _db.NewIterator(readOptions: _options); - if (direction == SeekDirection.Forward) + { for (it.Seek(keyOrPrefix); it.Valid(); it.Next()) yield return (it.Key(), it.Value()); + } else + { for (it.SeekForPrev(keyOrPrefix); it.Valid(); it.Prev()) yield return (it.Key(), it.Value()); + } } public IEnumerable<(byte[] Key, byte[] Value)> FindRange(byte[] start, byte[] end, SeekDirection direction = SeekDirection.Forward) @@ -78,43 +80,22 @@ public void Put(byte[] key, byte[] value) ArgumentNullException.ThrowIfNull(start); ArgumentNullException.ThrowIfNull(end); - if (start.AsSpan().SequenceCompareTo(end) >= 0) - yield break; - - using var it = _db.NewIterator(readOptions: _options); - - if (direction == SeekDirection.Forward) + var order = start.AsSpan().SequenceCompareTo(end); + if ((direction == SeekDirection.Forward && order >= 0) || + (direction == SeekDirection.Backward && order <= 0)) { - for (it.Seek(start); it.Valid(); it.Next()) - { - var key = it.Key(); - if (key.AsSpan().SequenceCompareTo(end) >= 0) - break; - - yield return (key, it.Value()); - } + yield break; } - else - { - it.Seek(end); - - if (!it.Valid()) - { - it.SeekToLast(); - } - else - { - it.Prev(); - } - for (; it.Valid(); it.Prev()) + foreach (var item in Find(start, direction)) + { + order = item.Key.AsSpan().SequenceCompareTo(end); + if ((direction == SeekDirection.Forward && order >= 0) || + (direction == SeekDirection.Backward && order <= 0)) { - var key = it.Key(); - if (key.AsSpan().SequenceCompareTo(start) < 0) - break; - - yield return (key, it.Value()); + yield break; // end is the exclusive max key if forward, or the exclusive min key if backward } + yield return item; } } diff --git a/plugins/RocksDBStore/Plugins/Storage/Store.cs b/plugins/RocksDBStore/Plugins/Storage/Store.cs index 5f22fcea0..91684ec16 100644 --- a/plugins/RocksDBStore/Plugins/Storage/Store.cs +++ b/plugins/RocksDBStore/Plugins/Storage/Store.cs @@ -43,14 +43,17 @@ public IStoreSnapshot GetSnapshot() public IEnumerable<(byte[] Key, byte[] Value)> Find(byte[]? keyOrPrefix, SeekDirection direction = SeekDirection.Forward) { keyOrPrefix ??= []; - using var it = _db.NewIterator(); if (direction == SeekDirection.Forward) + { for (it.Seek(keyOrPrefix); it.Valid(); it.Next()) yield return (it.Key(), it.Value()); + } else + { for (it.SeekForPrev(keyOrPrefix); it.Valid(); it.Prev()) yield return (it.Key(), it.Value()); + } } public IEnumerable<(byte[] Key, byte[] Value)> FindRange(byte[] start, byte[] end, SeekDirection direction = SeekDirection.Forward) @@ -58,43 +61,22 @@ public IStoreSnapshot GetSnapshot() ArgumentNullException.ThrowIfNull(start); ArgumentNullException.ThrowIfNull(end); - if (start.AsSpan().SequenceCompareTo(end) >= 0) - yield break; - - using var it = _db.NewIterator(); - - if (direction == SeekDirection.Forward) + var order = start.AsSpan().SequenceCompareTo(end); + if ((direction == SeekDirection.Forward && order >= 0) || + (direction == SeekDirection.Backward && order <= 0)) { - for (it.Seek(start); it.Valid(); it.Next()) - { - var key = it.Key(); - if (key.AsSpan().SequenceCompareTo(end) >= 0) - break; - - yield return (key, it.Value()); - } + yield break; } - else - { - it.Seek(end); - if (!it.Valid()) - { - it.SeekToLast(); - } - else - { - it.Prev(); - } - - for (; it.Valid(); it.Prev()) + foreach (var item in Find(start, direction)) + { + order = item.Key.AsSpan().SequenceCompareTo(end); + if ((direction == SeekDirection.Forward && order >= 0) || + (direction == SeekDirection.Backward && order <= 0)) { - var key = it.Key(); - if (key.AsSpan().SequenceCompareTo(start) < 0) - break; - - yield return (key, it.Value()); + yield break; // end is the exclusive max key if forward, or the exclusive min key if backward } + yield return item; } } diff --git a/tests/Neo.Plugins.Storage.Tests/StoreTest.cs b/tests/Neo.Plugins.Storage.Tests/StoreTest.cs index b6e28a0e8..10f4497ec 100644 --- a/tests/Neo.Plugins.Storage.Tests/StoreTest.cs +++ b/tests/Neo.Plugins.Storage.Tests/StoreTest.cs @@ -9,8 +9,6 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -#pragma warning disable CS0618 // Type or member is obsolete - using Neo.Persistence; using Neo.Persistence.Providers; @@ -48,16 +46,14 @@ public static void OnEnd() public void TestMemory() { using var store = new MemoryStore(); - // Test all with the same store + // Test all with the same store TestStorage(store); // Test with different storages - TestPersistence(store); // Test snapshot - TestSnapshot(store); TestMultiSnapshot(store); } @@ -68,15 +64,12 @@ public void TestLevelDb() using var store = s_levelDbStore.GetStore(Path_leveldb); // Test all with the same store - TestStorage(store); // Test with different storages - TestPersistence(store); // Test snapshot - TestSnapshot(store); TestMultiSnapshot(store); } @@ -87,15 +80,12 @@ public void TestRocksDb() using var store = s_rocksDBStore.GetStore(Path_rocksdb); // Test all with the same store - TestStorage(store); // Test with different storages - TestPersistence(store); // Test snapshot - TestSnapshot(store); TestMultiSnapshot(store); } @@ -183,7 +173,6 @@ private static void TestStorage(IStore store) Assert.IsFalse(store.Contains(key1)); // Test seek in order - store.Put([0x00, 0x00, 0x04], [0x04]); store.Put([0x00, 0x00, 0x00], [0x00]); store.Put([0x00, 0x00, 0x01], [0x01]); @@ -191,7 +180,6 @@ private static void TestStorage(IStore store) store.Put([0x00, 0x00, 0x03], [0x03]); // Seek Forward - var entries = store.Find([0x00, 0x00, 0x02], SeekDirection.Forward).ToArray(); Assert.HasCount(3, entries); CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x02 }, entries[0].Key); @@ -201,8 +189,40 @@ private static void TestStorage(IStore store) CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x04 }, entries[2].Key); CollectionAssert.AreEqual(new byte[] { 0x04 }, entries[2].Value); - // Seek Backward + // FindRange Forward + entries = store.FindRange([0x00, 0x00, 0x02], [0x00, 0x00, 0x04], SeekDirection.Forward).ToArray(); + Assert.HasCount(2, entries); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x02 }, entries[0].Key); + CollectionAssert.AreEqual(new byte[] { 0x02 }, entries[0].Value); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x03 }, entries[1].Key); + CollectionAssert.AreEqual(new byte[] { 0x03 }, entries[1].Value); + // FindRange Backward + entries = store.FindRange([0x00, 0x00, 0x02], [0x00, 0x00, 0x04], SeekDirection.Backward).ToArray(); + Assert.HasCount(0, entries); // start is the max key if backward, so no entries + + entries = store.FindRange([0x00, 0x00, 0x04], [0x00, 0x00, 0x02], SeekDirection.Backward).ToArray(); + Assert.HasCount(2, entries); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x04 }, entries[0].Key); + CollectionAssert.AreEqual(new byte[] { 0x04 }, entries[0].Value); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x03 }, entries[1].Key); + CollectionAssert.AreEqual(new byte[] { 0x03 }, entries[1].Value); + + // FindRange Backward with start is greater than the last key + entries = store.FindRange([0x00, 0x00, 0xFF], [0x00, 0x00, 0x03], SeekDirection.Backward).ToArray(); + Assert.HasCount(1, entries); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x04 }, entries[0].Key); + CollectionAssert.AreEqual(new byte[] { 0x04 }, entries[0].Value); + + // FindRange Forward with end is greater than the last key + entries = store.FindRange([0x00, 0x00, 0x03], [0x00, 0x00, 0xFF], SeekDirection.Forward).ToArray(); + Assert.HasCount(2, entries); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x03 }, entries[0].Key); + CollectionAssert.AreEqual(new byte[] { 0x03 }, entries[0].Value); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x04 }, entries[1].Key); + CollectionAssert.AreEqual(new byte[] { 0x04 }, entries[1].Value); + + // Seek Backward entries = store.Find([0x00, 0x00, 0x02], SeekDirection.Backward).ToArray(); Assert.HasCount(3, entries); CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x02 }, entries[0].Key); @@ -268,7 +288,6 @@ private static void TestStorage(IStore store) Assert.IsEmpty(entries); // Seek Backward - entries = snapshot.Find([0x00, 0x00, 0x02], SeekDirection.Backward).ToArray(); Assert.HasCount(2, entries); CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x01 }, entries[0].Key); @@ -333,5 +352,3 @@ private static void TestPersistence(IStore store) Assert.IsNull(retvalue); } } - -#pragma warning restore CS0618 // Type or member is obsolete