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() {