diff --git a/MPTStates.UnitTests/MPTStates.UnitTests.csproj b/MPTStates.UnitTests/MPTStates.UnitTests.csproj
new file mode 100644
index 000000000..2c768ea92
--- /dev/null
+++ b/MPTStates.UnitTests/MPTStates.UnitTests.csproj
@@ -0,0 +1,28 @@
+
+
+ Exe
+ netcoreapp2.0
+ MPTStates.UnitTests
+ MPTStates.UnitTests
+ true
+
+
+
+ runtime; build; native; contentfiles; analyzers
+ all
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MPTStates.UnitTests/TestDataCache.cs b/MPTStates.UnitTests/TestDataCache.cs
new file mode 100644
index 000000000..68f3b07da
--- /dev/null
+++ b/MPTStates.UnitTests/TestDataCache.cs
@@ -0,0 +1,52 @@
+using Neo.IO;
+using Neo.IO.Caching;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Neo.UnitTests
+{
+ public class TestDataCache : DataCache
+ where TKey : IEquatable, ISerializable
+ where TValue : class, ICloneable, ISerializable, new()
+ {
+ private readonly TValue _defaultValue;
+
+ public TestDataCache()
+ {
+ _defaultValue = null;
+ }
+
+ public TestDataCache(TValue defaultValue)
+ {
+ this._defaultValue = defaultValue;
+ }
+ public override void DeleteInternal(TKey key)
+ {
+ }
+
+ protected override void AddInternal(TKey key, TValue value)
+ {
+ }
+
+ protected override IEnumerable> FindInternal(byte[] key_prefix)
+ {
+ return Enumerable.Empty>();
+ }
+
+ protected override TValue GetInternal(TKey key)
+ {
+ if (_defaultValue == null) throw new NotImplementedException();
+ return _defaultValue;
+ }
+
+ protected override TValue TryGetInternal(TKey key)
+ {
+ return _defaultValue;
+ }
+
+ protected override void UpdateInternal(TKey key, TValue value)
+ {
+ }
+ }
+}
diff --git a/MPTStates.UnitTests/TestDataCacheWithInternal.cs b/MPTStates.UnitTests/TestDataCacheWithInternal.cs
new file mode 100644
index 000000000..b0de3bff2
--- /dev/null
+++ b/MPTStates.UnitTests/TestDataCacheWithInternal.cs
@@ -0,0 +1,33 @@
+using Neo.IO;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Neo.UnitTests
+{
+ public class TestDataCacheWithInternal : TestDataCache
+ where TKey : IEquatable, ISerializable
+ where TValue : class, ICloneable, ISerializable, new()
+ {
+ private readonly Dictionary _internal = new Dictionary();
+
+ public override void DeleteInternal(TKey key) => _internal.Remove(key);
+
+ protected override void AddInternal(TKey key, TValue value) => _internal.Add(key, value);
+
+ protected override IEnumerable> FindInternal(byte[] key_prefix)
+ {
+ return Enumerable.Empty>();
+ }
+
+ protected override TValue GetInternal(TKey key) => _internal[key];
+
+ protected override TValue TryGetInternal(TKey key)
+ {
+ _internal.TryGetValue(key, out var value);
+ return value;
+ }
+
+ protected override void UpdateInternal(TKey key, TValue value) => _internal[key] = value;
+ }
+}
\ No newline at end of file
diff --git a/MPTStates.UnitTests/UT_MerklePatricia.cs b/MPTStates.UnitTests/UT_MerklePatricia.cs
new file mode 100644
index 000000000..49815bdd7
--- /dev/null
+++ b/MPTStates.UnitTests/UT_MerklePatricia.cs
@@ -0,0 +1,521 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Neo.Plugins.MPT;
+
+namespace Neo.UnitTests.Ledger
+{
+ [TestClass]
+ public class UT_MerklePatricia
+ {
+ private static MerklePatriciaTree NewTree() => new MerklePatriciaTree();
+
+ private static void EqualsAfterRemove(List values, int selected = 0, int? max = null)
+ {
+ values = new List(new HashSet(values));
+ for (max = max ?? 1 << values.Count; selected < max; selected++)
+ {
+ var inserted = NewTree();
+ var insertedRemoved = NewTree();
+ var insertedRemovedBackwards = NewTree();
+
+ for (var i = 0; i < values.Count; i++)
+ {
+ if (selected.GetBit(i))
+ {
+ inserted[values[i]] = values[i];
+ }
+
+ insertedRemoved[values[i]] = values[i];
+ insertedRemovedBackwards[values[i]] = values[i];
+ }
+
+ for (var i = 0; i < values.Count; i++)
+ {
+ if (selected.GetBit(i)) continue;
+ insertedRemoved.Remove(values[i]);
+ }
+
+ for (var i = values.Count - 1; i >= 0; i--)
+ {
+ if (selected.GetBit(i)) continue;
+ insertedRemovedBackwards.Remove(values[i]);
+ }
+
+ var testMessage = $"Test {selected}/{max}";
+ var testBackwardsMessage = $"Test backwards {selected}/{max}";
+ for (var i = 0; i < values.Count; i++)
+ {
+ if (selected.GetBit(i))
+ {
+ Assert.AreEqual(values[i], inserted[values[i]], testMessage);
+ Assert.AreEqual(values[i], insertedRemoved[values[i]], testMessage);
+ Assert.AreEqual(values[i], insertedRemovedBackwards[values[i]], testBackwardsMessage);
+ }
+ else
+ {
+ Assert.IsFalse(inserted.ContainsKey(values[i]), testMessage);
+ Assert.IsFalse(insertedRemoved.ContainsKey(values[i]), testMessage);
+ Assert.IsFalse(insertedRemovedBackwards.ContainsKey(values[i]), testBackwardsMessage);
+ }
+ }
+
+ Assert.AreEqual(inserted, insertedRemoved, testMessage);
+ Assert.AreEqual(inserted, insertedRemovedBackwards, testBackwardsMessage);
+ // At this point insertedRemoved should be equals to insertedRemovedBackwards so it must be
+ Assert.AreEqual(insertedRemoved, insertedRemovedBackwards, testBackwardsMessage);
+ }
+ }
+
+ [TestMethod]
+ public void EqualsAfterRemove()
+ {
+ EqualsAfterRemove(new List {"a", "ab", "ba", "bb", "zba"}, 3);
+
+ EqualsAfterRemove(new List {"a"});
+ EqualsAfterRemove(new List {"a", "ab"});
+ EqualsAfterRemove(new List {"a", "ab", "ba"});
+ EqualsAfterRemove(new List {"a", "ab", "ba", "bb"});
+ EqualsAfterRemove(new List {"a", "ab", "ba", "bb", "zba"});
+ EqualsAfterRemove(new List {"a", "ab", "ba", "bb", "zba", "cba"});
+ EqualsAfterRemove(new List {"a", "b", "aaaa", "baaa", "aaab", "baab"});
+
+ EqualsAfterRemove(new List {"a", "b", "aaaa", "baaa", "aaab", "baab"});
+ }
+
+ private static void CheckCopy(MerklePatriciaTree mp)
+ {
+ var cloned = NewTree();
+ using (var ms = new MemoryStream())
+ using (var bw = new BinaryWriter(ms))
+ {
+ mp.Serialize(bw);
+ using (var br = new BinaryReader(bw.BaseStream))
+ {
+ br.BaseStream.Position = 0;
+ cloned.Deserialize(br);
+ }
+ }
+
+ Assert.IsTrue(mp.Validate());
+ Assert.IsTrue(cloned.Validate());
+ Assert.AreEqual(mp, cloned);
+
+ cloned = mp.Clone();
+ Assert.IsTrue(mp.Validate());
+ Assert.IsTrue(cloned.Validate());
+ Assert.AreEqual(mp, cloned);
+
+ cloned = NewTree();
+ cloned.FromReplica(mp);
+ Assert.IsTrue(mp.Validate());
+ Assert.IsTrue(cloned.Validate());
+ Assert.AreEqual(mp, cloned);
+ }
+
+ [TestMethod]
+ public void DistinctRoot()
+ {
+ var mp = NewTree();
+ Assert.IsFalse(mp == null);
+ mp[new byte[] {0x0, 0x0, 0x1}] = new byte[] {0x0, 0x0, 0x1};
+ Assert.IsTrue(new byte[] {0x0, 0x0, 0x1}.SequenceEqual(mp[new byte[] {0x0, 0x0, 0x1}]));
+
+ mp[new byte[] {0x11, 0x0, 0x2}] = new byte[] {0x11, 0x0, 0x2};
+ Assert.IsTrue(new byte[] {0x11, 0x0, 0x2}.SequenceEqual(mp[new byte[] {0x11, 0x0, 0x2}]));
+ Assert.IsNull(mp[new byte[] {0x20, 0x0, 0x2}]);
+
+ CheckCopy(mp);
+ }
+
+ [TestMethod]
+ public void SameRoot()
+ {
+ var mp = NewTree();
+ Assert.IsFalse(mp == null);
+ mp[new byte[] {0x0, 0x0, 0x1}] = new byte[] {0x0, 0x0, 0x1};
+ Assert.IsTrue(new byte[] {0x0, 0x0, 0x1}.SequenceEqual(mp[new byte[] {0x0, 0x0, 0x1}]));
+
+ mp[new byte[] {0x0, 0x0, 0x1}] = new byte[] {0x0, 0x0, 0x2};
+ Assert.IsTrue(new byte[] {0x0, 0x0, 0x2}.SequenceEqual(mp[new byte[] {0x0, 0x0, 0x1}]));
+
+ CheckCopy(mp);
+ }
+
+ [TestMethod]
+ public void EndsOnExtension()
+ {
+ var mp = NewTree();
+ mp["bola"] = "bola";
+ mp["bolo"] = "bolo";
+
+ Assert.AreEqual("bola", mp["bola"]);
+ Assert.AreEqual("bolo", mp["bolo"]);
+ Assert.AreEqual(2, mp.Count());
+ mp["bol"] = "bol";
+ Assert.AreEqual("bol", mp["bol"]);
+ mp["aol"] = "aol";
+ Assert.AreEqual("aol", mp["aol"]);
+ mp["zol"] = "zol";
+ Assert.AreEqual("zol", mp["zol"]);
+ mp["bala"] = "bala";
+ Assert.AreEqual("bala", mp["bala"]);
+ mp["bo"] = "bo";
+ Assert.AreEqual("bo", mp["bo"]);
+ Assert.AreEqual(7, mp.Count());
+
+ Assert.AreEqual("aol", mp["aol"]);
+ Assert.AreEqual("zol", mp["zol"]);
+ Assert.AreEqual("bol", mp["bol"]);
+ Assert.AreEqual("bola", mp["bola"]);
+ Assert.AreEqual("bolo", mp["bolo"]);
+
+ CheckCopy(mp);
+ }
+
+ [TestMethod]
+ public void ColidKeys()
+ {
+ var mp = NewTree();
+ mp["oi"] = "batata";
+ mp["oi"] = "batatatinha";
+ Assert.IsTrue(mp.ContainsKey("oi"));
+ Assert.AreEqual("batatatinha", mp["oi"]);
+ Assert.IsTrue(mp.Validate());
+
+ mp["orelha"] = "batatatinha";
+ Assert.AreEqual("batatatinha", mp["orelha"]);
+ Assert.IsTrue(mp.Validate());
+
+ mp["orfão"] = "criança";
+ Assert.AreEqual("criança", mp["orfão"]);
+ Assert.IsTrue(mp.Validate());
+
+ mp["orfanato"] = "crianças";
+ Assert.AreEqual("crianças", mp["orfanato"]);
+ Assert.IsTrue(mp.Validate());
+
+ Assert.IsTrue(mp.Remove("orfanato"));
+ Assert.AreEqual("criança", mp["orfão"]);
+ Assert.IsFalse(mp.ContainsKey("orfanato"));
+ Assert.IsTrue(mp.Validate());
+
+ mp["orfã"] = "menina";
+ Assert.AreEqual("menina", mp["orfã"]);
+ Assert.IsTrue(mp.Validate());
+
+ CheckCopy(mp);
+ }
+
+ [TestMethod]
+ public void StepByStep()
+ {
+ var mp = NewTree();
+ Assert.AreEqual(0, mp.Count());
+ var a0001 = new byte[] {0x0, 0x01};
+ mp[a0001] = a0001;
+ Assert.AreEqual(1, mp.Count());
+ Assert.AreEqual(a0001, mp[a0001]);
+
+ var a0101 = new byte[] {0x01, 0x01};
+ mp[a0101] = a0101;
+ Assert.AreEqual(2, mp.Count());
+ Assert.AreEqual(a0001, mp[a0001]);
+ Assert.AreEqual(a0101, mp[a0101]);
+
+ Assert.IsTrue(mp.Remove(a0101));
+ Assert.IsFalse(mp.ContainsKey(a0101));
+ Assert.AreEqual(a0001, mp[a0001]);
+
+ var merklePatricia = NewTree();
+ merklePatricia[a0001] = a0001;
+ Assert.AreEqual(merklePatricia, mp);
+
+ CheckCopy(mp);
+ }
+
+ [TestMethod]
+ public void ContainsValue()
+ {
+ var mp = NewTree();
+ mp["aoi"] = "oi";
+ mp["boi2"] = "oi2";
+ mp["coi1"] = "oi3";
+ Assert.IsTrue(mp.Validate());
+ Assert.IsTrue(mp.ContainsValue("oi"));
+ Assert.IsTrue(mp.ContainsValue("oi2"));
+ Assert.IsTrue(mp.ContainsValue("oi3"));
+
+ Assert.IsFalse(mp.ContainsValue("aoi"));
+ Assert.IsFalse(mp.ContainsValue("boi2"));
+ Assert.IsFalse(mp.ContainsValue("coi3"));
+ Assert.IsTrue(mp.Validate());
+
+ CheckCopy(mp);
+ }
+
+ [TestMethod]
+ public void PatriciaCount()
+ {
+ var mp = NewTree();
+ Assert.AreEqual(0, mp.Count());
+ Assert.IsTrue(mp.Validate());
+
+ mp["oi"] = "oi";
+ Assert.AreEqual(1, mp.Count());
+ Assert.IsTrue(mp.Validate());
+
+ mp["oi"] = "oi";
+ Assert.AreEqual(1, mp.Count());
+ Assert.IsTrue(mp.Validate());
+
+ mp["oi"] = "oi1";
+ Assert.AreEqual(1, mp.Count());
+
+ mp["oi1"] = "oi1";
+ mp["oi2"] = "oi2";
+ Assert.AreEqual(3, mp.Count());
+
+ mp["bala"] = "bala2";
+ Assert.AreEqual(4, mp.Count());
+ Assert.IsTrue(mp.Validate());
+
+ CheckCopy(mp);
+ }
+
+ [TestMethod]
+ public void Dictionary()
+ {
+ var exemplo = new Dictionary
+ {
+ ["oi"] = "bala",
+ ["oi1"] = "bala1",
+ ["oi2"] = "bala2",
+ ["oi12"] = "bala12",
+ ["bola"] = "oi",
+ ["birosca123"] = "bruca123",
+ ["ca123"] = "que123",
+ ["oi123"] = "bala123"
+ };
+
+ var merklePatricia = NewTree();
+ foreach (var keyValue in exemplo)
+ {
+ merklePatricia[keyValue.Key] = keyValue.Value;
+ }
+
+ Assert.IsTrue(merklePatricia.Validate());
+
+ foreach (var keyValue in exemplo)
+ {
+ Assert.AreEqual(keyValue.Value, merklePatricia[keyValue.Key]);
+ }
+
+ Assert.IsTrue(merklePatricia.Validate());
+
+ CheckCopy(merklePatricia);
+ }
+
+ [TestMethod]
+ public void ListOfValues()
+ {
+ var lista = new[] {"oi", "oi1", "oi2", "oi12", "bola", "birosca123", "ca123", "oi123"};
+ var mp = NewTree();
+ foreach (var it in lista)
+ {
+ mp[it] = it;
+ Console.WriteLine($"Linha: '{it}:{Encoding.UTF8.GetBytes(it).ByteToHexString(false, false)}':\n{mp}");
+ Assert.AreEqual(it, mp[it]);
+ Assert.IsTrue(mp.Validate());
+ }
+
+ CheckCopy(mp);
+ }
+
+ [TestMethod]
+ public void One()
+ {
+ var merklePatricia = NewTree();
+ Assert.IsTrue(merklePatricia.Validate());
+// Assert.AreEqual(0, merklePatricia.Height());
+
+ void InserirTestar(string x, string y)
+ {
+ merklePatricia[x] = y;
+ Assert.IsTrue(merklePatricia.Validate());
+ Assert.IsTrue(merklePatricia.ContainsKey(x));
+ Assert.IsFalse(merklePatricia.ContainsKey(x + "123"));
+ Assert.AreEqual(y, merklePatricia[x]);
+ Assert.IsNull(merklePatricia[x + "123k"]);
+ }
+
+ InserirTestar("01a2", "valor1");
+// Assert.AreEqual(1, merklePatricia.Height());
+ Assert.IsTrue(merklePatricia.Validate());
+
+ InserirTestar("11a2", "valor2");
+ Assert.IsTrue(merklePatricia.Validate());
+
+ InserirTestar("0212", "valor3");
+// Assert.Equal(3, merklePatricia.Height());
+ Assert.IsTrue(merklePatricia.Validate());
+
+ merklePatricia["0"] = "valor4";
+ Assert.IsTrue(merklePatricia.Validate());
+
+ CheckCopy(merklePatricia);
+ }
+
+ [TestMethod]
+ public void Remove()
+ {
+ var mp = NewTree();
+ Assert.IsTrue(mp.Validate());
+
+ void RemoverTestar(string x, string y)
+ {
+ mp[x] = y;
+ Assert.IsTrue(mp.ContainsKey(x));
+ Assert.IsFalse(mp.ContainsKey(x + "123"));
+ Assert.AreEqual(y, mp[x]);
+ Assert.IsNull(mp[x + "123k"]);
+
+ Assert.IsTrue(mp.Remove(x));
+ Assert.IsFalse(mp.Remove(x));
+ }
+
+ RemoverTestar("oi", "bala");
+ Assert.IsTrue(mp.Validate());
+ mp.Remove("oi");
+ Assert.IsFalse(mp.ContainsKey("oi"));
+ Assert.IsTrue(mp.Validate());
+
+ mp["123"] = "abc";
+ mp["a123"] = "1abc";
+ Assert.AreEqual(2, mp.Count());
+ Assert.IsTrue(mp.Validate());
+
+ Assert.IsFalse(mp.Remove("b123"));
+ Assert.AreEqual(2, mp.Count());
+ Assert.IsTrue(mp.Remove("a123"));
+ Assert.IsTrue(mp.Validate());
+ Assert.AreEqual(1, mp.Count());
+ Assert.IsFalse(mp.ContainsKey("a123"));
+ Assert.IsTrue(mp.ContainsKey("123"));
+ Assert.IsTrue(mp.ContainsKey("123"));
+ Assert.IsTrue(mp.Validate());
+
+ var mp2 = NewTree();
+ mp2["123"] = "abc";
+ Assert.AreEqual(mp2, mp);
+ Assert.IsTrue(mp.Validate());
+ Assert.IsTrue(mp2.Validate());
+
+ CheckCopy(mp);
+ CheckCopy(mp2);
+ }
+
+ [TestMethod]
+ public void EqualsThree()
+ {
+ Assert.AreNotEqual(null, NewTree());
+ var mpOi = NewTree();
+ mpOi["oi"] = "oi";
+ Assert.AreNotEqual(NewTree(), mpOi);
+
+ var mpA = NewTree();
+ mpA["oi"] = "bola";
+ mpA["oi1"] = "1bola";
+ mpA["oi2"] = "b2ola";
+ mpA["oi1"] = "bola1";
+ Assert.IsTrue(mpA.Validate());
+
+ var mpB = NewTree();
+ mpB["oi"] = "bola";
+ mpB["oi1"] = "1bola";
+ mpB["oi2"] = "b2ola";
+ mpB["oi1"] = "bola1";
+ Assert.IsTrue(mpB.Validate());
+ Assert.AreEqual(mpA, mpB);
+
+ mpA["oi"] = "escola";
+ Assert.AreNotEqual(mpA, mpB);
+ Assert.IsTrue(mpA.Validate());
+
+ mpB["oi"] = "escola";
+ Assert.AreEqual(mpA, mpB);
+ Assert.IsTrue(mpB.Validate());
+
+ mpA["oi123"] = "escola";
+ mpA["oi12"] = "escola1";
+ mpA["bola"] = "escola2";
+ mpA["dog"] = "escola2";
+ Assert.IsTrue(mpA.Validate());
+
+ mpB["bola"] = "escola2";
+ mpB["dog"] = "escola2";
+ mpB["oi12"] = "escola1";
+ mpB["oi123"] = "escola";
+ Assert.AreEqual(mpA, mpB);
+ Assert.IsTrue(mpB.Validate());
+
+ mpA.Remove("oi");
+ mpA.Remove("oi");
+ Assert.AreNotEqual(mpA, mpB);
+ Assert.IsTrue(mpA.Validate());
+
+ mpB.Remove("oi");
+ Assert.AreEqual(mpA, mpB);
+ Assert.IsTrue(mpB.Validate());
+
+ Assert.IsNull(mpA["Meg"]);
+
+ Assert.ThrowsException(() => mpA[(string) null]);
+ Assert.ThrowsException(() => mpA[(string) null] = null);
+ Assert.ThrowsException(() => mpA[(byte[]) null]);
+ Assert.ThrowsException(() => mpA[(byte[]) null] = null);
+ Assert.ThrowsException(() => mpA[new byte[] {0, 1}] = null);
+ Assert.ThrowsException(() => mpA["Meg"] = null);
+
+ CheckCopy(mpA);
+ CheckCopy(mpB);
+ }
+
+ [TestMethod]
+ public void ToStringTesting()
+ {
+ var mp = NewTree();
+ Assert.AreEqual("{}", mp.ToString());
+ mp["a"] = "a";
+ var converted = Encoding.UTF8.GetBytes("a").ByteToHexString();
+ Assert.AreEqual($"[\"{converted}\",\"{converted}\",\"{converted}\"]", mp.ToString());
+
+ CheckCopy(mp);
+ }
+
+ [TestMethod]
+ public void Equals()
+ {
+ var mpNode = NewTree();
+ Assert.AreNotEqual(mpNode, null);
+ Assert.IsFalse(mpNode == null);
+ Assert.IsFalse(mpNode.Equals(null));
+
+ Assert.AreEqual(mpNode, mpNode);
+ Assert.IsTrue(mpNode == mpNode);
+ Assert.IsTrue(mpNode.Equals(mpNode));
+
+ var mpA = NewTree();
+ mpA["a"] = "a";
+ Assert.AreNotEqual(mpNode, mpA);
+ Assert.IsFalse(mpNode == mpA);
+ Assert.IsFalse(mpNode.Equals(mpA));
+
+ CheckCopy(mpNode);
+ CheckCopy(mpA);
+ }
+ }
+}
\ No newline at end of file
diff --git a/MPTStates.UnitTests/UT_MerklePatriciaNode.cs b/MPTStates.UnitTests/UT_MerklePatriciaNode.cs
new file mode 100644
index 000000000..7eb09fa2c
--- /dev/null
+++ b/MPTStates.UnitTests/UT_MerklePatriciaNode.cs
@@ -0,0 +1,361 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Neo.Cryptography;
+using Neo.Ledger;
+using Neo.Plugins.MPT;
+
+namespace Neo.UnitTests.Ledger
+{
+ [TestClass]
+ public class UT_MerklePatriciaNode
+ {
+ [TestMethod]
+ public void CloneLeaf()
+ {
+ var mptItem = MerklePatriciaNode.LeafNode();
+ mptItem.Path = Encoding.UTF8.GetBytes("2");
+ mptItem.Key = Encoding.UTF8.GetBytes("oi").Sha256();
+ mptItem.Value = Encoding.UTF8.GetBytes("abc");
+
+ var cloned = mptItem.Clone();
+ Assert.IsTrue(cloned.IsLeaf);
+ Assert.IsTrue(Encoding.UTF8.GetBytes("2").SequenceEqual(cloned.Path));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Key));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+
+ Assert.AreEqual(mptItem, cloned);
+
+ mptItem.Path = Encoding.UTF8.GetBytes("23f");
+ Assert.IsTrue(Encoding.UTF8.GetBytes("2").SequenceEqual(cloned.Path));
+ mptItem.Value = Encoding.UTF8.GetBytes("abc1");
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+ }
+
+ [TestMethod]
+ public void CloneExtension()
+ {
+ var mptItem = MerklePatriciaNode.ExtensionNode();
+ mptItem.Path = Encoding.UTF8.GetBytes("2");
+ mptItem.Next = Encoding.UTF8.GetBytes("oi").Sha256();
+
+ var cloned = mptItem.Clone();
+ Assert.IsTrue(cloned.IsExtension);
+ Assert.IsTrue(Encoding.UTF8.GetBytes("2").SequenceEqual(cloned.Path));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Next));
+
+ Assert.AreEqual(mptItem, cloned);
+
+ mptItem.Path = Encoding.UTF8.GetBytes("23");
+ Assert.IsTrue(Encoding.UTF8.GetBytes("2").SequenceEqual(cloned.Path));
+ mptItem.Next = Encoding.UTF8.GetBytes("oi4").Sha256();
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Next));
+ }
+
+ [TestMethod]
+ public void CloneBranch()
+ {
+ var mptItem = MerklePatriciaNode.BranchNode();
+ mptItem.Key = Encoding.UTF8.GetBytes("oi").Sha256();
+ mptItem.Value = Encoding.UTF8.GetBytes("abc");
+ mptItem[0] = Encoding.UTF8.GetBytes("turma").Sha256();
+ mptItem[7] = Encoding.UTF8.GetBytes("turma7").Sha256();
+ mptItem[10] = Encoding.UTF8.GetBytes("turma10").Sha256();
+
+ var cloned = mptItem.Clone();
+ Assert.IsTrue(cloned.IsBranch);
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Key));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("turma").Sha256().SequenceEqual(cloned[0]));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("turma7").Sha256().SequenceEqual(cloned[7]));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("turma10").Sha256().SequenceEqual(cloned[10]));
+
+ Assert.AreEqual(mptItem, cloned);
+
+ mptItem.Key = Encoding.UTF8.GetBytes("oi11").Sha256();
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Key));
+ mptItem.Value = Encoding.UTF8.GetBytes("abc45");
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+ mptItem[0] = Encoding.UTF8.GetBytes("turma0").Sha256();
+ Assert.IsTrue(Encoding.UTF8.GetBytes("turma").Sha256().SequenceEqual(cloned[0]));
+ }
+
+ [TestMethod]
+ public void FromReplicaLeaf()
+ {
+ var mptItem = MerklePatriciaNode.LeafNode();
+ mptItem.Path = Encoding.UTF8.GetBytes("2");
+ mptItem.Key = Encoding.UTF8.GetBytes("oi").Sha256();
+ mptItem.Value = Encoding.UTF8.GetBytes("abc");
+
+ var cloned = MerklePatriciaNode.BranchNode();
+ cloned.FromReplica(mptItem);
+ Assert.IsTrue(cloned.IsLeaf);
+ Assert.IsTrue(Encoding.UTF8.GetBytes("2").SequenceEqual(cloned.Path));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Key));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+
+ Assert.AreEqual(mptItem, cloned);
+
+ mptItem.Path = Encoding.UTF8.GetBytes("23f");
+ Assert.IsTrue(Encoding.UTF8.GetBytes("2").SequenceEqual(cloned.Path));
+ mptItem.Value = Encoding.UTF8.GetBytes("abc1");
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+ }
+
+ [TestMethod]
+ public void FromReplicaExtension()
+ {
+ var mptItem = MerklePatriciaNode.ExtensionNode();
+ mptItem.Path = Encoding.UTF8.GetBytes("2");
+ mptItem.Next = Encoding.UTF8.GetBytes("oi").Sha256();
+
+ var cloned = MerklePatriciaNode.BranchNode();
+ cloned.FromReplica(mptItem);
+ Assert.IsTrue(cloned.IsExtension);
+ Assert.IsTrue(Encoding.UTF8.GetBytes("2").SequenceEqual(cloned.Path));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Next));
+
+ Assert.AreEqual(mptItem, cloned);
+
+ mptItem.Path = Encoding.UTF8.GetBytes("23");
+ Assert.IsTrue(Encoding.UTF8.GetBytes("2").SequenceEqual(cloned.Path));
+ mptItem.Next = Encoding.UTF8.GetBytes("oi4").Sha256();
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Next));
+ }
+
+ [TestMethod]
+ public void FromReplicaBranch()
+ {
+ var mptItem = MerklePatriciaNode.BranchNode();
+ mptItem.Key = Encoding.UTF8.GetBytes("oi").Sha256();
+ mptItem.Value = Encoding.UTF8.GetBytes("abc");
+ mptItem[0] = Encoding.UTF8.GetBytes("turma").Sha256();
+ mptItem[7] = Encoding.UTF8.GetBytes("turma7").Sha256();
+ mptItem[10] = Encoding.UTF8.GetBytes("turma10").Sha256();
+
+ var cloned = MerklePatriciaNode.LeafNode();
+ cloned.FromReplica(mptItem);
+ Assert.IsTrue(cloned.IsBranch);
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Key));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("turma").Sha256().SequenceEqual(cloned[0]));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("turma7").Sha256().SequenceEqual(cloned[7]));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("turma10").Sha256().SequenceEqual(cloned[10]));
+
+ Assert.AreEqual(mptItem, cloned);
+
+ mptItem.Key = Encoding.UTF8.GetBytes("oi11").Sha256();
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Key));
+ mptItem.Value = Encoding.UTF8.GetBytes("abc45");
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+ mptItem[0] = Encoding.UTF8.GetBytes("turma0").Sha256();
+ Assert.IsTrue(Encoding.UTF8.GetBytes("turma").Sha256().SequenceEqual(cloned[0]));
+ }
+
+ [TestMethod]
+ public void SerializeLeaf()
+ {
+ var mptItem = MerklePatriciaNode.LeafNode();
+ mptItem.Path = new byte[] {0, 1, 3, 4};
+ mptItem.Key = Encoding.UTF8.GetBytes("oi").Sha256();
+ mptItem.Value = Encoding.UTF8.GetBytes("abc");
+
+ var cloned = MerklePatriciaNode.BranchNode();
+ using (var ms = new MemoryStream())
+ using (var bw = new BinaryWriter(ms))
+ {
+ mptItem.Serialize(bw);
+ using (var br = new BinaryReader(bw.BaseStream))
+ {
+ br.BaseStream.Position = 0;
+ cloned.Deserialize(br);
+ }
+ }
+
+ Assert.IsTrue(cloned.IsLeaf);
+ Assert.IsTrue(new byte[] {0, 1, 3, 4}.SequenceEqual(cloned.Path));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Key));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+
+ Assert.AreEqual(mptItem, cloned);
+
+ mptItem.Path = Encoding.UTF8.GetBytes("23f");
+ Assert.IsTrue(new byte[] {0, 1, 3, 4}.SequenceEqual(cloned.Path));
+ mptItem.Value = Encoding.UTF8.GetBytes("abc1");
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+ }
+
+ [TestMethod]
+ public void SerializeLeafEmptyPath()
+ {
+ var mptItem = MerklePatriciaNode.LeafNode();
+ mptItem.Path = new byte[0];
+ mptItem.Key = Encoding.UTF8.GetBytes("oi").Sha256();
+ mptItem.Value = Encoding.UTF8.GetBytes("abc");
+
+ var cloned = MerklePatriciaNode.BranchNode();
+ using (var ms = new MemoryStream())
+ using (var bw = new BinaryWriter(ms))
+ {
+ mptItem.Serialize(bw);
+ using (var br = new BinaryReader(bw.BaseStream))
+ {
+ br.BaseStream.Position = 0;
+ cloned.Deserialize(br);
+ }
+ }
+
+ Assert.IsTrue(cloned.IsLeaf);
+ Assert.IsTrue(new byte[0].SequenceEqual(cloned.Path));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Key));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+
+ Assert.AreEqual(mptItem, cloned);
+
+ mptItem.Path = Encoding.UTF8.GetBytes("23f");
+ Assert.IsTrue(new byte[0].SequenceEqual(cloned.Path));
+ mptItem.Value = Encoding.UTF8.GetBytes("abc1");
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+ }
+
+ [TestMethod]
+ public void SerializeExtension()
+ {
+ var mptItem = MerklePatriciaNode.ExtensionNode();
+ mptItem.Path = new byte[] {0, 1};
+ mptItem.Next = Encoding.UTF8.GetBytes("oi").Sha256();
+
+ var cloned = MerklePatriciaNode.BranchNode();
+ using (var ms = new MemoryStream())
+ using (var bw = new BinaryWriter(ms))
+ {
+ mptItem.Serialize(bw);
+ using (var br = new BinaryReader(bw.BaseStream))
+ {
+ br.BaseStream.Position = 0;
+ cloned.Deserialize(br);
+ }
+ }
+
+ Assert.IsTrue(cloned.IsExtension);
+ Assert.IsTrue(new byte[] {0, 1}.SequenceEqual(cloned.Path));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Next));
+
+ Assert.AreEqual(mptItem, cloned);
+
+ mptItem.Path = new byte[] {2, 3, 7, 8};
+ Assert.IsTrue(new byte[] {0, 1}.SequenceEqual(cloned.Path));
+ mptItem.Next = Encoding.UTF8.GetBytes("oi4").Sha256();
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Next));
+ }
+
+ [TestMethod]
+ public void SerializeBranch()
+ {
+ var mptItem = MerklePatriciaNode.BranchNode();
+ mptItem.Key = Encoding.UTF8.GetBytes("oi").Sha256();
+ mptItem.Value = Encoding.UTF8.GetBytes("abc");
+ mptItem[0] = Encoding.UTF8.GetBytes("turma").Sha256();
+ mptItem[7] = Encoding.UTF8.GetBytes("turma7").Sha256();
+ mptItem[10] = Encoding.UTF8.GetBytes("turma10").Sha256();
+
+ var cloned = MerklePatriciaNode.LeafNode();
+ using (var ms = new MemoryStream())
+ using (var bw = new BinaryWriter(ms))
+ {
+ mptItem.Serialize(bw);
+ using (var br = new BinaryReader(bw.BaseStream))
+ {
+ br.BaseStream.Position = 0;
+ cloned.Deserialize(br);
+ }
+ }
+
+ Assert.IsTrue(cloned.IsBranch);
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Key));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("turma").Sha256().SequenceEqual(cloned[0]));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("turma7").Sha256().SequenceEqual(cloned[7]));
+ Assert.IsTrue(Encoding.UTF8.GetBytes("turma10").Sha256().SequenceEqual(cloned[10]));
+
+ Assert.AreEqual(mptItem, cloned);
+
+ mptItem.Key = Encoding.UTF8.GetBytes("oi11").Sha256();
+ Assert.IsTrue(Encoding.UTF8.GetBytes("oi").Sha256().SequenceEqual(cloned.Key));
+ mptItem.Value = Encoding.UTF8.GetBytes("abc45");
+ Assert.IsTrue(Encoding.UTF8.GetBytes("abc").SequenceEqual(cloned.Value));
+ mptItem[0] = Encoding.UTF8.GetBytes("turma0").Sha256();
+ Assert.IsTrue(Encoding.UTF8.GetBytes("turma").Sha256().SequenceEqual(cloned[0]));
+ }
+
+ [TestMethod]
+ public void EqualsBranch()
+ {
+ var mpNode = MerklePatriciaNode.BranchNode();
+ Assert.AreNotEqual(mpNode, null);
+ Assert.AreEqual(mpNode, mpNode);
+ Assert.AreNotEqual(mpNode, MerklePatriciaNode.ExtensionNode());
+ }
+
+ [TestMethod]
+ public void EqualsLeaf()
+ {
+ var mpNode = MerklePatriciaNode.LeafNode();
+ Assert.AreNotEqual(mpNode, null);
+ Assert.AreEqual(mpNode, mpNode);
+ Assert.AreNotEqual(mpNode, MerklePatriciaNode.ExtensionNode());
+ }
+
+ [TestMethod]
+ public void EqualsExtension()
+ {
+ var mpNode = MerklePatriciaNode.ExtensionNode();
+ Assert.AreNotEqual(mpNode, null);
+ Assert.AreEqual(mpNode, mpNode);
+ Assert.AreNotEqual(mpNode, MerklePatriciaNode.LeafNode());
+ }
+
+ [TestMethod]
+ public void ToStringBranch()
+ {
+ var mpNode = MerklePatriciaNode.BranchNode();
+ Assert.AreEqual("{}", $"{mpNode}");
+ }
+
+ [TestMethod]
+ public void UsingOnSet()
+ {
+ var setObj = new HashSet {MerklePatriciaNode.BranchNode()};
+ Assert.AreEqual(1, setObj.Count);
+ setObj.Add(MerklePatriciaNode.ExtensionNode());
+ Assert.AreEqual(2, setObj.Count);
+ setObj.Add(MerklePatriciaNode.LeafNode());
+ Assert.AreEqual(3, setObj.Count);
+ setObj.Add(MerklePatriciaNode.ExtensionNode());
+ Assert.AreEqual(3, setObj.Count);
+
+ var node = MerklePatriciaNode.LeafNode();
+ node.Key = new byte[] {0, 1};
+ node.Value = new byte[] {0, 1};
+ setObj.Add(node);
+ Assert.AreEqual(4, setObj.Count);
+ }
+
+ [TestMethod]
+ public void ToStringExtension()
+ {
+ var mpNode = MerklePatriciaNode.ExtensionNode();
+ Assert.AreEqual("[null,null]", $"{mpNode}");
+ }
+
+ [TestMethod]
+ public void ToStringLeaf()
+ {
+ var mpNode = MerklePatriciaNode.LeafNode();
+ Assert.AreEqual("[null,null,null]", $"{mpNode}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/MPTStates.UnitTests/UT_MerklePatriciaPersistence.cs b/MPTStates.UnitTests/UT_MerklePatriciaPersistence.cs
new file mode 100644
index 000000000..b671be7a0
--- /dev/null
+++ b/MPTStates.UnitTests/UT_MerklePatriciaPersistence.cs
@@ -0,0 +1,173 @@
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Neo.Ledger;
+using Neo.Plugins.MPT;
+
+namespace Neo.UnitTests.Ledger
+{
+ [TestClass]
+ public class UT_MerklePatriciaPersistence
+ {
+ private static MerklePatriciaTree NewTree() => new MerklePatriciaTree();
+
+ [TestMethod]
+ public void Serialize()
+ {
+ var mp = NewTree();
+ mp["oi"] = "bola";
+
+ var cloned = NewTree();
+ using (var ms = new MemoryStream())
+ using (var bw = new BinaryWriter(ms))
+ {
+ mp.Serialize(bw);
+ using (var br = new BinaryReader(bw.BaseStream))
+ {
+ br.BaseStream.Position = 0;
+ cloned.Deserialize(br);
+ }
+ }
+
+ Assert.IsTrue(mp.Validate());
+ Assert.IsTrue(cloned.Validate());
+ Assert.AreEqual("bola", cloned["oi"]);
+ Assert.AreEqual(mp, cloned);
+
+ mp["cachorro"] = "cachorro";
+ Assert.AreNotEqual(mp, cloned);
+ }
+
+ [TestMethod]
+ public void SerializeFakingData()
+ {
+ var mp = NewTree();
+ mp["oi"] = "bola";
+
+ var cloned = NewTree();
+ using (var ms = new MemoryStream())
+ using (var bw = new BinaryWriter(ms))
+ {
+ mp.Serialize(bw);
+ var data = ms.ToArray();
+ data[data.Length - 2] += 1;
+ using (var memoryStream = new MemoryStream())
+ using (var binWriter = new BinaryWriter(memoryStream))
+ {
+ binWriter.Write(data);
+ using (var br = new BinaryReader(binWriter.BaseStream))
+ {
+ br.BaseStream.Position = 0;
+ cloned.Deserialize(br);
+ }
+ }
+ }
+
+ Assert.IsTrue(mp.Validate());
+ Assert.IsFalse(cloned.Validate());
+ Assert.AreNotEqual(mp, cloned);
+
+ Assert.AreEqual("bola", mp["oi"]);
+ Assert.AreNotEqual("bola", cloned["oi"]);
+ }
+
+ [TestMethod]
+ public void SerializeFakingDataBranch()
+ {
+ var mp = NewTree();
+ mp["oi"] = "bola";
+ mp["ola"] = "ola";
+
+ var cloned = NewTree();
+ using (var ms = new MemoryStream())
+ using (var bw = new BinaryWriter(ms))
+ {
+ mp.Serialize(bw);
+ var data = ms.ToArray();
+ data[data.Length - 2] += 1;
+ using (var memoryStream = new MemoryStream())
+ using (var binWriter = new BinaryWriter(memoryStream))
+ {
+ binWriter.Write(data);
+ using (var br = new BinaryReader(binWriter.BaseStream))
+ {
+ br.BaseStream.Position = 0;
+ cloned.Deserialize(br);
+ }
+ }
+ }
+
+ Assert.IsTrue(mp.Validate());
+ Assert.IsFalse(cloned.Validate());
+ Assert.AreNotEqual(mp, cloned);
+ }
+
+ [TestMethod]
+ public void Clone()
+ {
+ var mp = NewTree();
+ mp["oi"] = "bola";
+
+ var cloned = mp.Clone();
+
+ Assert.IsTrue(mp.Validate());
+ Assert.IsTrue(cloned.Validate());
+ Assert.AreEqual("bola", cloned["oi"]);
+ Assert.AreEqual(mp, cloned);
+
+ mp["cachorro"] = "cachorro";
+ Assert.AreNotEqual(mp, cloned);
+ }
+
+ [TestMethod]
+ public void FromReplica()
+ {
+ var mp = NewTree();
+ mp["oi"] = "bola";
+
+ var cloned = NewTree();
+ cloned.FromReplica(mp);
+
+ Assert.IsTrue(mp.Validate());
+ Assert.IsTrue(cloned.Validate());
+ Assert.AreEqual("bola", cloned["oi"]);
+ Assert.AreEqual(mp, cloned);
+
+ mp["cachorro"] = "cachorro";
+ Assert.AreNotEqual(mp, cloned);
+ }
+
+ [TestMethod]
+ public void UsingOnSet()
+ {
+ var setObj = new HashSet();
+ Assert.AreEqual(0, setObj.Count);
+
+ var mpA = NewTree();
+ mpA["a"] = "a";
+ setObj.Add(mpA);
+ Assert.AreEqual(1, setObj.Count);
+
+ var mpB = NewTree();
+ mpB["b"] = "b";
+ setObj.Add(mpB);
+ Assert.AreEqual(2, setObj.Count);
+
+ var mpC = NewTree();
+ mpC["b"] = "b";
+ setObj.Add(mpC);
+ Assert.AreEqual(2, setObj.Count);
+
+ Assert.IsTrue(setObj.Remove(mpA));
+ Assert.AreEqual(1, setObj.Count);
+
+ var mpD = NewTree();
+ mpD["d"] = "d";
+ Assert.IsFalse(setObj.Remove(mpD));
+ setObj.Add(mpD);
+ Assert.AreEqual(2, setObj.Count);
+
+ setObj.Add(NewTree());
+ }
+ }
+}
\ No newline at end of file
diff --git a/MPTStates.UnitTests/UT_MerklePatriciaTools.cs b/MPTStates.UnitTests/UT_MerklePatriciaTools.cs
new file mode 100644
index 000000000..6637dc290
--- /dev/null
+++ b/MPTStates.UnitTests/UT_MerklePatriciaTools.cs
@@ -0,0 +1,66 @@
+using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Neo.Plugins.MPT;
+
+namespace Neo.UnitTests.Ledger
+{
+ [TestClass]
+ public class UT_MerklePatriciaTools
+ {
+ [TestMethod]
+ public void DistinctRoot()
+ {
+ Assert.AreEqual("faf01", new byte[] {0xfa, 0xf0, 0x1}.ByteToHexString(false, false));
+ Assert.AreEqual("fa f0 1", new byte[] {0xfa, 0xf0, 0x1}.ByteToHexString(true, false));
+ Assert.AreEqual("faf001", new byte[] {0xfa, 0xf0, 0x1}.ByteToHexString(false));
+ Assert.AreEqual("fa f0 01", new byte[] {0xfa, 0xf0, 0x1}.ByteToHexString());
+ }
+
+ [TestMethod]
+ public void Inc()
+ {
+ Assert.AreEqual(true, 1.GetBit(0));
+ Assert.AreEqual(false, 2.GetBit(0));
+ }
+
+ [TestMethod]
+ public void CompactEncode()
+ {
+ Assert.IsTrue(new byte[] {1, 2, 3, 4, 5}.CompactEncode().SequenceEqual(new byte[] {0x11, 0x23, 0x45}));
+ Assert.IsTrue(new byte[] {0, 1, 2, 3, 4, 5}.CompactEncode()
+ .SequenceEqual(new byte[] {0, 0x01, 0x23, 0x45}));
+ Assert.IsTrue(new byte[] {0, 0xf, 1, 0xc, 0xb, 8, 10}.CompactEncode()
+ .SequenceEqual(new byte[] {0x10, 0xf1, 0xcb, 0x8a}));
+ Assert.IsTrue(new byte[] {0xf, 1, 0xc, 0xb, 8, 10}.CompactEncode()
+ .SequenceEqual(new byte[] {0x00, 0xf1, 0xcb, 0x8a}));
+
+ Assert.IsTrue(new byte[] {1, 2, 3, 4, 5}.CompactEncode(true).SequenceEqual(new byte[] {0x31, 0x23, 0x45}));
+ Assert.IsTrue(new byte[] {0, 1, 2, 3, 4, 5}.CompactEncode(true)
+ .SequenceEqual(new byte[] {0x20, 0x01, 0x23, 0x45}));
+ Assert.IsTrue(new byte[] {0, 0xf, 1, 0xc, 0xb, 8, 10}.CompactEncode(true)
+ .SequenceEqual(new byte[] {0x30, 0xf1, 0xcb, 0x8a}));
+ Assert.IsTrue(new byte[] {0xf, 1, 0xc, 0xb, 8, 10}.CompactEncode(true)
+ .SequenceEqual(new byte[] {0x20, 0xf1, 0xcb, 0x8a}));
+ }
+
+ [TestMethod]
+ public void CompactDecode()
+ {
+ Assert.IsTrue(new byte[] {0x11, 0x23, 0x45}.CompactDecode().SequenceEqual(new byte[] {1, 2, 3, 4, 5}));
+ Assert.IsTrue(new byte[] {0, 0x01, 0x23, 0x45}.CompactDecode()
+ .SequenceEqual(new byte[] {0, 1, 2, 3, 4, 5}));
+ Assert.IsTrue(new byte[] {0x10, 0xf1, 0xcb, 0x8a}.CompactDecode()
+ .SequenceEqual(new byte[] {0, 0xf, 1, 0xc, 0xb, 8, 10}));
+ Assert.IsTrue(new byte[] {0x00, 0xf1, 0xcb, 0x8a}.CompactDecode()
+ .SequenceEqual(new byte[] {0xf, 1, 0xc, 0xb, 8, 10}));
+
+ Assert.IsTrue(new byte[] {0x31, 0x23, 0x45}.CompactDecode().SequenceEqual(new byte[] {1, 2, 3, 4, 5}));
+ Assert.IsTrue(new byte[] {0x20, 0x01, 0x23, 0x45}.CompactDecode()
+ .SequenceEqual(new byte[] {0, 1, 2, 3, 4, 5}));
+ Assert.IsTrue(new byte[] {0x30, 0xf1, 0xcb, 0x8a}.CompactDecode()
+ .SequenceEqual(new byte[] {0, 0xf, 1, 0xc, 0xb, 8, 10}));
+ Assert.IsTrue(new byte[] {0x20, 0xf1, 0xcb, 0x8a}.CompactDecode()
+ .SequenceEqual(new byte[] {0xf, 1, 0xc, 0xb, 8, 10}));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MPTStates/ByteArrayComparer.cs b/MPTStates/ByteArrayComparer.cs
new file mode 100644
index 000000000..c397cc14e
--- /dev/null
+++ b/MPTStates/ByteArrayComparer.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Neo.Cryptography;
+
+namespace Neo
+{
+ ///
+ ///
+ /// Byte array comparer, useful to have a byte array on a set or as key of a dictionary.
+ ///
+ public class ByteArrayComparer : IEqualityComparer
+ {
+ ///
+ public bool Equals(byte[] left, byte[] right)
+ {
+ if (left == null || right == null)
+ {
+ return left == null && right == null;
+ }
+
+ return left.SequenceEqual(right);
+ }
+
+ ///
+ public int GetHashCode(byte[] key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ return (int) key.Murmur32(0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/MPTStates/MPT/MPTKey.cs b/MPTStates/MPT/MPTKey.cs
new file mode 100644
index 000000000..3fa4e295f
--- /dev/null
+++ b/MPTStates/MPT/MPTKey.cs
@@ -0,0 +1,56 @@
+using Neo.IO;
+using System;
+using System.IO;
+
+namespace Neo.Plugins.MPT
+{
+ ///
+ ///
+ /// MPTKey for the DataCache.
+ ///
+ public class MPTKey : IEquatable, ISerializable
+ {
+ public UInt160 ScriptHash;
+ public UInt256 HashKey;
+
+ ///
+ int ISerializable.Size => ScriptHash.Size + HashKey.Size;
+
+ ///
+ public bool Equals(MPTKey other)
+ {
+ if (other is null)
+ return false;
+ if (ReferenceEquals(this, other))
+ return true;
+ return ScriptHash.Equals(other.ScriptHash) && HashKey.Equals(other.HashKey);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is null) return false;
+ return obj is MPTKey key && Equals(key);
+ }
+
+ ///
+ public override int GetHashCode() => ScriptHash.GetHashCode() + HashKey.GetHashCode();
+
+ ///
+ void ISerializable.Serialize(BinaryWriter writer)
+ {
+ writer.Write(ScriptHash);
+ writer.Write(HashKey);
+ }
+
+ ///
+ void ISerializable.Deserialize(BinaryReader reader)
+ {
+ ScriptHash = reader.ReadSerializable();
+ HashKey = reader.ReadSerializable();
+ }
+
+ ///
+ public override string ToString() => $"{{'ScriptHash':{ScriptHash}, 'HashKey':{HashKey}}}";
+ }
+}
\ No newline at end of file
diff --git a/MPTStates/MPT/MerklePatricia.cs b/MPTStates/MPT/MerklePatricia.cs
new file mode 100644
index 000000000..35dcf6a13
--- /dev/null
+++ b/MPTStates/MPT/MerklePatricia.cs
@@ -0,0 +1,218 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Neo.Cryptography;
+using Neo.Ledger;
+using Neo.Plugins.MPT.Operation;
+
+namespace Neo.Plugins.MPT
+{
+ ///
+ ///
+ /// Modified Merkel Patricia Tree.
+ /// Note: It is not a thread safe implementation.
+ ///
+ public abstract class MerklePatricia : StateBase, IEquatable
+ {
+ private readonly MPTSet mptSet;
+ private readonly MPTGet mptGet;
+ private readonly MPTRemove mptRemove;
+ private readonly MPTValidate mptValidate;
+
+ ///
+ /// Get data from the database.
+ ///
+ protected abstract MerklePatriciaNode GetDb(byte[] key);
+
+ ///
+ /// Removes data from the database.
+ ///
+ protected abstract bool RemoveDb(byte[] key);
+
+ ///
+ /// Set data on the database.
+ ///
+ protected abstract MerklePatriciaNode SetDb(byte[] kye, MerklePatriciaNode node);
+
+ ///
+ /// Check if the database contains the key.
+ ///
+ protected abstract bool ContainsKeyDb(byte[] key);
+
+ ///
+ /// Get the root hash.
+ ///
+ protected abstract byte[] GetRoot();
+
+ ///
+ /// Change the root hash.
+ ///
+ protected abstract void SetRoot(byte[] root);
+
+ internal MerklePatricia()
+ {
+ mptSet = new MPTSet(GetDb, RemoveDb, SetDb, GetRoot, SetRoot);
+ mptGet = new MPTGet(GetDb, GetRoot);
+ mptRemove = new MPTRemove(GetDb, RemoveDb, SetDb, GetRoot, SetRoot);
+ mptValidate = new MPTValidate(GetDb, ContainsKeyDb);
+ }
+
+ ///
+ /// Get and set the key and value pairs of the tree.
+ ///
+ /// The string key that indicates the reference.
+ public string this[string key]
+ {
+ get
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ var resp = this[Encoding.UTF8.GetBytes(key)];
+ return resp == null ? null : Encoding.UTF8.GetString(resp);
+ }
+ set
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ this[Encoding.UTF8.GetBytes(key)] = Encoding.UTF8.GetBytes(value);
+ }
+ }
+
+
+ ///
+ /// Get and set the key and value pairs of the tree.
+ ///
+ /// The key that indicates the reference.
+ public byte[] this[byte[] key]
+ {
+ get => mptGet.Get(key);
+ set => mptSet.Set(key, value);
+ }
+
+ ///
+ /// Test if contains a specific key.
+ ///
+ /// Key to be tested.
+ /// true in the case the tree contains the key.
+ public bool ContainsKey(byte[] key) => this[key] != null;
+
+ public bool ContainsKey(string key) => ContainsKey(Encoding.UTF8.GetBytes(key));
+
+ ///
+ /// Removes a value for a specific key.
+ ///
+ /// Remove this key from the tree.
+ /// true is the key was present and sucessifully removed.
+ public bool Remove(string key) => Remove(Encoding.UTF8.GetBytes(key));
+
+ ///
+ /// Removes a value for a specific key.
+ ///
+ /// Remove this key from the tree.
+ /// true is the key was present and sucessifully removed.
+ public bool Remove(byte[] key) => mptRemove.Remove(key);
+
+ ///
+ /// Checks if the hashes correspond to their nodes.
+ ///
+ /// In the case the validation is Ok.
+ public bool Validate() => mptValidate.Validate(GetRoot());
+
+ ///
+ public override string ToString() => GetRoot() == null ? "{}" : ToString(GetDb(GetRoot()));
+
+ ///
+ private string ToString(MerklePatriciaNode node)
+ {
+ if (node.IsExtension)
+ {
+ return $"{{\"{node.Path.ByteToHexString(false, false)}\": {ToString(GetDb(node.Next))}}}";
+ }
+
+ if (node.IsLeaf)
+ {
+ return node.ToString();
+ }
+
+ var resp = new StringBuilder("{");
+ var virgula = false;
+ for (var i = 0; i < node.Length; i++)
+ {
+ if (node[i] == null) continue;
+ resp.Append(virgula ? "," : "")
+ .Append(i < node.Length - 2
+ ? $"\"{i:x}\":{ToString(GetDb(node[i]))}"
+ : $"\"{i:x}\":\"{node[i].ByteToHexString(false, false)}\"");
+
+ virgula = true;
+ }
+
+ return resp.Append("}").ToString();
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ return obj.GetType() == GetType() && Equals((MerklePatricia) obj);
+ }
+
+ ///
+ public bool Equals(MerklePatricia other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ if (GetRoot() == null && other.GetRoot() == null) return true;
+ if ((GetRoot() == null && other.GetRoot() != null) ||
+ (GetRoot() != null && other.GetRoot() == null))
+ {
+ return false;
+ }
+
+ if (!GetRoot().SequenceEqual(other.GetRoot()))
+ {
+ return false;
+ }
+
+ var hashesToCheck = new Stack();
+ hashesToCheck.Push(GetRoot());
+ while (hashesToCheck.Count > 0)
+ {
+ var it = hashesToCheck.Pop();
+ if (it == null) continue;
+ var itNode = GetDb(it);
+ var otherNode = other.GetDb(it);
+ if (otherNode == null || !otherNode.Equals(itNode))
+ {
+ return false;
+ }
+
+ foreach (var hash in itNode)
+ {
+ if (hash != null)
+ {
+ hashesToCheck.Push(hash);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ public override int GetHashCode() => GetRoot() != null ? (int) GetRoot().Murmur32(0) : 0;
+ }
+}
\ No newline at end of file
diff --git a/MPTStates/MPT/MerklePatriciaDataCache.cs b/MPTStates/MPT/MerklePatriciaDataCache.cs
new file mode 100644
index 000000000..07534869a
--- /dev/null
+++ b/MPTStates/MPT/MerklePatriciaDataCache.cs
@@ -0,0 +1,55 @@
+using Neo.IO;
+using Neo.IO.Caching;
+
+namespace Neo.Plugins.MPT
+{
+ ///
+ ///
+ /// The MerklePatriciaDataCache data structure delegates the calls to the DataCache,
+ /// so there is no need to retrieve all the MPT from the database.
+ ///
+ public class MerklePatriciaDataCache : MerklePatricia
+ {
+ private readonly DataCache db;
+ private byte[] _rootHash;
+
+ public MerklePatriciaDataCache(DataCache db, byte[] rootHash)
+ {
+ this.db = db;
+ _rootHash = rootHash;
+ if (_rootHash != null && _rootHash.Length == 0)
+ {
+ _rootHash = null;
+ }
+ }
+
+ ///
+ protected override MerklePatriciaNode GetDb(byte[] hash) => db.TryGet(hash.AsSerializable());
+
+ ///
+ protected override bool RemoveDb(byte[] hash)
+ {
+ db.Delete(hash.AsSerializable());
+ return true;
+ }
+
+ ///
+ protected override MerklePatriciaNode SetDb(byte[] hash, MerklePatriciaNode node)
+ {
+ var hashAsSerializable = hash.AsSerializable();
+ // This next line is necessary due to the caching mechanism
+ db.Delete(hashAsSerializable);
+ db.Add(hashAsSerializable, node);
+ return node;
+ }
+
+ ///
+ protected override bool ContainsKeyDb(byte[] key) => db.TryGet(key.AsSerializable()) != null;
+
+ ///
+ protected override byte[] GetRoot() => _rootHash;
+
+ ///
+ protected override void SetRoot(byte[] root) => _rootHash = root;
+ }
+}
\ No newline at end of file
diff --git a/MPTStates/MPT/MerklePatriciaNode.cs b/MPTStates/MPT/MerklePatriciaNode.cs
new file mode 100644
index 000000000..c72fa5e50
--- /dev/null
+++ b/MPTStates/MPT/MerklePatriciaNode.cs
@@ -0,0 +1,312 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Neo.Cryptography;
+using Neo.IO;
+using Neo.Ledger;
+
+namespace Neo.Plugins.MPT
+{
+ ///
+ ///
+ /// Modified Merkel Patricia Node.
+ /// Note: It is not a thread safe implementation.
+ ///
+ public class MerklePatriciaNode : StateBase,
+ ICloneable,
+ IEquatable,
+ IEnumerable
+ {
+ private const int BranchSize = 18;
+ private const int ExtensionSize = 2;
+ private const int LeafSize = 3;
+
+ private byte[][] _hashes;
+
+ public MerklePatriciaNode() : this(0)
+ {
+ }
+
+ public MerklePatriciaNode(int size) => _hashes = new byte[size][];
+
+ ///
+ /// Indicates if the node is a branch.
+ ///
+ public bool IsBranch => _hashes.Length == BranchSize;
+
+ ///
+ /// Indicates if the node is an extension.
+ ///
+ public bool IsExtension => _hashes.Length == ExtensionSize;
+
+ ///
+ /// Indicates if the node is a leaf.
+ ///
+ public bool IsLeaf => _hashes.Length == LeafSize;
+
+ ///
+ /// Get and set the hashes by index.
+ ///
+ /// Index of the hash to get or set.
+ public byte[] this[int index]
+ {
+ get => _hashes[index];
+ set => _hashes[index] = value;
+ }
+
+ ///
+ /// Get and set the path of the node.
+ /// Used for leaf and extension nodes.
+ ///
+ public byte[] Path
+ {
+ get => _hashes[0];
+ set => _hashes[0] = value;
+ }
+
+ ///
+ /// Get and set the key of the node.
+ /// Used for leaf and branch nodes.
+ ///
+ public byte[] Key
+ {
+ get => _hashes[_hashes.Length - 2];
+ set => _hashes[_hashes.Length - 2] = value;
+ }
+
+ ///
+ /// Get and set the value of the node.
+ /// Used for leaf and branch nodes.
+ ///
+ public byte[] Value
+ {
+ get => _hashes[_hashes.Length - 1];
+ set => _hashes[_hashes.Length - 1] = value;
+ }
+
+ ///
+ /// Get and set the hash of the next node.
+ /// Only for extension node.
+ ///
+ public byte[] Next
+ {
+ get => _hashes[_hashes.Length - 1];
+ set => _hashes[_hashes.Length - 1] = value;
+ }
+
+ ///
+ /// Calculates the node hash.
+ ///
+ /// The calculated hash.
+ public byte[] Hash()
+ {
+ var bytes = new List();
+ for (var i = 0; i < _hashes.Length; i++)
+ {
+ bytes.Add((byte) i);
+ if (_hashes[i] != null)
+ {
+ bytes.AddRange(_hashes[i]);
+ }
+ }
+
+ return new Crypto().Hash256(bytes.ToArray());
+ }
+
+ ///
+ public override string ToString()
+ {
+ var resp = new StringBuilder(IsBranch ? "{" : "[");
+ var virgula = false;
+ for (var i = 0; i < _hashes.Length; i++)
+ {
+ if (IsBranch && _hashes[i] == null) continue;
+ resp.Append(virgula ? "," : "")
+ .Append(IsBranch ? $"\"{i:x}\":" : "")
+ .Append(_hashes[i] != null ? $"\"{_hashes[i].ByteToHexString(false, false)}\"" : "null");
+ virgula = true;
+ }
+
+ return resp.Append(IsBranch ? "}" : "]").ToString();
+ }
+
+ ///
+ ///
+ /// Iterates only on the hashes.
+ ///
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ if (IsLeaf)
+ {
+ yield break;
+ }
+
+ if (IsExtension)
+ {
+ yield return Next;
+ }
+ else
+ {
+ for (var i = 0; i < Length - 2; ++i)
+ {
+ yield return _hashes[i];
+ }
+ }
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// The number of hashes on the node.
+ ///
+ public int Length => _hashes.Length;
+
+ ///
+ /// Creates a branch node.
+ ///
+ /// The node created.
+ public static MerklePatriciaNode BranchNode() => new MerklePatriciaNode(BranchSize);
+
+ ///
+ /// Creates an extension node.
+ ///
+ /// The node created.
+ public static MerklePatriciaNode ExtensionNode() => new MerklePatriciaNode(ExtensionSize);
+
+ ///
+ /// Creates a leaf node.
+ ///
+ /// The node created.
+ public static MerklePatriciaNode LeafNode() => new MerklePatriciaNode(LeafSize);
+
+ ///
+ public MerklePatriciaNode Clone()
+ {
+ var resp = new MerklePatriciaNode(Length);
+ for (var i = 0; i < Length; i++)
+ {
+ resp._hashes[i] = _hashes[i] != null ? _hashes[i].ToArray() : null;
+ }
+
+ return resp;
+ }
+
+ ///
+ public void FromReplica(MerklePatriciaNode replica)
+ {
+ _hashes = new byte[replica.Length][];
+ for (var i = 0; i < Length; i++)
+ {
+ _hashes[i] = replica._hashes[i] != null ? replica._hashes[i].ToArray() : null;
+ }
+ }
+
+ ///
+ public bool Equals(MerklePatriciaNode other)
+ {
+ if (ReferenceEquals(null, other))
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ if (_hashes.Length != other.Length)
+ {
+ return false;
+ }
+
+ return !_hashes.Where((t, i) =>
+ (t != null || other._hashes[i] != null) &&
+ ((t == null && other._hashes[i] != null) || (t != null && other._hashes[i] == null) ||
+ !t.SequenceEqual(other._hashes[i]))).Any();
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ return obj.GetType() == GetType() && Equals((MerklePatriciaNode) obj);
+ }
+
+ ///
+ public override int GetHashCode() => (int) Hash().Murmur32(0);
+
+ ///
+ public override void Deserialize(BinaryReader reader)
+ {
+ base.Deserialize(reader);
+ _hashes = new byte[reader.ReadByte()][];
+ for (var i = 0; i < _hashes.Length; i++)
+ {
+ if (i == 0 && !IsBranch)
+ {
+ _hashes[i] = reader.ReadVarBytes().CompactDecode();
+ }
+ else
+ {
+ _hashes[i] = reader.ReadVarBytes();
+ _hashes[i] = IsBranch && _hashes[i].Length == 0 ? null : _hashes[i];
+ }
+ }
+ }
+
+ ///
+ public override void Serialize(BinaryWriter writer)
+ {
+ base.Serialize(writer);
+ writer.Write((byte) _hashes.Length);
+ for (var i = 0; i < _hashes.Length; i++)
+ {
+ if (i == 0 && !IsBranch)
+ {
+ writer.WriteVarBytes(_hashes[i] != null ? _hashes[i].CompactEncode() : new byte[0]);
+ }
+ else
+ {
+ writer.WriteVarBytes(_hashes[i] ?? new byte[0]);
+ }
+ }
+ }
+
+ ///
+ /// Returns the number of the first not null hash and the number of hashes.
+ ///
+ /// Max of hashes to check.
+ /// The index and the number of not null hashes.
+ public (int, int) IndexAndCountNotNullHashes(int max = 2)
+ {
+ var i = 0;
+ var cont = 0;
+ var index = -1;
+ foreach (var it in this)
+ {
+ if (it != null)
+ {
+ cont++;
+ index = i;
+ }
+
+ ++i;
+ if (cont >= max)
+ {
+ break;
+ }
+ }
+
+ return (index, cont);
+ }
+ }
+}
\ No newline at end of file
diff --git a/MPTStates/MPT/MerklePatriciaTools.cs b/MPTStates/MPT/MerklePatriciaTools.cs
new file mode 100644
index 000000000..f497a8520
--- /dev/null
+++ b/MPTStates/MPT/MerklePatriciaTools.cs
@@ -0,0 +1,117 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Neo.Ledger;
+
+namespace Neo.Plugins.MPT
+{
+ public static class MerklePatriciaTools
+ {
+ ///
+ /// Converts a byte array to an hexadecimal string.
+ ///
+ /// Byte array to be converted.
+ /// The converted string.
+ public static string ByteToHexString(this byte[] hexchar, bool useSpace = true, bool forceTwoChars = true) =>
+ string.Join(useSpace ? " " : "",
+ hexchar.Select(x => x.ToString("x" + (forceTwoChars ? "2" : ""))).ToList());
+
+ ///
+ /// Get the index-th bit of a number.
+ ///
+ /// Number from where to get the bit.
+ /// Index of the bit.
+ /// The bit
+ public static bool GetBit(this int number, int index) => (number >> index) % 2 == 1;
+
+ ///
+ /// Encodes nibbles into bytes.
+ ///
+ /// Nibbles to be encoded.
+ /// Indicates if is a leaf or an extension node.
+ /// An array of bytes
+ public static byte[] CompactEncode(this byte[] hexarray, bool isLeaf = false)
+ {
+ var first = (byte) ((isLeaf ? 2 : 0) + hexarray.Length % 2);
+ var hexarrayList = new List(hexarray);
+ if (first % 2 == 0)
+ {
+ hexarrayList.Insert(0, first);
+ hexarrayList.Insert(1, 0);
+ }
+ else
+ {
+ hexarrayList.Insert(0, first);
+ }
+
+ var resp = new List();
+ var hexarrayListCount = hexarrayList.Count;
+ for (var i = 0; i < hexarrayListCount; i += 2)
+ {
+ resp.Add((byte) (16 * hexarrayList[i] + hexarrayList[i + 1]));
+ }
+
+ return resp.ToArray();
+ }
+
+ ///
+ /// Decodes an array of bytes to a array of nibbles.
+ ///
+ /// The array to be decoded.
+ /// An array of nibbles.
+ public static byte[] CompactDecode(this byte[] hexarray)
+ {
+ var resp = new List(hexarray.Length * 2);
+ if (hexarray[0] / 16 % 2 == 1)
+ {
+ resp.Add((byte) (hexarray[0] % 16));
+ }
+
+ for (var i = 1; i < hexarray.Length; i++)
+ {
+ resp.Add((byte) (hexarray[i] / 16));
+ resp.Add((byte) (hexarray[i] % 16));
+ }
+
+ return resp.ToArray();
+ }
+
+ ///
+ /// Converts byte array to StorageItem
+ ///
+ /// The byte array.
+ /// The generated StorageItem.
+ public static StorageItem ToStorageItem(this byte[] data)
+ {
+ using (var memoryStream = new MemoryStream())
+ using (var binWriter = new BinaryWriter(memoryStream))
+ {
+ binWriter.Write(data);
+ using (var br = new BinaryReader(binWriter.BaseStream))
+ {
+ br.BaseStream.Position = 0;
+ var item = new StorageItem();
+ item.Deserialize(br);
+ return item;
+ }
+ }
+ }
+
+ ///
+ /// Convert a byte array in a nibble representation.
+ ///
+ /// The byte array to be converted.
+ /// The byte array with the nibbles.
+ public static byte[] ConvertToNibble(this byte[] key)
+ {
+ var resp = new byte[key.Length * 2];
+ for (var i = 0; i < key.Length; i++)
+ {
+ resp[2 * i] = (byte) (key[i] / 16);
+ resp[2 * i + 1] = (byte) (key[i] % 16);
+ }
+
+ return resp;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MPTStates/MPT/MerklePatriciaTree.cs b/MPTStates/MPT/MerklePatriciaTree.cs
new file mode 100644
index 000000000..7c7b4ab36
--- /dev/null
+++ b/MPTStates/MPT/MerklePatriciaTree.cs
@@ -0,0 +1,126 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Neo.IO;
+
+namespace Neo.Plugins.MPT
+{
+ ///
+ ///
+ public class MerklePatriciaTree : MerklePatricia,
+ ICloneable
+ {
+ private readonly MerklePatricia _merklePatricia;
+ private byte[] _rootHash;
+
+ private readonly Dictionary db = new
+ Dictionary(new ByteArrayComparer());
+
+ ///
+ protected override MerklePatriciaNode GetDb(byte[] hash) => db[hash];
+
+ ///
+ protected override bool RemoveDb(byte[] hash) => db.Remove(hash);
+
+ ///
+ protected override MerklePatriciaNode SetDb(byte[] hash, MerklePatriciaNode node) => db[hash] = node;
+
+ ///
+ protected override bool ContainsKeyDb(byte[] key) => db.ContainsKey(key);
+
+ ///
+ protected override byte[] GetRoot() => _rootHash;
+
+ ///
+ protected override void SetRoot(byte[] root) => _rootHash = root;
+
+ private bool ContainsValueDb(byte[] value) =>
+ db.Any(x => x.Value.Value != null && x.Value.Value.SequenceEqual(value));
+
+ private void ClearDb() => db.Clear();
+ private int CountAllDb() => db.Count();
+ private int CountValuesDb() => db.Count(x => x.Value.IsLeaf || (x.Value.IsBranch && x.Value.Value != null));
+ private IEnumerable> GetEnumeratorDb() => db;
+ private MerklePatriciaTree NewTree() => new MerklePatriciaTree();
+
+ ///
+ public MerklePatriciaTree Clone()
+ {
+ var resp = NewTree();
+ resp.SetRoot(GetRoot()?.ToArray());
+ foreach (var entry in GetEnumeratorDb())
+ {
+ resp.SetDb(entry.Key.ToArray(), entry.Value.Clone());
+ }
+
+ return resp;
+ }
+
+ ///
+ public void FromReplica(MerklePatriciaTree replica)
+ {
+ ClearDb();
+ SetRoot(replica.GetRoot()?.ToArray());
+ foreach (var entry in replica.GetEnumeratorDb())
+ {
+ SetDb(entry.Key.ToArray(), entry.Value.Clone());
+ }
+ }
+
+ ///
+ public override void Deserialize(BinaryReader reader)
+ {
+ base.Deserialize(reader);
+ ClearDb();
+ SetRoot(reader.ReadVarBytes());
+ if (GetRoot().Length == 0)
+ {
+ SetRoot(null);
+ }
+
+ var size = reader.ReadVarInt();
+ for (var i = 0ul; i < size; i++)
+ {
+ var key = reader.ReadVarBytes();
+ var value = MerklePatriciaNode.ExtensionNode();
+ value.Deserialize(reader);
+ SetDb(key, value);
+ }
+ }
+
+ ///
+ public override void Serialize(BinaryWriter writer)
+ {
+ base.Serialize(writer);
+ writer.WriteVarBytes(GetRoot() ?? new byte[0]);
+ writer.WriteVarInt(CountAllDb());
+ foreach (var it in GetEnumeratorDb())
+ {
+ writer.WriteVarBytes(it.Key);
+ writer.Write(it.Value);
+ }
+ }
+
+ ///
+ /// Count the number o values in the MPT not all the nodes, just the ones whith associated values.
+ ///
+ /// The number of values.
+ public int Count() => CountValuesDb();
+
+ ///
+ /// Verify if the MPT contains the specific value.
+ ///
+ /// The value to be checked.
+ /// true if the value is present.
+ public bool ContainsValue(string value) => ContainsValue(Encoding.UTF8.GetBytes(value));
+
+ ///
+ /// Test if the tree contains a specific value.
+ /// Takes O(n) operations.
+ ///
+ /// Value to look for.
+ /// true if the value is present.
+ public bool ContainsValue(byte[] value) => ContainsValueDb(value);
+ }
+}
\ No newline at end of file
diff --git a/MPTStates/MPT/Operation/MPTGet.cs b/MPTStates/MPT/Operation/MPTGet.cs
new file mode 100644
index 000000000..fd027b88e
--- /dev/null
+++ b/MPTStates/MPT/Operation/MPTGet.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Linq;
+
+namespace Neo.Plugins.MPT.Operation
+{
+ ///
+ /// MPT get operation.
+ ///
+ internal class MPTGet
+ {
+ ///
+ /// Delegate to get data from the database.
+ ///
+ private readonly Func _getDb;
+
+ ///
+ /// Delegate to get the root hash.
+ ///
+ private readonly Func _getRoot;
+
+ ///
+ /// MPT get operation.
+ ///
+ /// Delegate to get data from the database.
+ /// Delegate to get the root hash.
+ internal MPTGet(Func _getDb, Func _getRoot)
+ {
+ this._getDb = _getDb;
+ this._getRoot = _getRoot;
+ }
+
+ ///
+ /// Get a value from the database.
+ ///
+ /// Specific key.
+ /// The value attached to the key.
+ /// In the case a null key is received.
+ public byte[] Get(byte[] key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ var root = _getRoot();
+ return root == null ? null : Get(_getDb(root), key.ConvertToNibble());
+ }
+
+ private byte[] Get(MerklePatriciaNode node, byte[] path)
+ {
+ while (true)
+ {
+ if (node == null)
+ {
+ return null;
+ }
+
+ if (node.IsLeaf)
+ {
+ return node.Path.SequenceEqual(path) ? node.Value : null;
+ }
+
+ if (path.Length == 0 && !node.IsExtension)
+ {
+ return node.Value;
+ }
+
+ if (node.IsExtension)
+ {
+ if (path.SequenceEqual(node.Path))
+ {
+ node = _getDb(node.Next);
+ path = new byte[0];
+ continue;
+ }
+
+ if (node.Path.Length < path.Length &&
+ path.Take(node.Path.Length).ToArray().SequenceEqual(node.Path))
+ {
+ var node1 = node;
+ node = _getDb(node.Next);
+ path = path.Skip(node1.Path.Length).ToArray();
+ continue;
+ }
+
+ return null;
+ }
+
+ // Branch node
+ if (node[path[0]] != null)
+ {
+ node = _getDb(node[path[0]]);
+ path = path.Skip(1).ToArray();
+ continue;
+ }
+
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MPTStates/MPT/Operation/MPTRemove.cs b/MPTStates/MPT/Operation/MPTRemove.cs
new file mode 100644
index 000000000..bf73c8738
--- /dev/null
+++ b/MPTStates/MPT/Operation/MPTRemove.cs
@@ -0,0 +1,217 @@
+using System;
+using System.Linq;
+
+namespace Neo.Plugins.MPT.Operation
+{
+ ///
+ /// MPT remove operation.
+ ///
+ internal class MPTRemove
+ {
+ ///
+ /// Delegate to get data from the database.
+ ///
+ private readonly Func _getDb;
+
+ ///
+ /// Delegate to remove data from the database.
+ ///
+ private readonly Func _removeDb;
+
+ ///
+ /// Delegate to set data on the database.
+ ///
+ private readonly Func _setDb;
+
+ ///
+ /// Delegate to get the root hash.
+ ///
+ private readonly Func _getRoot;
+
+ ///
+ /// Delegate to change the root hash.
+ ///
+ private readonly Action _setRoot;
+
+ ///
+ /// MPT remove operation.
+ ///
+ /// Delegate to get data from the database.
+ /// Delegate to remove data from the database.
+ /// Delegate to set data on the database.
+ /// Delegate to get the root hash.
+ /// Delegate to change the root hash.
+ internal MPTRemove(Func _getDb, Func _removeDb,
+ Func _setDb, Func _getRoot, Action _setRoot)
+ {
+ this._getDb = _getDb;
+ this._removeDb = _removeDb;
+ this._setDb = _setDb;
+ this._getRoot = _getRoot;
+ this._setRoot = _setRoot;
+ }
+
+ ///
+ /// Removes the key-value pair from the database.
+ ///
+ /// The key to be removed.
+ /// true in the case the value is found.
+ public bool Remove(byte[] key)
+ {
+ var root = _getRoot();
+ if (root == null)
+ {
+ return false;
+ }
+
+ var removido = Remove(root, key.ConvertToNibble());
+ var resp = removido == null || !root.SequenceEqual(removido);
+ if (resp)
+ {
+ _removeDb(root);
+ }
+
+ _setRoot(removido);
+ return resp;
+ }
+
+ private byte[] Remove(byte[] nodeHash, byte[] path)
+ {
+ if (nodeHash == null)
+ {
+ return null;
+ }
+
+ var node = _getDb(nodeHash);
+ byte[] respHash = null;
+ if (node.IsLeaf)
+ {
+ respHash = RemoveLeaf(nodeHash, path, node);
+ }
+
+ if (node.IsExtension)
+ {
+ respHash = RemoveExtension(nodeHash, path, node);
+ }
+
+ if (node.IsBranch)
+ {
+ respHash = RemoveBranch(nodeHash, path, node);
+ }
+
+ return respHash;
+ }
+
+ private byte[] RemoveLeaf(byte[] nodeHash, byte[] path, MerklePatriciaNode node)
+ {
+ if (node.Path.SequenceEqual(path))
+ {
+ _removeDb(nodeHash);
+ return null;
+ }
+
+ _setDb(nodeHash, node);
+ return nodeHash;
+ }
+
+ private byte[] RemoveExtension(byte[] nodeHash, byte[] path, MerklePatriciaNode node)
+ {
+ if (path.Length >= node.Path.Length &&
+ path.Take(node.Path.Length).ToArray().SequenceEqual(node.Path))
+ {
+ node.Next = Remove(node.Next, path.Skip(node.Path.Length).ToArray());
+ var nodeNext = _getDb(node.Next);
+ if (nodeNext.IsLeaf || nodeNext.IsExtension)
+ {
+ _removeDb(node.Next);
+ nodeNext.Path = node.Path.Concat(nodeNext.Path).ToArray();
+ node = nodeNext;
+ }
+ else
+ {
+ var (index, cont) = nodeNext.IndexAndCountNotNullHashes();
+
+ if (cont == 1 && nodeNext.Value == null)
+ {
+ _removeDb(node.Next);
+ node.Path = node.Path.Concat(new[] {(byte) index}).ToArray();
+ node.Next = nodeNext[index];
+
+ nodeNext = _getDb(node.Next);
+ if (nodeNext.IsExtension)
+ {
+ _removeDb(node.Next);
+ node.Path = node.Path.Concat(nodeNext.Path).ToArray();
+ node.Next = nodeNext.Next;
+ }
+ }
+ }
+ }
+ else
+ {
+ _setDb(nodeHash, node);
+ return nodeHash;
+ }
+
+ _removeDb(nodeHash);
+ nodeHash = node.Hash();
+ _setDb(nodeHash, node);
+ return nodeHash;
+ }
+
+ private byte[] RemoveBranch(byte[] nodeHash, byte[] path, MerklePatriciaNode node)
+ {
+ if (path.Length == 0)
+ {
+ node.Key = null;
+ node.Value = null;
+ }
+ else if (node[path[0]] != null)
+ {
+ node[path[0]] = Remove(node[path[0]], path.Skip(1).ToArray());
+ }
+
+ var (indexInnerNode, contar) = node.IndexAndCountNotNullHashes();
+
+ if (contar == 0)
+ {
+ var newNode = MerklePatriciaNode.LeafNode();
+ newNode.Path = new byte[0];
+ newNode.Key = node.Key;
+ newNode.Value = node.Value;
+ node = newNode;
+ }
+ else if (contar == 1 && node.Value == null)
+ {
+ var innerNodeHash = node[indexInnerNode];
+ var innerNode = _getDb(innerNodeHash);
+ if (innerNode.IsLeaf)
+ {
+ _removeDb(innerNodeHash);
+ node = MerklePatriciaNode.LeafNode();
+ node.Path = new[] {(byte) indexInnerNode}.Concat(innerNode.Path).ToArray();
+ node.Key = innerNode.Key;
+ node.Value = innerNode.Value;
+ }
+ else if (innerNode.IsExtension)
+ {
+ _removeDb(innerNodeHash);
+ node = MerklePatriciaNode.ExtensionNode();
+ node.Path = new[] {(byte) indexInnerNode}.Concat(innerNode.Path).ToArray();
+ node.Next = innerNode.Next;
+ }
+ else if (innerNode.IsBranch)
+ {
+ node = MerklePatriciaNode.ExtensionNode();
+ node.Path = new[] {(byte) indexInnerNode};
+ node.Next = innerNodeHash;
+ }
+ }
+
+ _removeDb(nodeHash);
+ nodeHash = node.Hash();
+ _setDb(nodeHash, node);
+ return nodeHash;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MPTStates/MPT/Operation/MPTSet.cs b/MPTStates/MPT/Operation/MPTSet.cs
new file mode 100644
index 000000000..21ff9455a
--- /dev/null
+++ b/MPTStates/MPT/Operation/MPTSet.cs
@@ -0,0 +1,293 @@
+using System;
+using System.Linq;
+
+namespace Neo.Plugins.MPT.Operation
+{
+ ///
+ /// MPT set operation.
+ ///
+ internal class MPTSet
+ {
+ ///
+ /// Delegate to get data from the database.
+ ///
+ private readonly Func _getDb;
+
+ ///
+ /// Delegate to remove data from the database.
+ ///
+ private readonly Func _removeDb;
+
+ ///
+ /// Delegate to set data on the database.
+ ///
+ private readonly Func _setDb;
+
+ ///
+ /// Delegate to get the root hash.
+ ///
+ private readonly Func _getRoot;
+
+ ///
+ /// Delegate to change the root hash.
+ ///
+ private readonly Action _setRoot;
+
+ ///
+ /// MPT set operation.
+ ///
+ /// Delegate to get data from the database.
+ /// Delegate to remove data from the database.
+ /// Delegate to set data on the database.
+ /// Delegate to get the root hash.
+ /// Delegate to change the root hash.
+ internal MPTSet(Func _getDb, Func _removeDb,
+ Func _setDb, Func _getRoot, Action _setRoot)
+ {
+ this._getDb = _getDb;
+ this._removeDb = _removeDb;
+ this._setDb = _setDb;
+ this._getRoot = _getRoot;
+ this._setRoot = _setRoot;
+ }
+
+ ///
+ /// Set a key-value pair in the MPT.
+ ///
+ /// The key to be inserted.
+ /// The value to be inserted.
+ /// The last value.
+ /// In the case that a null key is used.
+ public byte[] Set(byte[] key, byte[] value)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ var root = _getRoot();
+ var node = root == null ? null : _getDb(root);
+ if (root != null)
+ {
+ _removeDb(root);
+ }
+
+ var resp = Set(node, key.ConvertToNibble(), key, value);
+ _setRoot(resp);
+ return resp;
+ }
+
+ private byte[] Set(MerklePatriciaNode node, byte[] path, byte[] key, byte[] value)
+ {
+ if (node == null)
+ {
+ node = MerklePatriciaNode.LeafNode();
+ node.Path = path;
+ node.Key = key;
+ node.Value = value;
+ }
+ else if (node.IsLeaf)
+ {
+ return SetLeaf(node, path, key, value);
+ }
+ else if (node.IsExtension)
+ {
+ return SetExtension(node, path, key, value);
+ }
+ else
+ {
+ return SetBranch(node, path, key, value);
+ }
+
+ var hashNode = node.Hash();
+ _setDb(hashNode, node);
+ return hashNode;
+ }
+
+ private byte[] SetLeaf(MerklePatriciaNode node, byte[] path, byte[] key, byte[] value)
+ {
+ if (path.Length == 0 || path.SequenceEqual(node.Path))
+ {
+ node.Key = key;
+ node.Value = value;
+ }
+ else if (node.Path.Length == 0 || path[0] != node.Path[0])
+ {
+ var innerHash = SetBranch(MerklePatriciaNode.BranchNode(), node.Path, node.Key, node.Value);
+ node = _getDb(innerHash);
+ _removeDb(innerHash);
+ innerHash = Set(node, path, key, value);
+ node = _getDb(innerHash);
+ _removeDb(innerHash);
+ }
+ else
+ {
+ for (var pos = 0;; pos++)
+ {
+ if (pos + 1 == path.Length || pos + 1 == node.Path.Length ||
+ path[pos + 1] != node.Path[pos + 1])
+ {
+ var innerNode = MerklePatriciaNode.ExtensionNode();
+ innerNode.Path = path.Take(pos + 1).ToArray();
+ innerNode.Next = SetBranch(MerklePatriciaNode.BranchNode(), node.Path.Skip(pos + 1).ToArray(),
+ node.Key,
+ node.Value);
+ node = innerNode;
+ innerNode = _getDb(node.Next);
+ _removeDb(node.Next);
+ node.Next = Set(innerNode, path.Skip(pos + 1).ToArray(), key, value);
+ break;
+ }
+ }
+ }
+
+ var hashNode = node.Hash();
+ _setDb(hashNode, node);
+ return hashNode;
+ }
+
+ private byte[] SetExtension(MerklePatriciaNode node, byte[] path, byte[] key, byte[] value)
+ {
+ void ProcessOldExtension(MerklePatriciaNode nodeValue, MerklePatriciaNode oldExtension)
+ {
+ if (oldExtension.Path.Length == 1)
+ {
+ nodeValue[oldExtension.Path[0]] = oldExtension.Next;
+ }
+ else
+ {
+ var position = oldExtension.Path[0];
+ oldExtension.Path = oldExtension.Path.Skip(1).ToArray();
+ nodeValue[position] = oldExtension.Hash();
+ _setDb(nodeValue[position], oldExtension);
+ }
+ }
+
+ if (path.Length == 0)
+ {
+ var oldExtension = node;
+ _removeDb(node.Hash());
+ node = MerklePatriciaNode.BranchNode();
+ ProcessOldExtension(node, oldExtension);
+
+ SetBranch(node, path, key, value);
+ }
+ else if (path.SequenceEqual(node.Path))
+ {
+ var innerHash = node.Next;
+ var innerNode = _getDb(innerHash);
+ _removeDb(innerHash);
+ node.Next = Set(innerNode, new byte[0], key, value);
+ }
+ else if (node.Path.Length == 0 || path[0] != node.Path[0])
+ {
+ var oldExtension = node;
+ _removeDb(node.Hash());
+ node = MerklePatriciaNode.BranchNode();
+ ProcessOldExtension(node, oldExtension);
+
+ SetBranch(node, path, key, value);
+ }
+ else
+ {
+ for (var pos = 0;; pos++)
+ {
+ if (pos + 1 == node.Path.Length)
+ {
+ var innerHash = node.Next;
+ _removeDb(node.Hash());
+ node.Next = Set(_getDb(innerHash), path.Skip(pos + 1).ToArray(), key, value);
+ _removeDb(innerHash);
+ break;
+ }
+
+ if (pos + 1 == path.Length)
+ {
+ var oldExtension = node;
+ _removeDb(node.Hash());
+ node = MerklePatriciaNode.ExtensionNode();
+ node.Path = oldExtension.Path.Take(pos + 1).ToArray();
+
+ var branchNode = MerklePatriciaNode.BranchNode();
+ oldExtension.Path = oldExtension.Path.Skip(pos + 1).ToArray();
+ if (oldExtension.Path.Length == 1)
+ {
+ branchNode[oldExtension.Path[0]] = oldExtension.Next;
+ }
+ else
+ {
+ var position = oldExtension.Path[0];
+ oldExtension.Path = oldExtension.Path.Skip(1).ToArray();
+ branchNode[position] = oldExtension.Hash();
+ _setDb(branchNode[position], oldExtension);
+ }
+
+ node.Next = SetBranch(branchNode, new byte[0], key, value);
+ break;
+ }
+
+ if (path[pos + 1] != node.Path[pos + 1])
+ {
+ var oldExtension = node;
+ node = MerklePatriciaNode.ExtensionNode();
+ node.Path = oldExtension.Path.Take(pos + 1).ToArray();
+ node.Next = SetBranch(MerklePatriciaNode.BranchNode(), path.Skip(pos + 1).ToArray(), key,
+ value);
+
+ var nodeNext = _getDb(node.Next);
+ _removeDb(node.Next);
+ var oldExtensionPath = oldExtension.Path;
+ oldExtension.Path = oldExtension.Path.Skip(pos + 2).ToArray();
+ if (oldExtension.Path.Length > 0)
+ {
+ nodeNext[oldExtensionPath[pos + 1]] = oldExtension.Hash();
+ _setDb(nodeNext[oldExtensionPath[pos + 1]], oldExtension);
+ }
+ else
+ {
+ nodeNext[oldExtensionPath[pos + 1]] = oldExtension.Next;
+ }
+
+ node.Next = nodeNext.Hash();
+ _setDb(node.Next, nodeNext);
+
+ break;
+ }
+ }
+ }
+
+ var hashNode = node.Hash();
+ _setDb(hashNode, node);
+ return hashNode;
+ }
+
+ private byte[] SetBranch(MerklePatriciaNode node, byte[] path, byte[] key, byte[] value)
+ {
+ if (path.Length == 0)
+ {
+ node.Key = key;
+ node.Value = value;
+ }
+ else
+ {
+ var innerHash = node[path[0]];
+ var innerNode = innerHash != null ? _getDb(innerHash) : null;
+ if (innerHash != null)
+ {
+ _removeDb(innerHash);
+ }
+
+ node[path[0]] = Set(innerNode, path.Skip(1).ToArray(), key, value);
+ }
+
+ var hashNode = node.Hash();
+ _setDb(hashNode, node);
+ return hashNode;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MPTStates/MPT/Operation/MPTValidate.cs b/MPTStates/MPT/Operation/MPTValidate.cs
new file mode 100644
index 000000000..b2380409d
--- /dev/null
+++ b/MPTStates/MPT/Operation/MPTValidate.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Linq;
+
+namespace Neo.Plugins.MPT.Operation
+{
+ ///
+ /// MPT validate operation.
+ ///
+ internal class MPTValidate
+ {
+ ///
+ /// Delegate to get data from the database.
+ ///
+ private readonly Func _getDb;
+
+ ///
+ /// Delegate to check if the database contains the key.
+ ///
+ private readonly Func _containsKeyDb;
+
+ ///
+ /// MPT validate operation.
+ ///
+ /// Delegate to get data from the database.
+ /// Delegate to check if the database contains the key.
+ internal MPTValidate(Func _getDb, Func _containsKeyDb)
+ {
+ this._getDb = _getDb;
+ this._containsKeyDb = _containsKeyDb;
+ }
+
+ ///
+ /// Check if the MPT is valid.
+ ///
+ /// Root key.
+ /// true case the MPT is valid.
+ public bool Validate(byte[] key) => key == null
+ || (_containsKeyDb(key) && Validate(key, _getDb(key)));
+
+ private bool Validate(byte[] nodeHash, MerklePatriciaNode node)
+ {
+ while (true)
+ {
+ if (!nodeHash.SequenceEqual(node.Hash()))
+ {
+ return false;
+ }
+
+ if (node.IsExtension)
+ {
+ nodeHash = node.Next;
+ node = _getDb(node.Next);
+ continue;
+ }
+
+ if (node.IsLeaf)
+ {
+ return true;
+ }
+
+ foreach (var subNodeHash in node)
+ {
+ if (subNodeHash != null && !Validate(subNodeHash, _getDb(subNodeHash)))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MPTStates/MPTStates.csproj b/MPTStates/MPTStates.csproj
new file mode 100644
index 000000000..a8212489c
--- /dev/null
+++ b/MPTStates/MPTStates.csproj
@@ -0,0 +1,23 @@
+
+
+ 2.10.2
+ netstandard2.0
+ Neo.Plugins
+
+
+
+ PreserveNewest
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 4b5a3170f..bead2aa45 100644
--- a/README.md
+++ b/README.md
@@ -47,3 +47,8 @@ Enables policies for Consensus or Seed Nodes. In particular, policies for accept
### StatesDumper
Exports NEO-CLI status data \(useful for debugging\).
+
+### MPTStates
+An experimental plugin for persisting neo blockchain states into Merkle Patricia Tries.
+It is related to the implementation on the [PR-528](https://github.com/neo-project/neo/pull/528) of the main project.
+
diff --git a/neo-plugins.sln b/neo-plugins.sln
index 4c1f919f6..a9709f8c2 100644
--- a/neo-plugins.sln
+++ b/neo-plugins.sln
@@ -23,6 +23,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreMetrics", "CoreMetrics\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcSystemAssetTracker", "RpcSystemAssetTracker\RpcSystemAssetTracker.csproj", "{D3C183C1-8C23-4566-8909-EBF468DAD67A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MPTStates", "MPTStates\MPTStates.csproj", "{4704220C-75F4-4A8C-B55C-7E809D6AAAA6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MPTStates.UnitTests", "MPTStates.UnitTests\MPTStates.UnitTests.csproj", "{A4517E03-B2E8-47EF-92C9-987C7E6508E7}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -68,6 +72,14 @@ Global
{D3C183C1-8C23-4566-8909-EBF468DAD67A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3C183C1-8C23-4566-8909-EBF468DAD67A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3C183C1-8C23-4566-8909-EBF468DAD67A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4704220C-75F4-4A8C-B55C-7E809D6AAAA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4704220C-75F4-4A8C-B55C-7E809D6AAAA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4704220C-75F4-4A8C-B55C-7E809D6AAAA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4704220C-75F4-4A8C-B55C-7E809D6AAAA6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A4517E03-B2E8-47EF-92C9-987C7E6508E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A4517E03-B2E8-47EF-92C9-987C7E6508E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A4517E03-B2E8-47EF-92C9-987C7E6508E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A4517E03-B2E8-47EF-92C9-987C7E6508E7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE