diff --git a/src/Neo/NeoSystem.cs b/src/Neo/NeoSystem.cs
index 1fd9cc762c..8e7c4cf4af 100644
--- a/src/Neo/NeoSystem.cs
+++ b/src/Neo/NeoSystem.cs
@@ -84,7 +84,7 @@ public class NeoSystem : IDisposable
///
/// It doesn't need to be disposed because the inside it is null.
///
- public DataCache StoreView => new StoreCache(store);
+ public StoreCache StoreView => new(store);
///
/// The memory pool of the .
diff --git a/src/Neo/Persistence/ClonedCache.cs b/src/Neo/Persistence/ClonedCache.cs
index 3bb3b18b5c..726c55bb98 100644
--- a/src/Neo/Persistence/ClonedCache.cs
+++ b/src/Neo/Persistence/ClonedCache.cs
@@ -20,7 +20,7 @@ class ClonedCache : DataCache
{
private readonly DataCache _innerCache;
- public ClonedCache(DataCache innerCache)
+ public ClonedCache(DataCache innerCache) : base(false)
{
_innerCache = innerCache;
}
diff --git a/src/Neo/Persistence/DataCache.cs b/src/Neo/Persistence/DataCache.cs
index 54382faa22..e3314810f3 100644
--- a/src/Neo/Persistence/DataCache.cs
+++ b/src/Neo/Persistence/DataCache.cs
@@ -43,7 +43,12 @@ public class Trackable(StorageItem item, TrackState state)
}
private readonly Dictionary _dictionary = [];
- private readonly HashSet _changeSet = [];
+ private readonly HashSet? _changeSet;
+
+ ///
+ /// True if DataCache is readOnly
+ ///
+ public bool IsReadOnly => _changeSet == null;
///
/// Reads a specified entry from the cache. If the entry is not in the cache, it will be automatically loaded from the underlying storage.
@@ -72,6 +77,16 @@ public StorageItem this[StorageKey key]
}
}
+ ///
+ /// Data cache constructor
+ ///
+ /// True if you don't want to allow writes
+ protected DataCache(bool readOnly)
+ {
+ if (!readOnly)
+ _changeSet = [];
+ }
+
///
/// Adds a new entry to the cache.
///
@@ -97,7 +112,7 @@ public void Add(StorageKey key, StorageItem value)
{
_dictionary[key] = new Trackable(value, TrackState.Added);
}
- _changeSet.Add(key);
+ _changeSet?.Add(key);
}
}
@@ -113,6 +128,11 @@ public void Add(StorageKey key, StorageItem value)
///
public virtual void Commit()
{
+ if (_changeSet is null)
+ {
+ throw new InvalidOperationException("DataCache is read only");
+ }
+
lock (_dictionary)
{
foreach (var key in _changeSet)
@@ -138,6 +158,24 @@ public virtual void Commit()
}
}
+ ///
+ /// Gets the change set in the cache.
+ ///
+ /// The change set.
+ public IEnumerable> GetChangeSet()
+ {
+ if (_changeSet is null)
+ {
+ throw new InvalidOperationException("DataCache is read only");
+ }
+
+ lock (_dictionary)
+ {
+ foreach (var key in _changeSet)
+ yield return new(key, _dictionary[key]);
+ }
+ }
+
///
/// Creates a snapshot, which uses this instance as the underlying storage.
///
@@ -170,12 +208,12 @@ public void Delete(StorageKey key)
if (trackable.State == TrackState.Added)
{
trackable.State = TrackState.NotFound;
- _changeSet.Remove(key);
+ _changeSet?.Remove(key);
}
else if (trackable.State != TrackState.NotFound)
{
trackable.State = TrackState.Deleted;
- _changeSet.Add(key);
+ _changeSet?.Add(key);
}
}
else
@@ -183,7 +221,7 @@ public void Delete(StorageKey key)
var item = TryGetInternal(key);
if (item == null) return;
_dictionary.Add(key, new Trackable(item, TrackState.Deleted));
- _changeSet.Add(key);
+ _changeSet?.Add(key);
}
}
}
@@ -262,19 +300,6 @@ public void Delete(StorageKey key)
yield break;
}
- ///
- /// Gets the change set in the cache.
- ///
- /// The change set.
- public IEnumerable> GetChangeSet()
- {
- lock (_dictionary)
- {
- foreach (var key in _changeSet)
- yield return new(key, _dictionary[key]);
- }
- }
-
///
/// Determines whether the cache contains the specified entry.
///
@@ -337,13 +362,13 @@ public bool Contains(StorageKey key)
else
{
trackable.State = TrackState.Added;
- _changeSet.Add(key);
+ _changeSet?.Add(key);
}
}
else if (trackable.State == TrackState.None)
{
trackable.State = TrackState.Changed;
- _changeSet.Add(key);
+ _changeSet?.Add(key);
}
}
else
@@ -359,7 +384,7 @@ public bool Contains(StorageKey key)
trackable = new Trackable(item, TrackState.Changed);
}
_dictionary.Add(key, trackable);
- _changeSet.Add(key);
+ _changeSet?.Add(key);
}
return trackable.Item;
}
@@ -392,7 +417,7 @@ public StorageItem GetOrAdd(StorageKey key, Func factory)
else
{
trackable.State = TrackState.Added;
- _changeSet.Add(key);
+ _changeSet?.Add(key);
}
}
}
@@ -402,7 +427,7 @@ public StorageItem GetOrAdd(StorageKey key, Func factory)
if (item == null)
{
trackable = new Trackable(factory(), TrackState.Added);
- _changeSet.Add(key);
+ _changeSet?.Add(key);
}
else
{
diff --git a/src/Neo/Persistence/StoreCache.cs b/src/Neo/Persistence/StoreCache.cs
index 04084a0a11..a3848e14bc 100644
--- a/src/Neo/Persistence/StoreCache.cs
+++ b/src/Neo/Persistence/StoreCache.cs
@@ -31,7 +31,8 @@ public class StoreCache : DataCache, IDisposable
/// Initializes a new instance of the class.
///
/// An to create a readonly cache.
- public StoreCache(IStore store)
+ /// True if you don't want to track write changes
+ public StoreCache(IStore store, bool readOnly = true) : base(readOnly)
{
_store = store;
}
@@ -40,7 +41,7 @@ public StoreCache(IStore store)
/// Initializes a new instance of the class.
///
/// An to create a snapshot cache.
- public StoreCache(IStoreSnapshot snapshot)
+ public StoreCache(IStoreSnapshot snapshot) : base(false)
{
_store = snapshot;
_snapshot = snapshot;
diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs
index 6cf2431d67..9bfbfdc36b 100644
--- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs
+++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs
@@ -342,7 +342,7 @@ public void TestListAddress_WhenWalletNotOpen()
public void TestCancelTransaction()
{
TestUtilOpenWallet();
- var snapshot = _neoSystem.GetSnapshot();
+ var snapshot = _neoSystem.GetSnapshotCache();
var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount);
snapshot.Commit();
var paramsArray = new JArray(tx.Hash.ToString(), new JArray(_walletAccount.Address));
diff --git a/tests/Neo.UnitTests/Persistence/UT_CloneCache.cs b/tests/Neo.UnitTests/Persistence/UT_CloneCache.cs
index 21ca3704fe..2b5b7faaaa 100644
--- a/tests/Neo.UnitTests/Persistence/UT_CloneCache.cs
+++ b/tests/Neo.UnitTests/Persistence/UT_CloneCache.cs
@@ -24,121 +24,131 @@ namespace Neo.UnitTests.IO.Caching
[TestClass]
public class UT_CloneCache
{
- private readonly MemoryStore store = new();
- private StoreCache myDataCache;
- private ClonedCache clonedCache;
-
- private static readonly StorageKey key1 = new() { Id = 0, Key = Encoding.UTF8.GetBytes("key1") };
- private static readonly StorageKey key2 = new() { Id = 0, Key = Encoding.UTF8.GetBytes("key2") };
- private static readonly StorageKey key3 = new() { Id = 0, Key = Encoding.UTF8.GetBytes("key3") };
- private static readonly StorageKey key4 = new() { Id = 0, Key = Encoding.UTF8.GetBytes("key4") };
-
- private static readonly StorageItem value1 = new(Encoding.UTF8.GetBytes("value1"));
- private static readonly StorageItem value2 = new(Encoding.UTF8.GetBytes("value2"));
- private static readonly StorageItem value3 = new(Encoding.UTF8.GetBytes("value3"));
- private static readonly StorageItem value4 = new(Encoding.UTF8.GetBytes("value4"));
-
- [TestInitialize]
- public void Init()
- {
- myDataCache = new(store);
- clonedCache = new ClonedCache(myDataCache);
- }
+ private readonly MemoryStore _store = new();
+
+ private static readonly StorageKey s_key1 = new() { Id = 0, Key = Encoding.UTF8.GetBytes("key1") };
+ private static readonly StorageKey s_key2 = new() { Id = 0, Key = Encoding.UTF8.GetBytes("key2") };
+ private static readonly StorageKey s_key3 = new() { Id = 0, Key = Encoding.UTF8.GetBytes("key3") };
+ private static readonly StorageKey s_key4 = new() { Id = 0, Key = Encoding.UTF8.GetBytes("key4") };
+
+ private static readonly StorageItem s_value1 = new(Encoding.UTF8.GetBytes("value1"));
+ private static readonly StorageItem s_value2 = new(Encoding.UTF8.GetBytes("value2"));
+ private static readonly StorageItem s_value3 = new(Encoding.UTF8.GetBytes("value3"));
[TestMethod]
public void TestCloneCache()
{
+ var myDataCache = new StoreCache(_store);
+ var clonedCache = myDataCache.CloneCache();
Assert.IsNotNull(clonedCache);
}
[TestMethod]
public void TestAddInternal()
{
- clonedCache.Add(key1, value1);
- Assert.AreEqual(value1, clonedCache[key1]);
+ var myDataCache = new StoreCache(_store);
+ var clonedCache = myDataCache.CloneCache();
+
+ clonedCache.Add(s_key1, s_value1);
+ Assert.AreEqual(s_value1, clonedCache[s_key1]);
clonedCache.Commit();
- Assert.IsTrue(myDataCache[key1].Value.Span.SequenceEqual(value1.Value.Span));
+ Assert.IsTrue(myDataCache[s_key1].Value.Span.SequenceEqual(s_value1.Value.Span));
}
[TestMethod]
public void TestDeleteInternal()
{
- myDataCache.Add(key1, value1);
- clonedCache.Delete(key1); // trackable.State = TrackState.Deleted
+ var myDataCache = new StoreCache(_store);
+ var clonedCache = myDataCache.CloneCache();
+
+ myDataCache.Add(s_key1, s_value1);
+ clonedCache.Delete(s_key1); // trackable.State = TrackState.Deleted
clonedCache.Commit();
- Assert.IsNull(clonedCache.TryGet(key1));
- Assert.IsNull(myDataCache.TryGet(key1));
+ Assert.IsNull(clonedCache.TryGet(s_key1));
+ Assert.IsNull(myDataCache.TryGet(s_key1));
}
[TestMethod]
public void TestFindInternal()
{
- clonedCache.Add(key1, value1);
- myDataCache.Add(key2, value2);
- store.Put(key3.ToArray(), value3.ToArray());
+ var myDataCache = new StoreCache(_store);
+ var clonedCache = myDataCache.CloneCache();
- var items = clonedCache.Find(key1.ToArray());
- Assert.AreEqual(key1, items.ElementAt(0).Key);
- Assert.AreEqual(value1, items.ElementAt(0).Value);
+ clonedCache.Add(s_key1, s_value1);
+ myDataCache.Add(s_key2, s_value2);
+ _store.Put(s_key3.ToArray(), s_value3.ToArray());
+
+ var items = clonedCache.Find(s_key1.ToArray());
+ Assert.AreEqual(s_key1, items.ElementAt(0).Key);
+ Assert.AreEqual(s_value1, items.ElementAt(0).Value);
Assert.AreEqual(1, items.Count());
- items = clonedCache.Find(key2.ToArray());
- Assert.AreEqual(key2, items.ElementAt(0).Key);
- Assert.IsTrue(value2.EqualsTo(items.ElementAt(0).Value));
+ items = clonedCache.Find(s_key2.ToArray());
+ Assert.AreEqual(s_key2, items.ElementAt(0).Key);
+ Assert.IsTrue(s_value2.EqualsTo(items.ElementAt(0).Value));
Assert.AreEqual(1, items.Count());
- items = clonedCache.Find(key3.ToArray());
- Assert.AreEqual(key3, items.ElementAt(0).Key);
- Assert.IsTrue(value3.EqualsTo(items.ElementAt(0).Value));
+ items = clonedCache.Find(s_key3.ToArray());
+ Assert.AreEqual(s_key3, items.ElementAt(0).Key);
+ Assert.IsTrue(s_value3.EqualsTo(items.ElementAt(0).Value));
Assert.AreEqual(1, items.Count());
- items = clonedCache.Find(key4.ToArray());
+ items = clonedCache.Find(s_key4.ToArray());
Assert.AreEqual(0, items.Count());
}
[TestMethod]
public void TestGetInternal()
{
- clonedCache.Add(key1, value1);
- myDataCache.Add(key2, value2);
- store.Put(key3.ToArray(), value3.ToArray());
+ var myDataCache = new StoreCache(_store);
+ var clonedCache = myDataCache.CloneCache();
+
+ clonedCache.Add(s_key1, s_value1);
+ myDataCache.Add(s_key2, s_value2);
+ _store.Put(s_key3.ToArray(), s_value3.ToArray());
- Assert.IsTrue(value1.EqualsTo(clonedCache[key1]));
- Assert.IsTrue(value2.EqualsTo(clonedCache[key2]));
- Assert.IsTrue(value3.EqualsTo(clonedCache[key3]));
+ Assert.IsTrue(s_value1.EqualsTo(clonedCache[s_key1]));
+ Assert.IsTrue(s_value2.EqualsTo(clonedCache[s_key2]));
+ Assert.IsTrue(s_value3.EqualsTo(clonedCache[s_key3]));
- Action action = () =>
+ void Action()
{
- var item = clonedCache[key4];
- };
- Assert.ThrowsExactly(action);
+ var item = clonedCache[s_key4];
+ }
+ Assert.ThrowsExactly(Action);
}
[TestMethod]
public void TestTryGetInternal()
{
- clonedCache.Add(key1, value1);
- myDataCache.Add(key2, value2);
- store.Put(key3.ToArray(), value3.ToArray());
-
- Assert.IsTrue(value1.EqualsTo(clonedCache.TryGet(key1)));
- Assert.IsTrue(value2.EqualsTo(clonedCache.TryGet(key2)));
- Assert.IsTrue(value3.EqualsTo(clonedCache.TryGet(key3)));
- Assert.IsNull(clonedCache.TryGet(key4));
+ var myDataCache = new StoreCache(_store);
+ var clonedCache = myDataCache.CloneCache();
+
+ clonedCache.Add(s_key1, s_value1);
+ myDataCache.Add(s_key2, s_value2);
+ _store.Put(s_key3.ToArray(), s_value3.ToArray());
+
+ Assert.IsTrue(s_value1.EqualsTo(clonedCache.TryGet(s_key1)));
+ Assert.IsTrue(s_value2.EqualsTo(clonedCache.TryGet(s_key2)));
+ Assert.IsTrue(s_value3.EqualsTo(clonedCache.TryGet(s_key3)));
+ Assert.IsNull(clonedCache.TryGet(s_key4));
}
[TestMethod]
public void TestUpdateInternal()
{
- clonedCache.Add(key1, value1);
- myDataCache.Add(key2, value2);
- store.Put(key3.ToArray(), value3.ToArray());
+ var myDataCache = new StoreCache(_store);
+ var clonedCache = myDataCache.CloneCache();
+
+ clonedCache.Add(s_key1, s_value1);
+ myDataCache.Add(s_key2, s_value2);
+ _store.Put(s_key3.ToArray(), s_value3.ToArray());
- clonedCache.GetAndChange(key1).Value = Encoding.Default.GetBytes("value_new_1");
- clonedCache.GetAndChange(key2).Value = Encoding.Default.GetBytes("value_new_2");
- clonedCache.GetAndChange(key3).Value = Encoding.Default.GetBytes("value_new_3");
+ clonedCache.GetAndChange(s_key1).Value = Encoding.Default.GetBytes("value_new_1");
+ clonedCache.GetAndChange(s_key2).Value = Encoding.Default.GetBytes("value_new_2");
+ clonedCache.GetAndChange(s_key3).Value = Encoding.Default.GetBytes("value_new_3");
clonedCache.Commit();
@@ -146,10 +156,10 @@ public void TestUpdateInternal()
StorageItem value_new_2 = new(Encoding.UTF8.GetBytes("value_new_2"));
StorageItem value_new_3 = new(Encoding.UTF8.GetBytes("value_new_3"));
- Assert.IsTrue(value_new_1.EqualsTo(clonedCache[key1]));
- Assert.IsTrue(value_new_2.EqualsTo(clonedCache[key2]));
- Assert.IsTrue(value_new_3.EqualsTo(clonedCache[key3]));
- Assert.IsTrue(value_new_2.EqualsTo(clonedCache[key2]));
+ Assert.IsTrue(value_new_1.EqualsTo(clonedCache[s_key1]));
+ Assert.IsTrue(value_new_2.EqualsTo(clonedCache[s_key2]));
+ Assert.IsTrue(value_new_3.EqualsTo(clonedCache[s_key3]));
+ Assert.IsTrue(value_new_2.EqualsTo(clonedCache[s_key2]));
}
[TestMethod]
diff --git a/tests/Neo.UnitTests/Persistence/UT_DataCache.cs b/tests/Neo.UnitTests/Persistence/UT_DataCache.cs
index 6971117171..a467f570c2 100644
--- a/tests/Neo.UnitTests/Persistence/UT_DataCache.cs
+++ b/tests/Neo.UnitTests/Persistence/UT_DataCache.cs
@@ -42,7 +42,7 @@ public class UT_DataCache
[TestInitialize]
public void Initialize()
{
- myDataCache = new(store);
+ myDataCache = new(store, false);
}
[TestMethod]
diff --git a/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs b/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs
index 7abfc538c5..d54e40814b 100644
--- a/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs
+++ b/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs
@@ -14,6 +14,7 @@
using Neo.Persistence;
using Neo.Persistence.Providers;
using Neo.SmartContract;
+using System;
using System.Linq;
using System.Text;
@@ -84,7 +85,7 @@ public void NeoSystemStoreViewTest()
var value = new StorageItem(Encoding.UTF8.GetBytes("testValue"));
store.Add(key, value);
- store.Commit();
+ _ = Assert.ThrowsExactly(store.Commit);
var result = store.TryGet(key);
// The StoreView is a readonly view of the store, here it will have value in the cache
diff --git a/tests/Neo.UnitTests/Persistence/UT_ReadOnlyStoreView.cs b/tests/Neo.UnitTests/Persistence/UT_ReadOnlyStore.cs
similarity index 70%
rename from tests/Neo.UnitTests/Persistence/UT_ReadOnlyStoreView.cs
rename to tests/Neo.UnitTests/Persistence/UT_ReadOnlyStore.cs
index d749d9663e..b3007c740e 100644
--- a/tests/Neo.UnitTests/Persistence/UT_ReadOnlyStoreView.cs
+++ b/tests/Neo.UnitTests/Persistence/UT_ReadOnlyStore.cs
@@ -1,6 +1,6 @@
// Copyright (C) 2015-2025 The Neo Project.
//
-// UT_ReadOnlyStoreView.cs file belongs to the neo project and is free
+// UT_ReadOnlyStore.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
@@ -13,13 +13,31 @@
using Neo.Persistence;
using Neo.Persistence.Providers;
using Neo.SmartContract;
+using System;
using System.Collections.Generic;
+using System.Linq;
namespace Neo.UnitTests.Persistence
{
[TestClass]
- public class UT_ReadOnlyStoreView
+ public class UT_ReadOnlyStore
{
+ [TestMethod]
+ public void TestError()
+ {
+ var store = new MemoryStore();
+ var snapshot = new StoreCache(store);
+
+ Assert.ThrowsExactly(() => snapshot.GetChangeSet().ToArray());
+ Assert.ThrowsExactly(snapshot.Commit);
+
+ // No errors
+
+ snapshot = new StoreCache(store.GetSnapshot());
+ _ = snapshot.GetChangeSet().ToArray();
+ snapshot.Commit();
+ }
+
[TestMethod]
public void TestReadOnlyStoreView()
{