diff --git a/neo.UnitTests/Ledger/UT_MemoryPool.cs b/neo.UnitTests/Ledger/UT_MemoryPool.cs index 6f9022bef1..631ae06dbc 100644 --- a/neo.UnitTests/Ledger/UT_MemoryPool.cs +++ b/neo.UnitTests/Ledger/UT_MemoryPool.cs @@ -8,6 +8,7 @@ using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.Plugins; +using Neo.SmartContract; using Neo.SmartContract.Native; using System; using System.Collections; @@ -92,6 +93,31 @@ private Transaction CreateTransactionWithFee(long fee) return mock.Object; } + private Transaction CreateTransactionWithFeeAndBalanceVerify(long fee) + { + Random random = new Random(); + var randomBytes = new byte[16]; + random.NextBytes(randomBytes); + Mock mock = new Mock(); + UInt160 sender = UInt160.Zero; + mock.Setup(p => p.Reverify(It.IsAny(), It.IsAny())).Returns(((Snapshot snapshot, BigInteger amount) => NativeContract.GAS.BalanceOf(snapshot, sender) >= amount + fee)); + mock.Setup(p => p.Verify(It.IsAny(), It.IsAny())).Returns(true); + mock.Object.Script = randomBytes; + mock.Object.Sender = sender; + mock.Object.NetworkFee = fee; + mock.Object.Attributes = new TransactionAttribute[0]; + mock.Object.Cosigners = new Cosigner[0]; + mock.Object.Witnesses = new[] + { + new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + } + }; + return mock.Object; + } + private Transaction CreateTransaction(long fee = -1) { if (fee != -1) @@ -115,6 +141,17 @@ private void AddTransaction(Transaction txToAdd) _unit.TryAdd(txToAdd.Hash, txToAdd); } + private void AddTransactionsWithBalanceVerify(int count, long fee) + { + for (int i = 0; i < count; i++) + { + var txToAdd = CreateTransactionWithFeeAndBalanceVerify(fee); + _unit.TryAdd(txToAdd.Hash, txToAdd); + } + + Console.WriteLine($"created {count} tx"); + } + [TestMethod] public void CapacityTest() { @@ -171,6 +208,38 @@ public void BlockPersistMovesTxToUnverifiedAndReverification() _unit.UnverifiedSortedTxCount.Should().Be(0); } + [TestMethod] + public void BlockPersistAndReverificationWillAbandonTxAsBalanceTransfered() + { + long txFee = 1; + AddTransactionsWithBalanceVerify(70, txFee); + + _unit.SortedTxCount.Should().Be(70); + + var block = new Block + { + Transactions = _unit.GetSortedVerifiedTransactions().Take(10).ToArray() + }; + + // Simulate the transfer process in tx by burning the balance + UInt160 sender = block.Transactions[0].Sender; + Snapshot snapshot = Blockchain.Singleton.GetSnapshot(); + BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, sender); + + ApplicationEngine applicationEngine = new ApplicationEngine(TriggerType.All, block, snapshot, (long)balance); + NativeContract.GAS.Burn(applicationEngine, sender, balance); + NativeContract.GAS.Mint(applicationEngine, sender, txFee * 30); // Set the balance to meet 30 txs only + + // Persist block and reverify all the txs in mempool, but half of the txs will be discarded + _unit.UpdatePoolForBlockPersisted(block, snapshot); + _unit.SortedTxCount.Should().Be(30); + _unit.UnverifiedSortedTxCount.Should().Be(0); + + // Revert the balance + NativeContract.GAS.Burn(applicationEngine, sender, txFee * 30); + NativeContract.GAS.Mint(applicationEngine, sender, balance); + } + private void VerifyTransactionsSortedDescending(IEnumerable transactions) { Transaction lastTransaction = null; diff --git a/neo/Ledger/MemoryPool.cs b/neo/Ledger/MemoryPool.cs index 15c647f163..78df9b97f6 100644 --- a/neo/Ledger/MemoryPool.cs +++ b/neo/Ledger/MemoryPool.cs @@ -417,7 +417,10 @@ private int ReverifyTransactions(SortedSet verifiedSortedTxPool, foreach (PoolItem item in unverifiedSortedTxPool.Reverse().Take(count)) { if (item.Tx.Reverify(snapshot, SendersFeeMonitor.GetSenderFee(item.Tx.Sender))) + { reverifiedItems.Add(item); + SendersFeeMonitor.AddSenderFee(item.Tx); + } else // Transaction no longer valid -- it will be removed from unverifiedTxPool. invalidItems.Add(item); @@ -438,7 +441,6 @@ private int ReverifyTransactions(SortedSet verifiedSortedTxPool, { if (_unsortedTransactions.TryAdd(item.Tx.Hash, item)) { - SendersFeeMonitor.AddSenderFee(item.Tx); verifiedSortedTxPool.Add(item); if (item.LastBroadcastTimestamp < rebroadcastCutOffTime) @@ -447,6 +449,8 @@ private int ReverifyTransactions(SortedSet verifiedSortedTxPool, item.LastBroadcastTimestamp = DateTime.UtcNow; } } + else + SendersFeeMonitor.RemoveSenderFee(item.Tx); _unverifiedTransactions.Remove(item.Tx.Hash); unverifiedSortedTxPool.Remove(item);