diff --git a/neo.UnitTests/TestDataCache.cs b/neo.UnitTests/TestDataCache.cs index d2d3afdabf..68f3b07da7 100644 --- a/neo.UnitTests/TestDataCache.cs +++ b/neo.UnitTests/TestDataCache.cs @@ -10,6 +10,17 @@ 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) { } @@ -25,12 +36,13 @@ protected override void AddInternal(TKey key, TValue value) protected override TValue GetInternal(TKey key) { - throw new NotImplementedException(); + if (_defaultValue == null) throw new NotImplementedException(); + return _defaultValue; } protected override TValue TryGetInternal(TKey key) { - return null; + return _defaultValue; } protected override void UpdateInternal(TKey key, TValue value) diff --git a/neo.UnitTests/UT_MemoryPool.cs b/neo.UnitTests/UT_MemoryPool.cs new file mode 100644 index 0000000000..e9094ece93 --- /dev/null +++ b/neo.UnitTests/UT_MemoryPool.cs @@ -0,0 +1,214 @@ +using System; +using System.Linq; +using System.Reflection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Ledger; +using FluentAssertions; +using Neo.Cryptography.ECC; +using Neo.IO.Wrappers; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; + +namespace Neo.UnitTests +{ + [TestClass] + public class UT_MemoryPool + { + private static NeoSystem TheNeoSystem; + + private readonly Random _random = new Random(); + + private MemoryPool _unit; + + [TestInitialize] + public void TestSetup() + { + if (TheNeoSystem == null) + { + var mockSnapshot = new Mock(); + mockSnapshot.SetupGet(p => p.Blocks).Returns(new TestDataCache()); + mockSnapshot.SetupGet(p => p.Transactions).Returns(new TestDataCache()); + mockSnapshot.SetupGet(p => p.Accounts).Returns(new TestDataCache()); + mockSnapshot.SetupGet(p => p.UnspentCoins).Returns(new TestDataCache()); + mockSnapshot.SetupGet(p => p.SpentCoins).Returns(new TestDataCache()); + mockSnapshot.SetupGet(p => p.Validators).Returns(new TestDataCache()); + mockSnapshot.SetupGet(p => p.Assets).Returns(new TestDataCache()); + mockSnapshot.SetupGet(p => p.Contracts).Returns(new TestDataCache()); + mockSnapshot.SetupGet(p => p.Storages).Returns(new TestDataCache()); + mockSnapshot.SetupGet(p => p.HeaderHashList) + .Returns(new TestDataCache()); + mockSnapshot.SetupGet(p => p.ValidatorsCount).Returns(new TestMetaDataCache()); + mockSnapshot.SetupGet(p => p.BlockHashIndex).Returns(new TestMetaDataCache()); + mockSnapshot.SetupGet(p => p.HeaderHashIndex).Returns(new TestMetaDataCache()); + + var mockStore = new Mock(); + + var defaultTx = CreateRandomHashInvocationTransaction(); + defaultTx.Outputs = new TransactionOutput[1]; + defaultTx.Outputs[0] = new TransactionOutput + { + AssetId = Blockchain.UtilityToken.Hash, + Value = new Fixed8(1000000), // 0.001 GAS (enough to be a high priority TX + ScriptHash = UInt160.Zero // doesn't matter for our purposes. + }; + + mockStore.Setup(p => p.GetBlocks()).Returns(new TestDataCache()); + mockStore.Setup(p => p.GetTransactions()).Returns(new TestDataCache( + new TransactionState + { + BlockIndex = 1, + Transaction = defaultTx + })); + + mockStore.Setup(p => p.GetAccounts()).Returns(new TestDataCache()); + mockStore.Setup(p => p.GetUnspentCoins()).Returns(new TestDataCache()); + mockStore.Setup(p => p.GetSpentCoins()).Returns(new TestDataCache()); + mockStore.Setup(p => p.GetValidators()).Returns(new TestDataCache()); + mockStore.Setup(p => p.GetAssets()).Returns(new TestDataCache()); + mockStore.Setup(p => p.GetContracts()).Returns(new TestDataCache()); + mockStore.Setup(p => p.GetStorages()).Returns(new TestDataCache()); + mockStore.Setup(p => p.GetHeaderHashList()).Returns(new TestDataCache()); + mockStore.Setup(p => p.GetValidatorsCount()).Returns(new TestMetaDataCache()); + mockStore.Setup(p => p.GetBlockHashIndex()).Returns(new TestMetaDataCache()); + mockStore.Setup(p => p.GetHeaderHashIndex()).Returns(new TestMetaDataCache()); + mockStore.Setup(p => p.GetSnapshot()).Returns(mockSnapshot.Object); + + Console.WriteLine("initialize NeoSystem"); + TheNeoSystem = new NeoSystem(mockStore.Object); // new Mock(mockStore.Object); + } + + // Create a MemoryPool with capacity of 100 + _unit = new MemoryPool(TheNeoSystem, 100); + + // Verify capacity equals the amount specified + _unit.Capacity.ShouldBeEquivalentTo(100); + + _unit.VerifiedCount.ShouldBeEquivalentTo(0); + _unit.UnVerifiedCount.ShouldBeEquivalentTo(0); + _unit.Count.ShouldBeEquivalentTo(0); + } + + private Transaction CreateRandomHashInvocationTransaction() + { + var tx = new InvocationTransaction(); + var randomBytes = new byte[16]; + _random.NextBytes(randomBytes); + tx.Script = randomBytes; + tx.Attributes = new TransactionAttribute[0]; + tx.Inputs = new CoinReference[0]; + tx.Outputs = new TransactionOutput[0]; + tx.Witnesses = new Witness[0]; + // Force getting the references + // Console.WriteLine($"Reference Count: {tx.References.Count}"); + return tx; + } + + private Transaction CreateMockHighPriorityTransaction() + { + var tx = CreateRandomHashInvocationTransaction(); + tx.Inputs = new CoinReference[1]; + // Any input will trigger reading the transaction output and get our mocked transaction output. + tx.Inputs[0] = new CoinReference + { + PrevHash = UInt256.Zero, + PrevIndex = 0 + }; + return tx; + } + + + private Transaction CreateMockLowPriorityTransaction() + { + return CreateRandomHashInvocationTransaction(); + } + + private void AddTransactions(int count, bool isHighPriority=false) + { + for (int i = 0; i < count; i++) + { + var lowPrioTx = isHighPriority ? CreateMockHighPriorityTransaction(): CreateMockLowPriorityTransaction(); + Console.WriteLine($"created tx: {lowPrioTx.Hash}"); + _unit.TryAdd(lowPrioTx.Hash, lowPrioTx); + } + } + + private void AddLowPriorityTransactions(int count) => AddTransactions(count); + public void AddHighPriorityTransactions(int count) => AddTransactions(count, true); + + + [TestMethod] + public void LowPriorityCapacityTest() + { + // Add over the capacity items, verify that the verified count increases each time + AddLowPriorityTransactions(50); + _unit.VerifiedCount.ShouldBeEquivalentTo(50); + AddLowPriorityTransactions(51); + Console.WriteLine($"VerifiedCount: {_unit.VerifiedCount} LowPrioCount {_unit.SortedLowPrioTxCount} HighPrioCount {_unit.SortedHighPrioTxCount}"); + _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(100); + _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(0); + + _unit.VerifiedCount.ShouldBeEquivalentTo(100); + _unit.UnVerifiedCount.ShouldBeEquivalentTo(0); + _unit.Count.ShouldBeEquivalentTo(100); + } + + [TestMethod] + public void HighPriorityCapacityTest() + { + // Add over the capacity items, verify that the verified count increases each time + AddHighPriorityTransactions(101); + + Console.WriteLine($"VerifiedCount: {_unit.VerifiedCount} LowPrioCount {_unit.SortedLowPrioTxCount} HighPrioCount {_unit.SortedHighPrioTxCount}"); + _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(0); + _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(100); + + _unit.VerifiedCount.ShouldBeEquivalentTo(100); + _unit.UnVerifiedCount.ShouldBeEquivalentTo(0); + _unit.Count.ShouldBeEquivalentTo(100); + } + + [TestMethod] + public void HighPriorityPushesOutLowPriority() + { + // Add over the capacity items, verify that the verified count increases each time + AddLowPriorityTransactions(70); + AddHighPriorityTransactions(40); + + Console.WriteLine($"VerifiedCount: {_unit.VerifiedCount} LowPrioCount {_unit.SortedLowPrioTxCount} HighPrioCount {_unit.SortedHighPrioTxCount}"); + _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(60); + _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(40); + _unit.Count.ShouldBeEquivalentTo(100); + } + + [TestMethod] + public void LowPriorityDoesNotPushOutHighPrority() + { + AddHighPriorityTransactions(70); + AddLowPriorityTransactions(40); + + _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(30); + _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(70); + _unit.Count.ShouldBeEquivalentTo(100); + } + + [TestMethod] + public void BlockPersistMovesTxToUnverified() + { + AddLowPriorityTransactions(30); + AddHighPriorityTransactions(70); + + + var block = new Block + { + Transactions = _unit.GetSortedVerifiedTransactions().Take(10) + .Concat(_unit.GetSortedVerifiedTransactions().Where(x => x.IsLowPriority).Take(5)).ToArray() + }; + _unit.UpdatePoolForBlockPersisted(block, Blockchain.Singleton.GetSnapshot()); + _unit.SortedLowPrioTxCount.ShouldBeEquivalentTo(0); + _unit.SortedHighPrioTxCount.ShouldBeEquivalentTo(0); + _unit.UnverifiedSortedHighPrioTxCount.ShouldBeEquivalentTo(60); + _unit.UnverifiedSortedLowPrioTxCount.ShouldBeEquivalentTo(25); + } + } +} \ No newline at end of file diff --git a/neo/Ledger/MemoryPool.cs b/neo/Ledger/MemoryPool.cs index 1d05679934..67de6f05a1 100644 --- a/neo/Ledger/MemoryPool.cs +++ b/neo/Ledger/MemoryPool.cs @@ -73,13 +73,14 @@ public int CompareTo(PoolItem otherItem) /// private readonly Dictionary _unsortedTransactions = new Dictionary(); /// - /// Stores the verified low priority sorted transactions currently in the pool. - /// - private readonly SortedSet _sortedLowPrioTransactions = new SortedSet(); - /// /// Stores the verified high priority sorted transactins currently in the pool. /// private readonly SortedSet _sortedHighPrioTransactions = new SortedSet(); + /// + /// Stores the verified low priority sorted transactions currently in the pool. + /// + private readonly SortedSet _sortedLowPrioTransactions = new SortedSet(); + /// /// Store the unverified transactions currently in the pool. @@ -92,6 +93,13 @@ public int CompareTo(PoolItem otherItem) private readonly SortedSet _unverifiedSortedHighPriorityTransactions = new SortedSet(); private readonly SortedSet _unverifiedSortedLowPriorityTransactions = new SortedSet(); + // internal methods to aid in unit testing + internal int SortedHighPrioTxCount => _sortedHighPrioTransactions.Count; + internal int SortedLowPrioTxCount => _sortedLowPrioTransactions.Count; + internal int UnverifiedSortedHighPrioTxCount => _unverifiedSortedHighPriorityTransactions.Count; + internal int UnverifiedSortedLowPrioTxCount => _unverifiedSortedLowPriorityTransactions.Count; + + private int _maxTxPerBlock; private int _maxLowPriorityTxPerBlock; @@ -401,7 +409,7 @@ internal void UpdatePoolForBlockPersisted(Block block, Snapshot snapshot) // If we know about headers of future blocks, no point in verifying transactions from the unverified tx pool // until we get caught up. - if (block.Index < Blockchain.Singleton.HeaderHeight) + if (block.Index > 0 && block.Index < Blockchain.Singleton.HeaderHeight) return; if (Plugin.Policies.Count == 0)