Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Neo Core MemoryStore] MemoryStore Unit Tests. #3404

Merged
merged 17 commits into from
Jul 15, 2024
Merged
155 changes: 139 additions & 16 deletions tests/Neo.Plugins.Storage.Tests/StoreTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,21 @@ public class StoreTest
{
private const string path_leveldb = "Data_LevelDB_UT";
private const string path_rocksdb = "Data_RocksDB_UT";
private static LevelDBStore levelDbStore;
private static RocksDBStore rocksDBStore;

[TestInitialize]
public void OnStart()
{
if (Directory.Exists(path_leveldb)) Directory.Delete(path_leveldb, true);
if (Directory.Exists(path_rocksdb)) Directory.Delete(path_rocksdb, true);
levelDbStore ??= new LevelDBStore();
rocksDBStore ??= new RocksDBStore();
ReSetStore();
}

[TestCleanup]
public void OnEnd()
{
ReSetStore();
}

[TestMethod]
Expand All @@ -49,35 +58,143 @@ public void TestMemory()
[TestMethod]
public void TestLevelDb()
{
using var plugin = new LevelDBStore();
TestPersistenceDelete(plugin.GetStore(path_leveldb));
ReSetStore();
TestPersistenceDelete(levelDbStore.GetStore(path_leveldb));
// Test all with the same store

TestStorage(plugin.GetStore(path_leveldb));
TestStorage(levelDbStore.GetStore(path_leveldb));

// Test with different storages

TestPersistenceWrite(plugin.GetStore(path_leveldb));
TestPersistenceRead(plugin.GetStore(path_leveldb), true);
TestPersistenceDelete(plugin.GetStore(path_leveldb));
TestPersistenceRead(plugin.GetStore(path_leveldb), false);
TestPersistenceWrite(levelDbStore.GetStore(path_leveldb));
TestPersistenceRead(levelDbStore.GetStore(path_leveldb), true);
TestPersistenceDelete(levelDbStore.GetStore(path_leveldb));
TestPersistenceRead(levelDbStore.GetStore(path_leveldb), false);
}

[TestMethod]
public void TestLevelDbSnapshot()
{
ReSetStore();
using var store = levelDbStore.GetStore(path_leveldb);

var snapshot = store.GetSnapshot();

var testKey = new byte[] { 0x01, 0x02, 0x03 };
var testValue = new byte[] { 0x04, 0x05, 0x06 };

snapshot.Put(testKey, testValue);
// Data saved to the leveldb snapshot shall not be visible to the store
Assert.IsNull(snapshot.TryGet(testKey));

// Value is in the write batch, not visible to the store and snapshot
Assert.AreEqual(false, snapshot.Contains(testKey));
Assert.AreEqual(false, store.Contains(testKey));

snapshot.Commit();

// After commit, the data shall be visible to the store but not to the snapshot
Assert.IsNull(snapshot.TryGet(testKey));
CollectionAssert.AreEqual(testValue, store.TryGet(testKey));
Assert.AreEqual(false, snapshot.Contains(testKey));
Assert.AreEqual(true, store.Contains(testKey));

snapshot.Dispose();
}

[TestMethod]
public void TestLevelDbMultiSnapshot()
{
ReSetStore();
using var store = levelDbStore.GetStore(path_leveldb);

var snapshot = store.GetSnapshot();

var testKey = new byte[] { 0x01, 0x02, 0x03 };
var testValue = new byte[] { 0x04, 0x05, 0x06 };

snapshot.Put(testKey, testValue);
snapshot.Commit();
CollectionAssert.AreEqual(testValue, store.TryGet(testKey));

var snapshot2 = store.GetSnapshot();

// Data saved to the leveldb from snapshot1 shall be visible to snapshot2 but not visible to snapshot1
CollectionAssert.AreEqual(testValue, snapshot2.TryGet(testKey));
Assert.IsNull(snapshot.TryGet(testKey));

snapshot.Dispose();
snapshot2.Dispose();
}

[TestMethod]
public void TestRocksDb()
{
using var plugin = new RocksDBStore();
TestPersistenceDelete(plugin.GetStore(path_rocksdb));
TestPersistenceDelete(rocksDBStore.GetStore(path_rocksdb));
// Test all with the same store

TestStorage(plugin.GetStore(path_rocksdb));
TestStorage(rocksDBStore.GetStore(path_rocksdb));

// Test with different storages

TestPersistenceWrite(plugin.GetStore(path_rocksdb));
TestPersistenceRead(plugin.GetStore(path_rocksdb), true);
TestPersistenceDelete(plugin.GetStore(path_rocksdb));
TestPersistenceRead(plugin.GetStore(path_rocksdb), false);
TestPersistenceWrite(rocksDBStore.GetStore(path_rocksdb));
TestPersistenceRead(rocksDBStore.GetStore(path_rocksdb), true);
TestPersistenceDelete(rocksDBStore.GetStore(path_rocksdb));
TestPersistenceRead(rocksDBStore.GetStore(path_rocksdb), false);
}

[TestMethod]
public void TestRocksDbSnapshot()
{
ReSetStore();
using var store = rocksDBStore.GetStore(path_leveldb);

var snapshot = store.GetSnapshot();

var testKey = new byte[] { 0x01, 0x02, 0x03 };
var testValue = new byte[] { 0x04, 0x05, 0x06 };

snapshot.Put(testKey, testValue);
// Data saved to the leveldb snapshot shall not be visible
Assert.IsNull(snapshot.TryGet(testKey));
Assert.IsNull(store.TryGet(testKey));

// Value is in the write batch, not visible to the store and snapshot
Assert.AreEqual(false, snapshot.Contains(testKey));
Assert.AreEqual(false, store.Contains(testKey));

snapshot.Commit();

// After commit, the data shall be visible to the store but not to the snapshot
Assert.IsNull(snapshot.TryGet(testKey));
CollectionAssert.AreEqual(testValue, store.TryGet(testKey));
Assert.AreEqual(false, snapshot.Contains(testKey));
Assert.AreEqual(true, store.Contains(testKey));

snapshot.Dispose();
}

[TestMethod]
public void TestRocksDbMultiSnapshot()
{
ReSetStore();
using var store = rocksDBStore.GetStore(path_leveldb);

var snapshot = store.GetSnapshot();

var testKey = new byte[] { 0x01, 0x02, 0x03 };
var testValue = new byte[] { 0x04, 0x05, 0x06 };

snapshot.Put(testKey, testValue);
snapshot.Commit();
CollectionAssert.AreEqual(testValue, store.TryGet(testKey));

var snapshot2 = store.GetSnapshot();
// Data saved to the leveldb from snapshot1 shall only be visible to snapshot2
CollectionAssert.AreEqual(testValue, snapshot2.TryGet(testKey));

snapshot.Dispose();
snapshot2.Dispose();
}

/// <summary>
Expand Down Expand Up @@ -196,5 +313,11 @@ private void TestPersistenceRead(IStore store, bool shouldExist)
else Assert.IsNull(ret);
}
}

private void ReSetStore()
{
if (Directory.Exists(path_leveldb)) Directory.Delete(path_leveldb, true);
if (Directory.Exists(path_rocksdb)) Directory.Delete(path_rocksdb, true);
}
}
}
107 changes: 107 additions & 0 deletions tests/Neo.UnitTests/Persistence/UT_MemoryClonedCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// UT_MemoryClonedCache.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
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.Persistence;
using Neo.SmartContract;

namespace Neo.UnitTests.Persistence;

[TestClass]
public class UT_MemoryClonedCache
{
private MemoryStore _memoryStore;
private MemorySnapshot _snapshot;
private SnapshotCache _snapshotCache;
private DataCache _dataCache;

[TestInitialize]
public void Setup()
{
_memoryStore = new MemoryStore();
_snapshot = _memoryStore.GetSnapshot() as MemorySnapshot;
_snapshotCache = new SnapshotCache(_snapshot);
_dataCache = _snapshotCache.CreateSnapshot();
}

[TestCleanup]
public void CleanUp()
{
_dataCache.Commit();
_snapshotCache.Commit();
_memoryStore.Reset();
}

[TestMethod]
public void SingleSnapshotCacheTest()
{
var key1 = new KeyBuilder(0, 1);
var value1 = new StorageItem([0x03, 0x04]);

_dataCache.Add(key1, value1);

Assert.IsTrue(_dataCache.Contains(key1));
Assert.IsFalse(_snapshotCache.Contains(key1));
Assert.IsFalse(_snapshot.Contains(key1.ToArray()));
Assert.IsFalse(_memoryStore.Contains(key1.ToArray()));

// After the data cache is committed, it should be dropped
// so its value after the commit is meaningless and should not be used.
_dataCache.Commit();

Assert.IsTrue(_dataCache.Contains(key1));
Assert.IsTrue(_snapshotCache.Contains(key1));
Assert.IsFalse(_snapshot.Contains(key1.ToArray()));
Assert.IsFalse(_memoryStore.Contains(key1.ToArray()));
Comment on lines +73 to +80
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't agree with this comment (and with related comments in this PR). One of the purposes of DataCache is to serve sequential block's handling in Blockchain (the main part of all Blockchain: OnPersist + transactions handling + PostPersist). And there DataCache usage goes against this comment: we create a single DataCache instance that should be shared between subsequent transaction runs. If transaction is HALTed, then DataCache is being committed and then it's being reused for the next transaction processing, so next transactions can see the changes made by the previous transaction even after DataCacke commit.

So I think that reusage after commit is a substantial part of the DataCache design for sequential execution and can't be prohibited like it's said in the comment.

DataCache clonedSnapshot = snapshot.CreateSnapshot();
// Warning: Do not write into variable snapshot directly. Write into variable clonedSnapshot and commit instead.
foreach (TransactionState transactionState in transactionStates)
{
Transaction tx = transactionState.Transaction;
using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, tx, clonedSnapshot, block, system.Settings, tx.SystemFee);
engine.LoadScript(tx.Script);
transactionState.State = engine.Execute();
if (transactionState.State == VMState.HALT)
{
clonedSnapshot.Commit();
}
else
{
clonedSnapshot = snapshot.CreateSnapshot();
}
ApplicationExecuted application_executed = new(engine);
Context.System.EventStream.Publish(application_executed);
all_application_executed.Add(application_executed);
}

Parallel execution is different although, and I'm not really sure that it's designed for parallel execution, I'm not that familiar with all DataCache usages.

Copy link
Contributor Author

@Jim8y Jim8y Jul 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't agree with this comment

@AnnaShaleva As i have said everywhere multiple times, the real problem is a committed snapshotcache and clonedcache no longer have the latest store states, that is the reason. You can not agree with what? like any type of execution should be run based on an out dated snapshot? Why would you want to run execution on old store states and get wrong execution result?


// After the snapshot is committed, it should be dropped
// so its value after the commit is meaningless and should not be used.
_snapshotCache.Commit();

Assert.IsTrue(_dataCache.Contains(key1));
Assert.IsTrue(_snapshotCache.Contains(key1));
Assert.IsFalse(_snapshot.Contains(key1.ToArray()));
Assert.IsTrue(_memoryStore.Contains(key1.ToArray()));

// Test delete

// Reset the snapshot to make it accessible to the new value.
_snapshot = _memoryStore.GetSnapshot() as MemorySnapshot;
_snapshotCache = new SnapshotCache(_snapshot);
_dataCache = _snapshotCache.CreateSnapshot();

_dataCache.Delete(key1);

Assert.IsFalse(_dataCache.Contains(key1));
Assert.IsTrue(_snapshotCache.Contains(key1));
Assert.IsTrue(_snapshot.Contains(key1.ToArray()));
Assert.IsTrue(_memoryStore.Contains(key1.ToArray()));

// After the data cache is committed, it should be dropped
// so its value after the commit is meaningless and should not be used.
_dataCache.Commit();

Assert.IsFalse(_dataCache.Contains(key1));
Assert.IsFalse(_snapshotCache.Contains(key1));
Assert.IsTrue(_snapshot.Contains(key1.ToArray()));
Assert.IsTrue(_memoryStore.Contains(key1.ToArray()));


// After the snapshot cache is committed, it should be dropped
// so its value after the commit is meaningless and should not be used.
_snapshotCache.Commit();

Assert.IsTrue(_dataCache.Contains(key1));
Assert.IsTrue(_snapshotCache.Contains(key1));
Assert.IsTrue(_snapshot.Contains(key1.ToArray()));
Assert.IsFalse(_memoryStore.Contains(key1.ToArray()));
}
}
Loading
Loading