diff --git a/All.slnx b/All.slnx
index 7379cfb..d8655bc 100644
--- a/All.slnx
+++ b/All.slnx
@@ -6,6 +6,7 @@
+
@@ -13,6 +14,7 @@
+
diff --git a/src/Neo.Configuration/BlockchainStoreOptions.cs b/src/Neo.Configuration/BlockchainStoreOptions.cs
new file mode 100644
index 0000000..adaaf5d
--- /dev/null
+++ b/src/Neo.Configuration/BlockchainStoreOptions.cs
@@ -0,0 +1,35 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Neo.Configuration.Json;
+using System;
+using System.IO;
+
+namespace Neo.Configuration
+{
+ public class BlockchainStoreOptions : JsonModel
+ {
+ public string DatabasePath { get; init; } = Path.Combine(AppContext.BaseDirectory, "data", "chain");
+
+ public bool CreateIfMissing { get; init; } = true;
+ }
+}
diff --git a/src/Neo.Core/Extensions/ByteExtensions.cs b/src/Neo.Core/Extensions/ByteExtensions.cs
index bd7d01e..5264028 100644
--- a/src/Neo.Core/Extensions/ByteExtensions.cs
+++ b/src/Neo.Core/Extensions/ByteExtensions.cs
@@ -73,7 +73,7 @@ public static byte[] Xor(this byte[] x, byte[] y)
/// The byte array to be converted.
/// The offset into the byte array from which to begin using data.
/// The converted object.
- public static T? AsSerializable(this byte[] data, int startIndex = 0)
+ public static T AsSerializable(this byte[] data, int startIndex = 0)
where T : class?, INeoSerializable?
{
using var ms = new MemoryStream(data, false);
diff --git a/src/Neo.Core/Neo.Core.csproj b/src/Neo.Core/Neo.Core.csproj
index 884b9c9..e708933 100644
--- a/src/Neo.Core/Neo.Core.csproj
+++ b/src/Neo.Core/Neo.Core.csproj
@@ -7,9 +7,9 @@
-
-
-
+
+
+
diff --git a/src/Neo.IO/Hashing/Murmur128.cs b/src/Neo.IO/Hashing/Murmur128.cs
index cbdd7b6..52ae2fe 100644
--- a/src/Neo.IO/Hashing/Murmur128.cs
+++ b/src/Neo.IO/Hashing/Murmur128.cs
@@ -43,10 +43,8 @@ public class Murmur128 : NonCryptographicHashAlgorithm
private Murmur128(uint seed = DefaultSeed) : base(16)
{
- Reset();
-
_seed = seed;
- _h1 = _h2 = _seed;
+ Reset();
}
public static byte[] Hash(byte[] data, uint seed = DefaultSeed) =>
@@ -65,22 +63,22 @@ public static byte[] Hash(string text, uint seed = DefaultSeed)
return Hash(bytes, seed);
}
- public static ulong HashToUInt64(byte[] data, uint seed = DefaultSeed) =>
- HashToUInt64(data.AsSpan(), seed);
+ public static UInt128 HashToUInt128(byte[] data, uint seed = DefaultSeed) =>
+ HashToUInt128(data.AsSpan(), seed);
- public static ulong HashToUInt64(ReadOnlySpan data, uint seed = DefaultSeed) =>
- BitConverter.ToUInt64(Hash(data, seed), 0);
+ public static UInt128 HashToUInt128(ReadOnlySpan data, uint seed = DefaultSeed) =>
+ BitConverter.ToUInt128(Hash(data, seed), 0);
- public static ulong HashToUInt64(string text, uint seed = DefaultSeed)
+ public static UInt128 HashToUInt128(string text, uint seed = DefaultSeed)
{
var bytes = Encoding.UTF8.GetBytes(text);
- return HashToUInt64(bytes, seed);
+ return HashToUInt128(bytes, seed);
}
public override void Reset()
{
- _h1 = 0;
- _h2 = 0;
+ _h1 = _seed;
+ _h2 = _seed;
_length = 0;
}
@@ -91,20 +89,12 @@ public override void Append(ReadOnlySpan source)
var index = 0;
var bytesRemaining = source.Length;
- while (bytesRemaining >= 16)
+ for (; bytesRemaining >= 16; index += 16, bytesRemaining -= 16)
{
- unsafe
- {
- fixed (byte* ptr = &source[index])
- {
- var k1 = Unsafe.ReadUnaligned(ptr);
- var k2 = Unsafe.ReadUnaligned(ptr + 8);
-
- ProcessBlock(k1, k2);
- }
- }
- index += 16;
- bytesRemaining -= 16;
+ var k1 = Unsafe.ReadUnaligned(in source[index]);
+ var k2 = Unsafe.ReadUnaligned(in source[index + 8]);
+
+ ProcessBlock(k1, k2);
}
// Process leftover bytes if the source isn't an exact multiple of 16 bytes
@@ -150,40 +140,13 @@ protected override void GetCurrentHashCore(Span destination)
_h1 += _h2;
_h2 += _h1;
- unsafe
- {
- fixed (byte* destPtr = destination)
- {
- Unsafe.WriteUnaligned(destPtr, _h1);
- Unsafe.WriteUnaligned(destPtr + 8, _h2);
- }
- }
+ Unsafe.WriteUnaligned(ref destination[0], _h1);
+ Unsafe.WriteUnaligned(ref destination[8], _h2);
}
protected override void GetHashAndResetCore(Span destination)
{
- // Finalization
- _h1 ^= _length;
- _h2 ^= _length;
-
- _h1 += _h2;
- _h2 += _h1;
-
- _h1 = MixFinal(_h1);
- _h2 = MixFinal(_h2);
-
- _h1 += _h2;
- _h2 += _h1;
-
- unsafe
- {
- fixed (byte* destPtr = destination)
- {
- Unsafe.WriteUnaligned(destPtr, _h1);
- Unsafe.WriteUnaligned(destPtr + 8, _h2);
- }
- }
-
+ GetCurrentHashCore(destination);
Reset(); // Reset after reading
}
@@ -227,9 +190,9 @@ private static ulong MixK2(ulong k2)
private static ulong MixFinal(ulong k)
{
k ^= k >> 33;
- k *= 0xff51afd7ed558ccdUL;
+ k *= 0xff51afd7ed558ccdul;
k ^= k >> 33;
- k *= 0xc4ceb9fe1a85ec53UL;
+ k *= 0xc4ceb9fe1a85ec53ul;
k ^= k >> 33;
return k;
}
diff --git a/src/Neo.IO/Neo.IO.csproj b/src/Neo.IO/Neo.IO.csproj
index 75b799f..e6d840e 100644
--- a/src/Neo.IO/Neo.IO.csproj
+++ b/src/Neo.IO/Neo.IO.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/src/Neo.Localization/Neo.Localization.csproj b/src/Neo.Localization/Neo.Localization.csproj
index 8c0abdd..0f48a0e 100644
--- a/src/Neo.Localization/Neo.Localization.csproj
+++ b/src/Neo.Localization/Neo.Localization.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/src/Neo.Localization/Resources/VM/VirtualMachineLocalizer.resx b/src/Neo.Localization/Resources/VM/VirtualMachineLocalizer.resx
index a4bc037..72d954b 100644
--- a/src/Neo.Localization/Resources/VM/VirtualMachineLocalizer.resx
+++ b/src/Neo.Localization/Resources/VM/VirtualMachineLocalizer.resx
@@ -121,9 +121,9 @@
L{0:D4} {1,-11:G}{2}
- Starting execution | HardFork: {0:G} | GasLimit: {1:N0}
+ Starting execution | HardFork: {0:G} | GasLimit: {1:N8}
- Execution completed successfully | GasConsumed: {0:N0} | GasLeft: {1:N0} | State: {2:G}
+ Execution completed successfully | GasConsumed: {0:N8} | GasLeft: {1:N8} | State: {2:G}
\ No newline at end of file
diff --git a/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj b/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj
index 8c6ee08..87a7ff8 100644
--- a/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj
+++ b/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj
@@ -10,9 +10,9 @@
-
-
-
+
+
+
diff --git a/src/Neo.Platform.Hosting/Neo.Platform.Hosting.csproj b/src/Neo.Platform.Hosting/Neo.Platform.Hosting.csproj
index 6225137..a8d8730 100644
--- a/src/Neo.Platform.Hosting/Neo.Platform.Hosting.csproj
+++ b/src/Neo.Platform.Hosting/Neo.Platform.Hosting.csproj
@@ -6,9 +6,9 @@
-
-
-
+
+
+
diff --git a/src/Neo.Platform.Storage/BlockchainStore.cs b/src/Neo.Platform.Storage/BlockchainStore.cs
new file mode 100644
index 0000000..2fca4dc
--- /dev/null
+++ b/src/Neo.Platform.Storage/BlockchainStore.cs
@@ -0,0 +1,263 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+using Neo.Configuration;
+using Neo.Platform.Storage.Interface;
+using Neo.Platform.Storage.Logging;
+using RocksDbNet;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+
+namespace Neo.Platform.Storage
+{
+ public sealed class BlockchainStore : IEnumerable>, IStore
+ {
+ public BlockchainStoreOptions Options => _options;
+
+ private static readonly ColumnFamilyDescriptor[] s_columnFamilies =
+ [
+ new(ColumnFamilyNames.Default),
+ new(ColumnFamilyNames.Checkpoints),
+ new(ColumnFamilyNames.Backups),
+ ];
+
+ private readonly Cache _blockSharedCache;
+ private readonly FilterPolicy _bloomFilter;
+ private readonly RocksDb _db;
+
+ private readonly BlockchainStoreOptions _options;
+
+ private readonly ILoggerFactory _loggerFactory;
+ private readonly ILogger _logger;
+
+ public BlockchainStore(
+ IOptions options,
+ ILoggerFactory? loggerFactory = default)
+ {
+ _options = options.Value;
+ _loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
+ _logger = _loggerFactory.CreateLogger();
+
+ var dirInfo = new DirectoryInfo(_options.DatabasePath);
+
+ if (dirInfo.Exists == false)
+ dirInfo.Create();
+
+ var gcInfo = GC.GetGCMemoryInfo();
+ var ram = (ulong)gcInfo.TotalAvailableMemoryBytes / 3ul; // 33% of all RAM
+
+ _blockSharedCache = Cache.CreateLru(ram);
+ _bloomFilter = FilterPolicy.CreateBloom(10);
+
+ var dbOptions = new DbOptions()
+ {
+ CreateIfMissing = _options.CreateIfMissing,
+ CreateMissingColumnFamilies = true,
+
+ // Compression
+ Compression = Compression.Lz4,
+ BottommostCompression = Compression.Zstd,
+
+ // Parallelism
+ MaxBackgroundJobs = Math.Max(2, Environment.ProcessorCount),
+ MaxSubcompactions = (uint)Environment.ProcessorCount / 2,
+
+ // Write buffers
+ WriteBufferSize = 256 * 1024 * 1024, // 256MB
+ MaxWriteBufferNumber = 5,
+ MinWriteBufferNumberToMerge = 2,
+
+ // Compaction
+ NumLevels = 7,
+ Level0FileNumCompactionTrigger = 4,
+ Level0SlowdownWritesTrigger = 20,
+ Level0StopWritesTrigger = 36,
+ TargetFileSizeBase = 128 * 1024 * 1024, // 128MB
+ MaxBytesForLevelBase = 256 * 1024 * 1024, // 256MB
+ LevelCompactionDynamicLevelBytes = true,
+
+ // I/O
+ BytesPerSync = 0,
+ UseDirectReads = false,
+ AllowConcurrentMemtableWrite = true,
+ AtomicFlush = false,
+
+ // Files
+ MaxOpenFiles = -1, // Keep all files open
+
+ // WAL
+ WalRecoveryMode = WalRecoveryMode.PointInTime,
+ ManualWalFlush = false,
+
+ // Cache
+ BlockBasedTableFactory = CreateBlockTableOptions(),
+ };
+
+ dbOptions.OptimizeForPointLookup(64); // 64MB
+
+ _db = RocksDb.Open(dbOptions, _options.DatabasePath, s_columnFamilies);
+ }
+
+ public void Dispose()
+ {
+ _db.Dispose();
+ _blockSharedCache.Dispose();
+ _bloomFilter.Dispose();
+ }
+
+ public IStoreSnapshot CreateSnapshot()
+ {
+ var logLevel = LogLevel.Debug;
+
+ if (_logger.IsEnabled(logLevel))
+ _logger.LogCreateSnapshotMessage(logLevel, "Creating snapshot of the store.");
+
+ return new BlockchainStoreSnapshot(this, _db, _loggerFactory);
+ }
+
+ public void Put(ReadOnlySpan key, ReadOnlySpan value)
+ {
+ _db.Put(key, value);
+
+ var logLevel = LogLevel.Debug;
+
+ if (_logger.IsEnabled(logLevel))
+ _logger.LogWriteMessage(logLevel, $"Put key: {Convert.ToHexStringLower(key)} value: {Convert.ToHexStringLower(value)}");
+ }
+
+ public void Delete(ReadOnlySpan key)
+ {
+ if (_db.KeyMayExist(key))
+ {
+ _db.Delete(key);
+
+ var logLevel = LogLevel.Debug;
+
+ if (_logger.IsEnabled(logLevel))
+ _logger.LogDeleteMessage(logLevel, $"Deleted key: {Convert.ToHexStringLower(key)}");
+ }
+ }
+
+ public bool ContainsKey(ReadOnlySpan key)
+ {
+ if (_db.KeyMayExist(key))
+ return TryGet(key, out _);
+ return false;
+ }
+
+ public byte[]? Get(ReadOnlySpan key)
+ {
+ if (_db.KeyMayExist(key))
+ {
+ var data = _db.Get(key);
+ if (data is not null)
+ {
+ var logLevel = LogLevel.Debug;
+
+ if (_logger.IsEnabled(logLevel))
+ _logger.LogReadMessage(logLevel, $"Get key: {Convert.ToHexStringLower(key)} value: {Convert.ToHexStringLower(data)}");
+
+ return data;
+ }
+ }
+ return default;
+ }
+
+ public bool TryGet(ReadOnlySpan key, [NotNullWhen(true)] out byte[]? value)
+ {
+ if (_db.KeyMayExist(key))
+ {
+ var data = _db.Get(key);
+ if (data is not null)
+ {
+ value = data;
+
+ var logLevel = LogLevel.Debug;
+
+ if (_logger.IsEnabled(logLevel))
+ _logger.LogReadMessage(logLevel, $"TryGet key: {Convert.ToHexStringLower(key)} value: {Convert.ToHexStringLower(data)}");
+
+ return true;
+ }
+ }
+
+ value = default;
+ return false;
+ }
+
+ public IEnumerable> Seek(ReadOnlyMemory keyOrPrefix, bool seekFromEnd = false)
+ {
+ using var iter = _db.NewIterator();
+
+ for (iter.SeekForPrev(keyOrPrefix.Span); iter.IsValid();)
+ {
+ var key = iter.KeyToArray();
+ var value = iter.ValueToArray();
+
+ yield return new(key, value);
+
+ var logLevel = LogLevel.Debug;
+
+ if (_logger.IsEnabled(logLevel))
+ _logger.LogReadMessage(logLevel, $"Seek key: {Convert.ToHexStringLower(key)} value: {Convert.ToHexStringLower(value)}");
+
+ if (seekFromEnd)
+ iter.Prev();
+ else
+ iter.Next();
+ }
+ }
+
+ public IEnumerator GetEnumerator() =>
+ GetEnumerator();
+
+ IEnumerator> IEnumerable>.GetEnumerator()
+ {
+ using var iter = _db.NewIterator();
+ for (iter.SeekToFirst(); iter.IsValid(); iter.Next())
+ yield return new(iter.KeyToArray(), iter.ValueToArray());
+ }
+
+ private BlockBasedTableOptions CreateBlockTableOptions()
+ {
+ var blockOptions = new BlockBasedTableOptions()
+ {
+ FormatVersion = 5,
+ BlockSize = 64 * 1024, // 64KB
+ CacheIndexAndFilterBlocks = true,
+ PinL0FilterAndIndexBlocksInCache = true,
+ WholeKeyFiltering = true,
+ };
+
+ blockOptions.SetBlockCache(_blockSharedCache);
+ blockOptions.SetFilterPolicy(_bloomFilter);
+
+ return blockOptions;
+ }
+ }
+}
diff --git a/src/Neo.Platform.Storage/BlockchainStoreSnapshot.cs b/src/Neo.Platform.Storage/BlockchainStoreSnapshot.cs
new file mode 100644
index 0000000..670fd1d
--- /dev/null
+++ b/src/Neo.Platform.Storage/BlockchainStoreSnapshot.cs
@@ -0,0 +1,195 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Neo.Platform.Storage.Interface;
+using Neo.Platform.Storage.Logging;
+using RocksDbNet;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Neo.Platform.Storage
+{
+ public class BlockchainStoreSnapshot : IEnumerable>, IStoreSnapshot
+ {
+ public IStore Store => _store;
+
+ private readonly IStore _store;
+ private readonly RocksDb _db;
+ private readonly Snapshot _snapshot;
+ private readonly ReadOptions _readOptions;
+ private readonly WriteBatch _writeBatch;
+
+ private readonly ILoggerFactory _loggerFactory;
+ private readonly ILogger _logger;
+
+ public BlockchainStoreSnapshot(
+ IStore store,
+ RocksDb db,
+ ILoggerFactory? loggerFactory = default)
+ {
+ _store = store;
+ _db = db;
+ _loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
+ _logger = _loggerFactory.CreateLogger();
+
+ _snapshot = _db.NewSnapshot();
+ _readOptions = new();
+ _readOptions.SetSnapshot(_snapshot);
+ _writeBatch = new();
+ }
+
+ public void Dispose()
+ {
+ _snapshot.Dispose();
+ _readOptions.Dispose();
+ _writeBatch.Dispose();
+ GC.SuppressFinalize(this);
+ }
+
+ public void Commit()
+ {
+ var logLevel = LogLevel.Debug;
+
+ if (_logger.IsEnabled(logLevel))
+ _logger.LogCommitMessage(logLevel, $"Committing {_writeBatch.Count} change(s) to the database");
+
+ _db.Write(_writeBatch);
+ }
+
+ public bool ContainsKey(ReadOnlySpan key)
+ {
+ if (_db.KeyMayExist(key))
+ return TryGet(key, out _);
+ return false;
+ }
+
+ public IStoreSnapshot CreateSnapshot()
+ {
+ var logLevel = LogLevel.Debug;
+
+ if (_logger.IsEnabled(logLevel))
+ _logger.LogCreateSnapshotMessage(logLevel, "Creating snapshot of the snapshot.");
+
+ return new BlockchainStoreSnapshot(this, _db, _loggerFactory);
+ }
+
+ public void Delete(ReadOnlySpan key)
+ {
+ if (_db.KeyMayExist(key))
+ {
+ _writeBatch.Delete(key);
+
+ var logLevel = LogLevel.Debug;
+
+ if (_logger.IsEnabled(logLevel))
+ _logger.LogDeleteMessage(logLevel, $"Deleted key: {Convert.ToHexStringLower(key)}");
+ }
+ }
+
+ public byte[]? Get(ReadOnlySpan key)
+ {
+ if (_db.KeyMayExist(key))
+ {
+ var data = _db.Get(key, _readOptions);
+ if (data is not null)
+ {
+ var logLevel = LogLevel.Debug;
+
+ if (_logger.IsEnabled(logLevel))
+ _logger.LogReadMessage(logLevel, $"Get key: {Convert.ToHexStringLower(key)} value: {Convert.ToHexStringLower(data)}");
+
+ return data;
+ }
+ }
+ return default;
+ }
+
+ public IEnumerator> GetEnumerator()
+ {
+ using var iter = _db.NewIterator(_readOptions);
+ for (iter.SeekToFirst(); iter.IsValid(); iter.Next())
+ yield return new(iter.KeyToArray(), iter.ValueToArray());
+ }
+
+ public void Put(ReadOnlySpan key, ReadOnlySpan value)
+ {
+ _writeBatch.Put(key, value);
+
+ var logLevel = LogLevel.Debug;
+
+ if (_logger.IsEnabled(logLevel))
+ _logger.LogWriteMessage(logLevel, $"Put key: {Convert.ToHexStringLower(key)} value: {Convert.ToHexStringLower(value)}");
+ }
+
+ public IEnumerable> Seek(ReadOnlyMemory keyOrPrefix, bool seekFromEnd = false)
+ {
+ using var iter = _db.NewIterator(_readOptions);
+
+ for (iter.SeekForPrev(keyOrPrefix.Span); iter.IsValid();)
+ {
+ var key = iter.KeyToArray();
+ var value = iter.ValueToArray();
+
+ yield return new(key, value);
+
+ var logLevel = LogLevel.Debug;
+
+ if (_logger.IsEnabled(logLevel))
+ _logger.LogReadMessage(logLevel, $"Seek key: {Convert.ToHexStringLower(key)} value: {Convert.ToHexStringLower(value)}");
+
+ if (seekFromEnd)
+ iter.Prev();
+ else
+ iter.Next();
+ }
+ }
+
+ public bool TryGet(ReadOnlySpan key, [NotNullWhen(true)] out byte[]? value)
+ {
+ if (_db.KeyMayExist(key))
+ {
+ var data = _db.Get(key, _readOptions);
+ if (data is not null)
+ {
+ value = data;
+
+ var logLevel = LogLevel.Debug;
+
+ if (_logger.IsEnabled(logLevel))
+ _logger.LogReadMessage(logLevel, $"TryGet key: {Convert.ToHexStringLower(key)} value: {Convert.ToHexStringLower(data)}");
+
+ return true;
+ }
+ }
+
+ value = default;
+ return false;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() =>
+ GetEnumerator();
+ }
+}
diff --git a/src/Neo.Platform.Storage/ColumnFamilyNames.cs b/src/Neo.Platform.Storage/ColumnFamilyNames.cs
new file mode 100644
index 0000000..d46b04b
--- /dev/null
+++ b/src/Neo.Platform.Storage/ColumnFamilyNames.cs
@@ -0,0 +1,31 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+namespace Neo.Platform.Storage
+{
+ public static class ColumnFamilyNames
+ {
+ public static readonly string Default = "default";
+ public static readonly string Checkpoints = "checkpoints";
+ public static readonly string Backups = "backups";
+ }
+}
diff --git a/src/Neo.Platform.Storage/Interface/IReadOnlyStore.cs b/src/Neo.Platform.Storage/Interface/IReadOnlyStore.cs
new file mode 100644
index 0000000..c911de2
--- /dev/null
+++ b/src/Neo.Platform.Storage/Interface/IReadOnlyStore.cs
@@ -0,0 +1,39 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Neo.Platform.Storage.Interface
+{
+ public interface IReadOnlyStore : IDisposable
+ {
+ bool ContainsKey(ReadOnlySpan key);
+
+ byte[]? Get(ReadOnlySpan key);
+
+ bool TryGet(ReadOnlySpan key, [NotNullWhen(true)] out byte[]? value);
+
+ IEnumerable> Seek(ReadOnlyMemory keyOrPrefix, bool seekFromEnd = false);
+ }
+}
diff --git a/src/Neo.Platform.Storage/Interface/IStore.cs b/src/Neo.Platform.Storage/Interface/IStore.cs
new file mode 100644
index 0000000..f7684bd
--- /dev/null
+++ b/src/Neo.Platform.Storage/Interface/IStore.cs
@@ -0,0 +1,35 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using System;
+
+namespace Neo.Platform.Storage.Interface
+{
+ public interface IStore : IReadOnlyStore
+ {
+ IStoreSnapshot CreateSnapshot();
+
+ void Put(ReadOnlySpan key, ReadOnlySpan value);
+
+ void Delete(ReadOnlySpan key);
+ }
+}
diff --git a/src/Neo.Platform.Storage/Interface/IStoreSnapshot.cs b/src/Neo.Platform.Storage/Interface/IStoreSnapshot.cs
new file mode 100644
index 0000000..f9eaaed
--- /dev/null
+++ b/src/Neo.Platform.Storage/Interface/IStoreSnapshot.cs
@@ -0,0 +1,33 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using System;
+
+namespace Neo.Platform.Storage.Interface
+{
+ public interface IStoreSnapshot : IStore, IDisposable
+ {
+ IStore Store { get; }
+
+ void Commit();
+ }
+}
diff --git a/src/Neo.Platform.Storage/Logging/LoggingExtensions.cs b/src/Neo.Platform.Storage/Logging/LoggingExtensions.cs
new file mode 100644
index 0000000..5536de9
--- /dev/null
+++ b/src/Neo.Platform.Storage/Logging/LoggingExtensions.cs
@@ -0,0 +1,106 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Logging;
+using System;
+
+namespace Neo.Platform.Storage.Logging
+{
+ internal static partial class LoggerExtensions
+ {
+ [LoggerMessage(
+ EventId = StoreEventId.Fault,
+ EventName = nameof(StoreEventId.Fault),
+ Message = "{Message}"
+ )]
+ public static partial void LogFaultMessage(
+ this ILogger logger,
+ LogLevel logLevel,
+ string message
+ );
+
+ [LoggerMessage(
+ EventId = StoreEventId.Fault,
+ Message = ""
+ )]
+ public static partial void LogFaultMessage(
+ this ILogger logger,
+ LogLevel logLevel,
+ Exception exception
+ );
+
+ [LoggerMessage(
+ EventId = StoreEventId.Read,
+ EventName = nameof(StoreEventId.Read),
+ Message = "{Message}"
+ )]
+ public static partial void LogReadMessage(
+ this ILogger logger,
+ LogLevel logLevel,
+ string message
+ );
+
+ [LoggerMessage(
+ EventId = StoreEventId.Write,
+ EventName = nameof(StoreEventId.Write),
+ Message = "{Message}"
+ )]
+ public static partial void LogWriteMessage(
+ this ILogger logger,
+ LogLevel logLevel,
+ string message
+ );
+
+ [LoggerMessage(
+ EventId = StoreEventId.Delete,
+ EventName = nameof(StoreEventId.Delete),
+ Message = "{Message}"
+ )]
+ public static partial void LogDeleteMessage(
+ this ILogger logger,
+ LogLevel logLevel,
+ string message
+ );
+
+ [LoggerMessage(
+ EventId = StoreEventId.CreateSnapshot,
+ EventName = nameof(StoreEventId.CreateSnapshot),
+ Message = "{Message}"
+ )]
+ public static partial void LogCreateSnapshotMessage(
+ this ILogger logger,
+ LogLevel logLevel,
+ string message
+ );
+
+ [LoggerMessage(
+ EventId = StoreEventId.Commit,
+ EventName = nameof(StoreEventId.Commit),
+ Message = "{Message}"
+ )]
+ public static partial void LogCommitMessage(
+ this ILogger logger,
+ LogLevel logLevel,
+ string message
+ );
+ }
+}
diff --git a/src/Neo.Platform.Storage/Logging/StoreEventId.cs b/src/Neo.Platform.Storage/Logging/StoreEventId.cs
new file mode 100644
index 0000000..4946a70
--- /dev/null
+++ b/src/Neo.Platform.Storage/Logging/StoreEventId.cs
@@ -0,0 +1,34 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+namespace Neo.Platform.Storage.Logging
+{
+ internal static class StoreEventId
+ {
+ public const int Fault = 100;
+ public const int Read = 200;
+ public const int Write = 300;
+ public const int Delete = 400;
+ public const int CreateSnapshot = 500;
+ public const int Commit = 600;
+ }
+}
diff --git a/src/Neo.Platform.Storage/Neo.Platform.Storage.csproj b/src/Neo.Platform.Storage/Neo.Platform.Storage.csproj
new file mode 100644
index 0000000..86e14f3
--- /dev/null
+++ b/src/Neo.Platform.Storage/Neo.Platform.Storage.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net10.0
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Neo.Platform.Storage/StoreExtensions.cs b/src/Neo.Platform.Storage/StoreExtensions.cs
new file mode 100644
index 0000000..25bbd6c
--- /dev/null
+++ b/src/Neo.Platform.Storage/StoreExtensions.cs
@@ -0,0 +1,54 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Neo.Core.Extensions;
+using Neo.Core.Serialization;
+using Neo.Platform.Storage.Interface;
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Neo.Platform.Storage
+{
+ public static class StoreExtensions
+ {
+ public static void Put(this IStore store, ReadOnlySpan key, T value)
+ where T : INeoSerializable =>
+ store.Put(key, value.ToArray());
+
+ public static TResult? Get(this IReadOnlyStore store, ReadOnlySpan key)
+ where TResult : class?, INeoSerializable =>
+ store.Get(key)?.AsSerializable();
+
+ public static bool TryGet(this IReadOnlyStore store, ReadOnlySpan key, [NotNullWhen(true)] out T? value)
+ where T : class?, INeoSerializable
+ {
+ value = default;
+ if (store.TryGet(key, out var valueBytes))
+ {
+ value = valueBytes.AsSerializable();
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs b/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs
index 68616b3..bfdfc72 100644
--- a/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs
@@ -32,18 +32,24 @@ public static class ServiceCollectionExtensions
public static IServiceCollection AddExecutionEngine(this IServiceCollection services)
{
// Register VirtualMachineEngine using the built pipeline
- services.AddScoped(sp =>
- {
- var middleware = sp.GetServices(); // NOTE: Get all custom middleware
- var debugger = sp.GetRequiredService(); // NOTE: get the debugger middleware
- var pipeline = VirtualMachinePipelineBuilder.Create()
- .Use([debugger, .. middleware]) // NOTE: ORDER MATTERS HERE
- .Build();
+ services.AddScoped(
+ sp =>
+ {
+ var middleware = sp.GetServices(); // NOTE: Get all custom middleware
+ var debugger = sp.GetService(); // NOTE: get the debugger middleware
+
+ if (debugger is not null)
+ middleware = [debugger, .. middleware];
- return VirtualMachineBuilder.Create()
- .UsePipeline(pipeline)
- .Build();
- });
+ var pipeline = VirtualMachinePipelineBuilder.Create()
+ .Use(middleware) // NOTE: ORDER MATTERS HERE
+ .Build();
+
+ return VirtualMachineBuilder.Create()
+ .UsePipeline(pipeline)
+ .Build();
+ }
+ );
return services;
}
@@ -52,17 +58,18 @@ public static IServiceCollection AddEngineMiddleware(this IServiceC
where TMiddleware : class, IEngineMiddleware
{
services.AddSingleton();
+
return services;
}
- public static IServiceCollection AddEngineDebugger(this IServiceCollection services)
+ public static IServiceCollection AddEngineMiddlewareDebugger(this IServiceCollection services)
{
services.AddScoped();
return services;
}
- public static IServiceCollection AddExecuteLogger(this IServiceCollection services)
+ public static IServiceCollection AddEngineMiddlewareLogger(this IServiceCollection services)
{
services.AddScoped();
diff --git a/src/Neo.VM/Middleware/DebuggerMiddleware.cs b/src/Neo.VM/Middleware/DebuggerMiddleware.cs
index f9eb260..d7453cc 100644
--- a/src/Neo.VM/Middleware/DebuggerMiddleware.cs
+++ b/src/Neo.VM/Middleware/DebuggerMiddleware.cs
@@ -36,6 +36,8 @@ public sealed class DebuggerMiddleware(IServiceProvider sp, ILogger sp.GetRequiredService();
private readonly HashSet _breakpoints = []; // script offset breakpoints
@@ -87,8 +89,9 @@ public void PreExecute(ExecutionContext? context, ExecuteDelegate next)
if (logger.IsEnabled(LogLevel.Debug))
{
- var gasLeft = Engine.GasLimit - context.GasConsumed;
- var message = $"Breakpoint hit | GasConsumed: {context.GasConsumed:N0} | GasLeft: {gasLeft:N0}";
+ var gasLeft = (Engine.GasLimit - context.GasConsumed) / s_factor;
+ var gasConsumed = context.GasConsumed / s_factor;
+ var message = $"Breakpoint hit | GasConsumed: {gasConsumed:N8} | GasLeft: {gasLeft:N8}";
logger.LogBreakMessage(LogLevel.Debug, message);
}
diff --git a/src/Neo.VM/Middleware/ExecuteLoggerMiddleware.cs b/src/Neo.VM/Middleware/ExecuteLoggerMiddleware.cs
index d84d961..7720941 100644
--- a/src/Neo.VM/Middleware/ExecuteLoggerMiddleware.cs
+++ b/src/Neo.VM/Middleware/ExecuteLoggerMiddleware.cs
@@ -31,6 +31,8 @@ namespace Neo.VM.Middleware
{
public sealed class ExecuteLoggerMiddleware(IServiceProvider sp, ILogger logger) : IEngineMiddleware
{
+ private static readonly decimal s_factor = 1_00000000m;
+
private VirtualMachineEngine Engine => sp.GetRequiredService();
public void PostExecute(ExecutionContext? context, ExecuteDelegate next)
@@ -47,7 +49,7 @@ public void PreExecution(ExecutionDelegate next)
var message = VirtualMachineLocalizer.GetMessage(
VirtualMachineMessageNames.ExecuteStartup,
Engine.ActiveFork,
- Engine.GasLimit
+ Engine.GasLimit / s_factor
);
logger.LogExecuteMessage(logLevel, message);
@@ -87,8 +89,8 @@ public void PostExecution(ExecutionDelegate next)
{
var message = VirtualMachineLocalizer.GetMessage(
VirtualMachineMessageNames.ExecuteSuccessfully,
- Engine.GasConsumed,
- Engine.GasLeft,
+ Engine.GasConsumed / s_factor,
+ Engine.GasLeft / s_factor,
Engine.State
);
diff --git a/src/Neo.VM/Neo.VM.csproj b/src/Neo.VM/Neo.VM.csproj
index ef12f3f..a4f16c3 100644
--- a/src/Neo.VM/Neo.VM.csproj
+++ b/src/Neo.VM/Neo.VM.csproj
@@ -6,8 +6,8 @@
-
-
+
+
diff --git a/tests/Directory.Build.Props b/tests/Directory.Build.Props
index 2e84aab..82df4df 100644
--- a/tests/Directory.Build.Props
+++ b/tests/Directory.Build.Props
@@ -17,7 +17,7 @@
-
+
diff --git a/tests/Neo.Configuration.Tests/Neo.Configuration.Tests.csproj b/tests/Neo.Configuration.Tests/Neo.Configuration.Tests.csproj
index f00a051..49a4608 100644
--- a/tests/Neo.Configuration.Tests/Neo.Configuration.Tests.csproj
+++ b/tests/Neo.Configuration.Tests/Neo.Configuration.Tests.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/tests/Neo.Core.Tests/Blockchain/UT_BlockHeader.cs b/tests/Neo.Core.Tests/Blockchain/UT_BlockHeader.cs
index 1bdd996..e09ca1c 100644
--- a/tests/Neo.Core.Tests/Blockchain/UT_BlockHeader.cs
+++ b/tests/Neo.Core.Tests/Blockchain/UT_BlockHeader.cs
@@ -73,9 +73,9 @@ public void TestDeserialize()
Assert.AreEqual(expectedBlockHeader.Index, actualBlockHeader.Index);
Assert.AreEqual(expectedBlockHeader.NextConsensus, actualBlockHeader.NextConsensus);
- CollectionAssert.AreEqual(expectedBlockHeader.Witness.InvocationScript, actualBlockHeader.Witness.InvocationScript);
- CollectionAssert.AreEqual(expectedBlockHeader.Witness.VerificationScript, actualBlockHeader.Witness.VerificationScript);
- CollectionAssert.AreEqual(Convert.FromHexString(s_blockHeaderHexString), actualBlockHeaderBytes);
+ Assert.AreSequenceEqual(expectedBlockHeader.Witness.InvocationScript, actualBlockHeader.Witness.InvocationScript);
+ Assert.AreSequenceEqual(expectedBlockHeader.Witness.VerificationScript, actualBlockHeader.Witness.VerificationScript);
+ Assert.AreSequenceEqual(Convert.FromHexString(s_blockHeaderHexString), actualBlockHeaderBytes);
}
[TestMethod]
diff --git a/tests/Neo.Core.Tests/Extensions/UT_ByteExtensions.cs b/tests/Neo.Core.Tests/Extensions/UT_ByteExtensions.cs
index e59b350..3306b4a 100644
--- a/tests/Neo.Core.Tests/Extensions/UT_ByteExtensions.cs
+++ b/tests/Neo.Core.Tests/Extensions/UT_ByteExtensions.cs
@@ -46,13 +46,13 @@ public void TestXor()
var actualXorByte3 = expectedBytes3.Xor(new byte[ushort.MaxValue]);
Assert.HasCount(3, actualXorByte1);
- CollectionAssert.AreEqual(expectedBytes1, actualXorByte1);
+ Assert.AreSequenceEqual(expectedBytes1, actualXorByte1);
Assert.HasCount(3, actualXorByte2);
- CollectionAssert.AreEqual(expectedBytes2, actualXorByte2);
+ Assert.AreSequenceEqual(expectedBytes2, actualXorByte2);
Assert.HasCount(ushort.MaxValue, actualXorByte3);
- CollectionAssert.AreEqual(expectedBytes3, actualXorByte3);
+ Assert.AreSequenceEqual(expectedBytes3, actualXorByte3);
Assert.ThrowsExactly(() => new byte[0].Xor(new byte[ushort.MaxValue]));
}
diff --git a/tests/Neo.Core.Tests/Extensions/UT_StreamExtensions.cs b/tests/Neo.Core.Tests/Extensions/UT_StreamExtensions.cs
index 8deed7d..89713b9 100644
--- a/tests/Neo.Core.Tests/Extensions/UT_StreamExtensions.cs
+++ b/tests/Neo.Core.Tests/Extensions/UT_StreamExtensions.cs
@@ -50,7 +50,7 @@ public void TestWrite()
var actualData = ms.ToArray();
- CollectionAssert.AreEqual(expectedData, actualData);
+ Assert.AreSequenceEqual(expectedData, actualData);
}
[TestMethod]
@@ -77,7 +77,7 @@ public void TestRead()
var actualArrayData = ms.ReadDynamic();
Assert.HasCount(1, actualArrayData);
- CollectionAssert.AreEqual(expectedArrayData, actualArrayData);
+ Assert.AreSequenceEqual(expectedArrayData, actualArrayData);
}
}
}
diff --git a/tests/Neo.Core.Tests/Neo.Core.Tests.csproj b/tests/Neo.Core.Tests/Neo.Core.Tests.csproj
index 7413dff..835b575 100644
--- a/tests/Neo.Core.Tests/Neo.Core.Tests.csproj
+++ b/tests/Neo.Core.Tests/Neo.Core.Tests.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/tests/Neo.Core.Tests/Text/UT_Base58.cs b/tests/Neo.Core.Tests/Text/UT_Base58.cs
index cf6f707..0669e7c 100644
--- a/tests/Neo.Core.Tests/Text/UT_Base58.cs
+++ b/tests/Neo.Core.Tests/Text/UT_Base58.cs
@@ -74,7 +74,7 @@ public void TestEncodeDecode()
var actualDecodeBytes = Base58.Decode(entry.Value);
Assert.AreEqual(entry.Value, actualEncodeBytes);
- CollectionAssert.AreEqual(keyBytes, actualDecodeBytes);
+ Assert.AreSequenceEqual(keyBytes, actualDecodeBytes);
}
var invalidBase58 = new string[] { "0", "O", "I", "l", "+", "/" };
diff --git a/tests/Neo.Core.Tests/UT_UInt160.cs b/tests/Neo.Core.Tests/UT_UInt160.cs
index 0077a15..96f3abd 100644
--- a/tests/Neo.Core.Tests/UT_UInt160.cs
+++ b/tests/Neo.Core.Tests/UT_UInt160.cs
@@ -20,7 +20,6 @@
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES
-#pragma warning disable CS1718 // Comparison made to same variable
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
using System;
diff --git a/tests/Neo.Core.Tests/UT_UInt256.cs b/tests/Neo.Core.Tests/UT_UInt256.cs
index d1991a8..ec2621c 100644
--- a/tests/Neo.Core.Tests/UT_UInt256.cs
+++ b/tests/Neo.Core.Tests/UT_UInt256.cs
@@ -20,7 +20,6 @@
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES
-#pragma warning disable CS1718 // Comparison made to same variable
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
using Neo.Core.Extensions;
diff --git a/tests/Neo.IO.Tests/Data/UT_BloomFilter.cs b/tests/Neo.IO.Tests/Data/UT_BloomFilter.cs
index 5d5df2f..6f4f3c8 100644
--- a/tests/Neo.IO.Tests/Data/UT_BloomFilter.cs
+++ b/tests/Neo.IO.Tests/Data/UT_BloomFilter.cs
@@ -46,7 +46,7 @@ public void TestAddContains()
}
[TestMethod]
- public void TestBloomFIlterConstructorGetMTweak()
+ public void TestBloomFilterConstructorGetMTweak()
{
var expectedElements = 7;
var nTweak = 123456u;
diff --git a/tests/Neo.IO.Tests/Hashing/UT_Murmur128.cs b/tests/Neo.IO.Tests/Hashing/UT_Murmur128.cs
index ec51fce..8548ede 100644
--- a/tests/Neo.IO.Tests/Hashing/UT_Murmur128.cs
+++ b/tests/Neo.IO.Tests/Hashing/UT_Murmur128.cs
@@ -32,7 +32,7 @@ public class UT_Murmur128
private const uint DefaultTestSeed = 123u;
[TestMethod]
- public void TestHashToUInt64()
+ public void TestHash()
{
var arrayData = Encoding.ASCII.GetBytes("hello");
var actualHash = Murmur128.Hash(arrayData, DefaultTestSeed);
@@ -52,5 +52,31 @@ public void TestHashToUInt64()
Assert.AreEqual("e0a0632d4f51302c55e3b3e48d28795d", actualHashString);
}
+
+ [TestMethod]
+ public void TestMoreThan16Characters()
+ {
+ var arrayData = Encoding.ASCII.GetBytes("Testing more than 16 characters.");
+ var actualHash = Murmur128.Hash(arrayData, DefaultTestSeed);
+ var actualHashString = Convert.ToHexStringLower(actualHash);
+
+ Assert.AreEqual("6903e6d3d2fe781787819c89b2c11aa9", actualHashString);
+
+ arrayData = Encoding.ASCII.GetBytes
+ (
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
+ "Sed do eiusmod tempor incididunt ut labore et dolore magna " +
+ "aliqua. Ut enim ad minim veniam, quis nostrud exercitation " +
+ "ullamco laboris nisi ut aliquip ex ea commodo consequat. " +
+ "Duis aute irure dolor in reprehenderit in voluptate velit " +
+ "esse cillum dolore eu fugiat nulla pariatur. Excepteur sint " +
+ "occaecat cupidatat non proident, sunt in culpa qui officia " +
+ "deserunt mollit anim id est laborum."
+ );
+ actualHash = Murmur128.Hash(arrayData, DefaultTestSeed);
+ actualHashString = Convert.ToHexStringLower(actualHash);
+
+ Assert.AreEqual("dacfff46c267a7be8dbb73489c1d2678", actualHashString);
+ }
}
}
diff --git a/tests/Neo.IO.Tests/Neo.IO.Tests.csproj b/tests/Neo.IO.Tests/Neo.IO.Tests.csproj
index 43491ce..44c0458 100644
--- a/tests/Neo.IO.Tests/Neo.IO.Tests.csproj
+++ b/tests/Neo.IO.Tests/Neo.IO.Tests.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/tests/Neo.Platform.Storage.Tests/MSTestSettings.cs b/tests/Neo.Platform.Storage.Tests/MSTestSettings.cs
new file mode 100644
index 0000000..7cde898
--- /dev/null
+++ b/tests/Neo.Platform.Storage.Tests/MSTestSettings.cs
@@ -0,0 +1,23 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
diff --git a/tests/Neo.Platform.Storage.Tests/Neo.Platform.Storage.Tests.csproj b/tests/Neo.Platform.Storage.Tests/Neo.Platform.Storage.Tests.csproj
new file mode 100644
index 0000000..d411cfd
--- /dev/null
+++ b/tests/Neo.Platform.Storage.Tests/Neo.Platform.Storage.Tests.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net10.0
+ latest
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Neo.Platform.Storage.Tests/TestStorePool.cs b/tests/Neo.Platform.Storage.Tests/TestStorePool.cs
new file mode 100644
index 0000000..55d048e
--- /dev/null
+++ b/tests/Neo.Platform.Storage.Tests/TestStorePool.cs
@@ -0,0 +1,62 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Options;
+using Neo.Configuration;
+using System.Collections.Concurrent;
+using System.IO;
+
+namespace Neo.Platform.Storage.Tests
+{
+ internal sealed class TestStorePool
+ {
+ public static TestStorePool Shared => s_storePool;
+
+ private static readonly TestStorePool s_storePool = new();
+
+ private readonly ConcurrentDictionary _store = [];
+
+ public BlockchainStore Rent()
+ {
+ var storeOptions = Options.Create(new BlockchainStoreOptions()
+ {
+ DatabasePath = Path.Combine(Path.GetRandomFileName()),
+ });
+
+ var store = new BlockchainStore(storeOptions, TestUtilities.TraceLoggerFactory);
+
+ _store.TryAdd(storeOptions.Value.DatabasePath, store);
+
+ return store;
+ }
+
+ public void Return(BlockchainStore store)
+ {
+ if (_store.TryRemove(store.Options.DatabasePath, out var value))
+ {
+ value.Dispose();
+ new DirectoryInfo(store.Options.DatabasePath)
+ .Delete(true);
+ }
+ }
+ }
+}
diff --git a/tests/Neo.Platform.Storage.Tests/TestUtilities.cs b/tests/Neo.Platform.Storage.Tests/TestUtilities.cs
new file mode 100644
index 0000000..82d8406
--- /dev/null
+++ b/tests/Neo.Platform.Storage.Tests/TestUtilities.cs
@@ -0,0 +1,40 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+
+namespace Neo.Platform.Storage.Tests
+{
+ internal class TestUtilities
+ {
+ public static ILoggerFactory TraceLoggerFactory => LoggerFactory.Create(logging =>
+ {
+ var manger = new ConfigurationManager();
+ logging.AddConfiguration(manger);
+ logging.ClearProviders();
+ logging.AddSimpleConsole(options => options.SingleLine = true);
+ logging.AddDebug();
+ logging.SetMinimumLevel(LogLevel.Trace);
+ });
+ }
+}
diff --git a/tests/Neo.Platform.Storage.Tests/UT_BlockchainStore.cs b/tests/Neo.Platform.Storage.Tests/UT_BlockchainStore.cs
new file mode 100644
index 0000000..ec101e0
--- /dev/null
+++ b/tests/Neo.Platform.Storage.Tests/UT_BlockchainStore.cs
@@ -0,0 +1,97 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using System.Linq;
+
+namespace Neo.Platform.Storage.Tests
+{
+ [TestClass]
+ public sealed class UT_BlockchainStore
+ {
+ [TestMethod]
+ public void TestSnapshot()
+ {
+ var store = TestStorePool.Shared.Rent();
+
+ store.Put([0xff, 0x00, 0x00], [0x00]);
+
+ using (var snapshot = store.CreateSnapshot())
+ {
+ var actualBytes = snapshot.Get([0xff, 0x00, 0x00]);
+
+ Assert.IsTrue(snapshot.ContainsKey([0xff, 0x00, 0x00]));
+ Assert.IsNotNull(actualBytes);
+ Assert.AreEqual(1, actualBytes?.Length);
+ Assert.AreEqual(0x00, actualBytes?[0]);
+
+ actualBytes = store.Get([0xff, 0x00, 0x00]);
+
+ Assert.IsTrue(store.ContainsKey([0xff, 0x00, 0x00]));
+ Assert.IsNotNull(actualBytes);
+ Assert.AreEqual(1, actualBytes?.Length);
+ Assert.AreEqual(0x00, actualBytes?[0]);
+
+ snapshot.Put([0xff, 0x00, 0x00], [0x01]);
+ snapshot.Commit();
+
+ actualBytes = store.Get([0xff, 0x00, 0x00]);
+
+ Assert.IsTrue(store.ContainsKey([0xff, 0x00, 0x00]));
+ Assert.IsNotNull(actualBytes);
+ Assert.AreEqual(1, actualBytes?.Length);
+ Assert.AreEqual(0x01, actualBytes?[0]);
+ }
+
+ TestStorePool.Shared.Return(store);
+ }
+
+ [TestMethod]
+ public void TestSeek()
+ {
+ var store = TestStorePool.Shared.Rent();
+
+ store.Put([0x00, 0x00, 0x00], [0x00]);
+ store.Put([0x00, 0x00, 0x01], [0x01]);
+ store.Put([0x00, 0x00, 0x02], [0x02]);
+ store.Put([0x00, 0x00, 0x03], [0x03]);
+ store.Put([0x00, 0x00, 0x04], [0x04]);
+
+ var actualResults = store.Seek((byte[])[0x00, 0x00, 0x01]).ToArray();
+
+ Assert.HasCount(4, actualResults);
+ Assert.AreSequenceEqual((byte[])[0x00, 0x00, 0x01], actualResults[0].Key);
+ Assert.AreSequenceEqual((byte[])[0x00, 0x00, 0x02], actualResults[1].Key);
+ Assert.AreSequenceEqual((byte[])[0x00, 0x00, 0x03], actualResults[2].Key);
+ Assert.AreSequenceEqual((byte[])[0x00, 0x00, 0x04], actualResults[3].Key);
+
+ actualResults = [.. store.Seek((byte[])[0x00, 0x00, 0x03], seekFromEnd: true)];
+
+ Assert.HasCount(4, actualResults);
+ Assert.AreSequenceEqual((byte[])[0x00, 0x00, 0x03], actualResults[0].Key);
+ Assert.AreSequenceEqual((byte[])[0x00, 0x00, 0x02], actualResults[1].Key);
+ Assert.AreSequenceEqual((byte[])[0x00, 0x00, 0x01], actualResults[2].Key);
+ Assert.AreSequenceEqual((byte[])[0x00, 0x00, 0x00], actualResults[3].Key);
+
+ TestStorePool.Shared.Return(store);
+ }
+ }
+}
diff --git a/tests/Neo.VM.Tests/Middleware/UT_DebuggerMiddleware.cs b/tests/Neo.VM.Tests/Middleware/UT_DebuggerMiddleware.cs
index 3ace102..92a818d 100644
--- a/tests/Neo.VM.Tests/Middleware/UT_DebuggerMiddleware.cs
+++ b/tests/Neo.VM.Tests/Middleware/UT_DebuggerMiddleware.cs
@@ -51,7 +51,7 @@ public void TestJsonFiles()
var script = OpCodeAssembler.Assemble(jsonTest.Script);
using var scope = TestUtilities.Services.CreateScope();
- var vm = scope.ServiceProvider.GetRequiredService();
+ using var vm = scope.ServiceProvider.GetRequiredService();
vm.LoadScript(script);
@@ -78,7 +78,7 @@ public void TestBreakpoints()
var script = sb.ToArray();
using var scope = TestUtilities.Services.CreateScope();
- var vm = scope.ServiceProvider.GetRequiredService();
+ using var vm = scope.ServiceProvider.GetRequiredService();
vm.LoadScript(script);
// Get the debugger that is inside the VM
diff --git a/tests/Neo.VM.Tests/Neo.VM.Tests.csproj b/tests/Neo.VM.Tests/Neo.VM.Tests.csproj
index 62edadf..61a4763 100644
--- a/tests/Neo.VM.Tests/Neo.VM.Tests.csproj
+++ b/tests/Neo.VM.Tests/Neo.VM.Tests.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/tests/Neo.VM.Tests/TestUtilities.cs b/tests/Neo.VM.Tests/TestUtilities.cs
index 98b313d..93a15ad 100644
--- a/tests/Neo.VM.Tests/TestUtilities.cs
+++ b/tests/Neo.VM.Tests/TestUtilities.cs
@@ -34,7 +34,7 @@ internal class TestUtilities
private static readonly ServiceProvider s_serviceProvider;
public static IServiceProvider Services => s_serviceProvider;
- public static readonly ILoggerFactory TraceLoggerFactory = LoggerFactory.Create(logging =>
+ public static ILoggerFactory TraceLoggerFactory => LoggerFactory.Create(logging =>
{
var manger = new ConfigurationManager();
logging.AddConfiguration(manger);
@@ -48,8 +48,8 @@ static TestUtilities()
var services = new ServiceCollection();
services
- .AddEngineDebugger()
- .AddExecuteLogger()
+ .AddEngineMiddlewareDebugger()
+ .AddEngineMiddlewareLogger()
.AddExecutionEngine()
.AddScoped()
.AddLogging(
diff --git a/tests/Neo.VM.Tests/Types/UT_VMObject.cs b/tests/Neo.VM.Tests/Types/UT_VMObject.cs
index ace465d..b8fae4f 100644
--- a/tests/Neo.VM.Tests/Types/UT_VMObject.cs
+++ b/tests/Neo.VM.Tests/Types/UT_VMObject.cs
@@ -280,7 +280,7 @@ public void TestCast()
item = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };
Assert.IsInstanceOfType(item);
- CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }, item.AsSpan().ToArray());
+ Assert.AreSequenceEqual(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }, item.AsSpan().ToArray());
}
[TestMethod]
@@ -301,7 +301,7 @@ public void TestCloneSelfReference()
var aa = (VMArray)a.Clone();
- Assert.AreEqual(a, aa);
+ Assert.AreSequenceEqual(a, aa);
Assert.AreNotSame(a, aa);
Assert.IsTrue(a[^2].Equals(aa[^2]));
Assert.IsTrue(a.HasCircularReference());
diff --git a/tests/Neo.VM.Tests/UT_NeoVirtualMachine.cs b/tests/Neo.VM.Tests/UT_NeoVirtualMachine.cs
index 7b41573..08a829f 100644
--- a/tests/Neo.VM.Tests/UT_NeoVirtualMachine.cs
+++ b/tests/Neo.VM.Tests/UT_NeoVirtualMachine.cs
@@ -63,7 +63,7 @@ public void TestSystemCall()
var expectedGasPrice = 1_00000000L + GasTable.GetGasCost(OpCode.PUSHDATA1, HardFork.Gorgon);
using var scope = TestUtilities.Services.CreateScope();
- var vm = scope.ServiceProvider.GetRequiredService();
+ using var vm = scope.ServiceProvider.GetRequiredService();
var actualMethodDesc = vm.RegisterSystemCall(
expectedSystemName,
@@ -74,8 +74,8 @@ public void TestSystemCall()
var actualFoundSystemCall = vm.SystemCallTable.TryGetValue(expectedSystemName, out var actualSystemCallAddress);
using var sb = new ScriptBuilder()
- .EmitSysCall(actualMethodDesc, expectedParamValue)
- .EmitReturn();
+ .EmitSysCall(actualMethodDesc, expectedParamValue)
+ .EmitReturn();
vm.LoadScript(sb.ToArray());
@@ -112,7 +112,7 @@ public void TestSimpleAdd()
];
using var scope = TestUtilities.Services.CreateScope();
- var vm = scope.ServiceProvider.GetRequiredService();
+ using var vm = scope.ServiceProvider.GetRequiredService();
vm.LoadScript(script);
var actualState = vm.Execute();
@@ -136,7 +136,7 @@ public void TestCircularReferenceCreateArray()
.EmitReturn();
using var scope = TestUtilities.Services.CreateScope();
- var vm = scope.ServiceProvider.GetRequiredService();
+ using var vm = scope.ServiceProvider.GetRequiredService();
vm.LoadScript(sb.ToArray());
var actualState = vm.Execute();
@@ -162,7 +162,7 @@ public void TestCreateArray()
.EmitReturn();
using var scope = TestUtilities.Services.CreateScope();
- var vm = scope.ServiceProvider.GetRequiredService();
+ using var vm = scope.ServiceProvider.GetRequiredService();
vm.LoadScript(sb.ToArray());
var actualState = vm.Execute();
@@ -203,7 +203,7 @@ public void TestPushingSimpleTypes()
var script = sb.ToArray();
using var scope = TestUtilities.Services.CreateScope();
- var vm = scope.ServiceProvider.GetRequiredService();
+ using var vm = scope.ServiceProvider.GetRequiredService();
vm.LoadScript(script);
var actualState = vm.Execute();
diff --git a/tests/Neo.Wallet.Tests/Neo.Wallet.Tests.csproj b/tests/Neo.Wallet.Tests/Neo.Wallet.Tests.csproj
index 19f638a..ae2202a 100644
--- a/tests/Neo.Wallet.Tests/Neo.Wallet.Tests.csproj
+++ b/tests/Neo.Wallet.Tests/Neo.Wallet.Tests.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/tests/Neo.Wallet.Tests/UT_ChainWallet.cs b/tests/Neo.Wallet.Tests/UT_ChainWallet.cs
index a0ce0da..d9aebd9 100644
--- a/tests/Neo.Wallet.Tests/UT_ChainWallet.cs
+++ b/tests/Neo.Wallet.Tests/UT_ChainWallet.cs
@@ -41,7 +41,7 @@ public void TestGetKeyFromWifString()
var actualKeyBytes = ChainWallet.GetKeyFromWifString("L3tgppXLgdaeqSGSFw1Go3skBiy8vQAM7YMXvTHsKQtE16PBncSU");
- CollectionAssert.AreEqual(expectedKeyBytes, actualKeyBytes);
+ Assert.AreSequenceEqual(expectedKeyBytes, actualKeyBytes);
}
[TestMethod]
@@ -53,7 +53,7 @@ public void TestNep2String()
var actualNep2String = ChainWallet.ToNep2String(expectedPrivateKeyBytes, "pwd", defaultSCryptParameters);
var actualPrivateKeyBytes = ChainWallet.GetKeyFromNep2String(actualNep2String, "pwd", defaultSCryptParameters);
- CollectionAssert.AreEqual(expectedPrivateKeyBytes, actualPrivateKeyBytes);
+ Assert.AreSequenceEqual(expectedPrivateKeyBytes, actualPrivateKeyBytes);
}
}
}
diff --git a/tests/Neo.Wallet.Tests/UT_DevWallet.cs b/tests/Neo.Wallet.Tests/UT_DevWallet.cs
index 8a7a952..a0573bf 100644
--- a/tests/Neo.Wallet.Tests/UT_DevWallet.cs
+++ b/tests/Neo.Wallet.Tests/UT_DevWallet.cs
@@ -51,9 +51,9 @@ public void TestCreateAccount()
Assert.AreEqual(expectedAccount.IsLocked, actualAccount.IsLocked);
Assert.AreEqual(expectedAccount.Contract.ScriptHash, actualAccount.Contract.ScriptHash);
- CollectionAssert.AreEqual(expectedAccount.Contract.Script, actualAccount.Contract.Script);
- CollectionAssert.AreEqual(expectedAccount.Contract.ParameterList, actualAccount.Contract.ParameterList);
- CollectionAssert.AreEqual(expectedAccount.GetPrivateKey(), actualAccount.GetPrivateKey());
+ Assert.AreSequenceEqual(expectedAccount.Contract.Script, actualAccount.Contract.Script);
+ Assert.AreSequenceEqual(expectedAccount.Contract.ParameterList, actualAccount.Contract.ParameterList);
+ Assert.AreSequenceEqual(expectedAccount.GetPrivateKey(), actualAccount.GetPrivateKey());
}
}
}
diff --git a/tests/Neo.Wallet.Tests/UT_Nep6Wallet.cs b/tests/Neo.Wallet.Tests/UT_Nep6Wallet.cs
index 0fc7c02..e1a2f36 100644
--- a/tests/Neo.Wallet.Tests/UT_Nep6Wallet.cs
+++ b/tests/Neo.Wallet.Tests/UT_Nep6Wallet.cs
@@ -57,9 +57,9 @@ public void TestCreateAccount()
Assert.AreEqual(expectedAccount.IsLocked, actualAccount.IsLocked);
Assert.AreEqual(expectedAccount.Contract.ScriptHash, actualAccount.Contract.ScriptHash);
- CollectionAssert.AreEqual(expectedAccount.Contract.Script, actualAccount.Contract.Script);
- CollectionAssert.AreEqual(expectedAccount.Contract.ParameterList, actualAccount.Contract.ParameterList);
- CollectionAssert.AreEqual(expectedAccount.GetPrivateKey(), actualAccount.GetPrivateKey());
+ Assert.AreSequenceEqual(expectedAccount.Contract.Script, actualAccount.Contract.Script);
+ Assert.AreSequenceEqual(expectedAccount.Contract.ParameterList, actualAccount.Contract.ParameterList);
+ Assert.AreSequenceEqual(expectedAccount.GetPrivateKey(), actualAccount.GetPrivateKey());
Assert.IsTrue(expectedAccount.ChangePassword("abc123", "pwd"));
Assert.IsTrue(actualAccount.ChangePassword("abc123", "pwd"));