forked from neo-project/neo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make PoolItem independent and add PoolItem tests (neo-project#562)
* make poolitem independent * Merging * Multiply by -1 * Fix other * Fix Tx * Removing -1 extra multiplication * Fix * make PoolItem internal and added test class * Update PoolItem.cs * added comments for PoolItem variables * getting time from TimeProvider to allow testing * basic test * reset time provider * Add Hash comparison * Adding time provider again and equals * Fix arithmetic * Comment on PoolItem * Update PoolItem.cs * protecting tests against TimeProvider changes on fails * reusing setup part * fixed serialization properties * Improve generation of creating mock DateTime values. Implement hash comparison tests. * Adjust comment.
- Loading branch information
1 parent
8fe4920
commit 688de1c
Showing
4 changed files
with
218 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
using System; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using Moq; | ||
using Neo.Ledger; | ||
using FluentAssertions; | ||
using Neo.Network.P2P.Payloads; | ||
|
||
namespace Neo.UnitTests | ||
{ | ||
[TestClass] | ||
public class UT_PoolItem | ||
{ | ||
//private PoolItem uut; | ||
private static readonly Random TestRandom = new Random(1337); // use fixed seed for guaranteed determinism | ||
|
||
[TestInitialize] | ||
public void TestSetup() | ||
{ | ||
var timeValues = new[] { | ||
new DateTime(1968, 06, 01, 0, 0, 1, DateTimeKind.Utc), | ||
}; | ||
|
||
var timeMock = new Mock<TimeProvider>(); | ||
timeMock.SetupGet(tp => tp.UtcNow).Returns(() => timeValues[0]) | ||
.Callback(() => timeValues[0] = timeValues[0].Add(TimeSpan.FromSeconds(1))); | ||
TimeProvider.Current = timeMock.Object; | ||
} | ||
|
||
[TestCleanup] | ||
public void TestCleanup() | ||
{ | ||
// important to leave TimeProvider correct | ||
TimeProvider.ResetToDefault(); | ||
} | ||
|
||
[TestMethod] | ||
public void PoolItem_CompareTo_Fee() | ||
{ | ||
int size1 = 50; | ||
int netFeeSatoshi1 = 1; | ||
var tx1 = MockGenerateInvocationTx(new Fixed8(netFeeSatoshi1), size1); | ||
int size2 = 50; | ||
int netFeeSatoshi2 = 2; | ||
var tx2 = MockGenerateInvocationTx(new Fixed8(netFeeSatoshi2), size2); | ||
|
||
PoolItem pitem1 = new PoolItem(tx1.Object); | ||
PoolItem pitem2 = new PoolItem(tx2.Object); | ||
|
||
Console.WriteLine($"item1 time {pitem1.Timestamp} item2 time {pitem2.Timestamp}"); | ||
// pitem1 < pitem2 (fee) => -1 | ||
pitem1.CompareTo(pitem2).Should().Be(-1); | ||
// pitem2 > pitem1 (fee) => 1 | ||
pitem2.CompareTo(pitem1).Should().Be(1); | ||
} | ||
|
||
[TestMethod] | ||
public void PoolItem_CompareTo_Hash() | ||
{ | ||
int sizeFixed = 50; | ||
int netFeeSatoshiFixed = 1; | ||
|
||
for (int testRuns = 0; testRuns < 30; testRuns++) | ||
{ | ||
var tx1 = GenerateMockTxWithFirstByteOfHashGreaterThanOrEqualTo(0x80, new Fixed8(netFeeSatoshiFixed), sizeFixed); | ||
var tx2 = GenerateMockTxWithFirstByteOfHashLessThanOrEqualTo(0x79,new Fixed8(netFeeSatoshiFixed), sizeFixed); | ||
|
||
PoolItem pitem1 = new PoolItem(tx1.Object); | ||
PoolItem pitem2 = new PoolItem(tx2.Object); | ||
|
||
// pitem2 < pitem1 (fee) => -1 | ||
pitem2.CompareTo(pitem1).Should().Be(-1); | ||
|
||
// pitem1 > pitem2 (fee) => 1 | ||
pitem1.CompareTo(pitem2).Should().Be(1); | ||
} | ||
|
||
// equal hashes should be equal | ||
var tx3 = MockGenerateInvocationTx(new Fixed8(netFeeSatoshiFixed), sizeFixed, new byte[] {0x13, 0x37}); | ||
var tx4 = MockGenerateInvocationTx(new Fixed8(netFeeSatoshiFixed), sizeFixed, new byte[] {0x13, 0x37}); | ||
PoolItem pitem3 = new PoolItem(tx3.Object); | ||
PoolItem pitem4 = new PoolItem(tx4.Object); | ||
|
||
pitem3.CompareTo(pitem4).Should().Be(0); | ||
pitem4.CompareTo(pitem3).Should().Be(0); | ||
} | ||
|
||
[TestMethod] | ||
public void PoolItem_CompareTo_Equals() | ||
{ | ||
int sizeFixed = 500; | ||
int netFeeSatoshiFixed = 10; | ||
var tx1 = MockGenerateInvocationTx(new Fixed8(netFeeSatoshiFixed), sizeFixed, new byte[] {0x13, 0x37}); | ||
var tx2 = MockGenerateInvocationTx(new Fixed8(netFeeSatoshiFixed), sizeFixed, new byte[] {0x13, 0x37}); | ||
|
||
PoolItem pitem1 = new PoolItem(tx1.Object); | ||
PoolItem pitem2 = new PoolItem(tx2.Object); | ||
|
||
// pitem1 == pitem2 (fee) => 0 | ||
pitem1.CompareTo(pitem2).Should().Be(0); | ||
pitem2.CompareTo(pitem1).Should().Be(0); | ||
} | ||
|
||
public Mock<InvocationTransaction> GenerateMockTxWithFirstByteOfHashGreaterThanOrEqualTo(byte firstHashByte, Fixed8 networkFee, int size) | ||
{ | ||
Mock<InvocationTransaction> mockTx; | ||
do | ||
{ | ||
mockTx = MockGenerateInvocationTx(networkFee, size); | ||
} while (mockTx.Object.Hash >= new UInt256(TestUtils.GetByteArray(32, firstHashByte))); | ||
|
||
return mockTx; | ||
} | ||
|
||
public Mock<InvocationTransaction> GenerateMockTxWithFirstByteOfHashLessThanOrEqualTo(byte firstHashByte, Fixed8 networkFee, int size) | ||
{ | ||
Mock<InvocationTransaction> mockTx; | ||
do | ||
{ | ||
mockTx = MockGenerateInvocationTx(networkFee, size); | ||
} while (mockTx.Object.Hash <= new UInt256(TestUtils.GetByteArray(32, firstHashByte))); | ||
|
||
return mockTx; | ||
} | ||
|
||
|
||
// Generate Mock InvocationTransaction with different sizes and prices | ||
public static Mock<InvocationTransaction> MockGenerateInvocationTx(Fixed8 networkFee, int size, byte[] overrideScriptBytes=null) | ||
{ | ||
var mockTx = new Mock<InvocationTransaction>(); | ||
mockTx.CallBase = true; | ||
mockTx.SetupGet(mr => mr.NetworkFee).Returns(networkFee); | ||
mockTx.SetupGet(mr => mr.Size).Returns(size); | ||
|
||
var tx = mockTx.Object; | ||
// use random bytes in the script to get a different hash since we cannot mock the Hash | ||
byte[] randomBytes; | ||
if (overrideScriptBytes != null) | ||
randomBytes = overrideScriptBytes; | ||
else | ||
{ | ||
randomBytes = new byte[16]; | ||
TestRandom.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]; | ||
return mockTx; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
using Neo.Network.P2P.Payloads; | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Runtime.CompilerServices; | ||
using System.Threading; | ||
using Akka.Util.Internal; | ||
using Neo.Network.P2P; | ||
using Neo.Persistence; | ||
using Neo.Plugins; | ||
|
||
namespace Neo.Ledger | ||
{ | ||
/// <summary> | ||
/// Represents an item in the Memory Pool. | ||
/// | ||
// Note: PoolItem objects don't consider transaction priority (low or high) in their compare CompareTo method. | ||
/// This is because items of differing priority are never added to the same sorted set in MemoryPool. | ||
/// </summary> | ||
internal class PoolItem : IComparable<PoolItem> | ||
{ | ||
/// <summary> | ||
/// Internal transaction for PoolItem | ||
/// </summary> | ||
public readonly Transaction Tx; | ||
|
||
/// <summary> | ||
/// Timestamp when transaction was stored on PoolItem | ||
/// </summary> | ||
public readonly DateTime Timestamp; | ||
|
||
/// <summary> | ||
/// Timestamp where this transaction was last broadcast to other nodes | ||
/// </summary> | ||
public DateTime LastBroadcastTimestamp; | ||
|
||
internal PoolItem(Transaction tx) | ||
{ | ||
Tx = tx; | ||
Timestamp = TimeProvider.Current.UtcNow; | ||
LastBroadcastTimestamp = Timestamp; | ||
} | ||
|
||
public int CompareTo(Transaction otherTx) | ||
{ | ||
if (otherTx == null) return 1; | ||
// Fees sorted ascending | ||
int ret = Tx.FeePerByte.CompareTo(otherTx.FeePerByte); | ||
if (ret != 0) return ret; | ||
ret = Tx.NetworkFee.CompareTo(otherTx.NetworkFee); | ||
if (ret != 0) return ret; | ||
// Transaction hash sorted descending | ||
return otherTx.Hash.CompareTo(Tx.Hash); | ||
} | ||
|
||
public int CompareTo(PoolItem otherItem) | ||
{ | ||
if (otherItem == null) return 1; | ||
return CompareTo(otherItem.Tx); | ||
} | ||
} | ||
} |