Skip to content
This repository has been archived by the owner on Aug 16, 2021. It is now read-only.

Mem pool DescendantScore tests #8

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 206 additions & 7 deletions Stratis.Bitcoin.Tests/MemoryPoolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ public TxMemPoolEntry FromTx(Transaction tx, TxMemPool pool = null)
}

// Change the default value
TestMemPoolEntryHelper Fee(Money _fee) { nFee = _fee; return this; }
TestMemPoolEntryHelper Time(long _time) { nTime = _time; return this; }
TestMemPoolEntryHelper Priority(double _priority) { dPriority = _priority; return this; }
TestMemPoolEntryHelper Height(int _height) { nHeight = _height; return this; }
TestMemPoolEntryHelper SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return this; }
TestMemPoolEntryHelper SigOpsCost(long _sigopsCost) { sigOpCost = _sigopsCost; return this; }
public TestMemPoolEntryHelper Fee(Money _fee) { nFee = _fee; return this; }
public TestMemPoolEntryHelper Time(long _time) { nTime = _time; return this; }
public TestMemPoolEntryHelper Priority(double _priority) { dPriority = _priority; return this; }
public TestMemPoolEntryHelper Height(int _height) { nHeight = _height; return this; }
public TestMemPoolEntryHelper SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return this; }
public TestMemPoolEntryHelper SigOpsCost(long _sigopsCost) { sigOpCost = _sigopsCost; return this; }

}

Expand Down Expand Up @@ -129,5 +129,204 @@ public void MempoolRemoveTest()
Assert.Equal(testPool.Size, poolSize - 6);
Assert.Equal(testPool.Size, 0);
}
}

private void CheckSort(TxMemPool pool, List<TxMemPoolEntry> sortedSource, List<string> sortedOrder)
{
Assert.Equal(pool.Size, sortedOrder.Count());
int count = 0;
using (var it = sortedSource.GetEnumerator())
for (; it.MoveNext(); ++count)
{
Assert.Equal(it.Current.TransactionHash.ToString(), sortedOrder[count]);
}
}

[Fact]
public void MempoolIndexingTest()
{

//Transaction xxx = new Transaction();
//xxx.AddOutput(new TxOut(new Money(000 * Money.COIN), new Script(OpcodeType.OP_11, OpcodeType.OP_EQUAL)));
//pool.AddUnchecked(xxx.GetHash(), entry.Fee(new Money(000L)).Priority(000.0).FromTx(xxx));

var pool = new TxMemPool(new FeeRate(0));
var entry = new TestMemPoolEntryHelper();

/* 3rd highest fee */
Transaction tx1 = new Transaction();
tx1.AddOutput(new TxOut(new Money(10 * Money.COIN), new Script(OpcodeType.OP_11, OpcodeType.OP_EQUAL)));
pool.AddUnchecked(tx1.GetHash(), entry.Fee(new Money(10000L)).Priority(10.0).FromTx(tx1));

/* highest fee */
Transaction tx2 = new Transaction();
tx2.AddOutput(new TxOut(new Money(2 * Money.COIN), new Script(OpcodeType.OP_11, OpcodeType.OP_EQUAL)));
pool.AddUnchecked(tx2.GetHash(), entry.Fee(new Money(20000L)).Priority(9.0).FromTx(tx2));

/* lowest fee */
Transaction tx3 = new Transaction();
tx3.AddOutput(new TxOut(new Money(5 * Money.COIN), new Script(OpcodeType.OP_11, OpcodeType.OP_EQUAL)));
pool.AddUnchecked(tx3.GetHash(), entry.Fee(new Money(0L)).Priority(100.0).FromTx(tx3));

/* 2nd highest fee */
Transaction tx4 = new Transaction();
tx4.AddOutput(new TxOut(new Money(6 * Money.COIN), new Script(OpcodeType.OP_11, OpcodeType.OP_EQUAL)));
pool.AddUnchecked(tx4.GetHash(), entry.Fee(new Money(15000L)).Priority(1.0).FromTx(tx4));

/* equal fee rate to tx1, but newer */
Transaction tx5 = new Transaction();
tx5.AddOutput(new TxOut(new Money(11 * Money.COIN), new Script(OpcodeType.OP_11, OpcodeType.OP_EQUAL)));
pool.AddUnchecked(tx5.GetHash(), entry.Fee(new Money(10000L)).Priority(10.0).Time(1).FromTx(tx5));

// assert size
Assert.Equal(pool.Size, 5);

List<string> sortedOrder = new List<string>(5);
sortedOrder.Insert(0, tx3.GetHash().ToString()); // 0
sortedOrder.Insert(1, tx5.GetHash().ToString()); // 10000
sortedOrder.Insert(2, tx1.GetHash().ToString()); // 10000
sortedOrder.Insert(3, tx4.GetHash().ToString()); // 15000
sortedOrder.Insert(4, tx2.GetHash().ToString()); // 20000
CheckSort(pool, pool.MapTx.DescendantScore.ToList(), sortedOrder);

/* low fee but with high fee child */
/* tx6 -> tx7 -> tx8, tx9 -> tx10 */
Transaction tx6 = new Transaction();
tx6.AddOutput(new TxOut(new Money(20 * Money.COIN), new Script(OpcodeType.OP_11, OpcodeType.OP_EQUAL)));
pool.AddUnchecked(tx6.GetHash(), entry.Fee(new Money(0L)).FromTx(tx6));

// assert size
Assert.Equal(pool.Size, 6);

// Check that at this point, tx6 is sorted low
sortedOrder.Insert(0, tx6.GetHash().ToString());
CheckSort(pool, pool.MapTx.DescendantScore.ToList(), sortedOrder);


TxMemPool.SetEntries setAncestors = new TxMemPool.SetEntries();
setAncestors.Add(pool.MapTx.TryGet(tx6.GetHash()));
Transaction tx7 = new Transaction();
tx7.AddInput(new TxIn(new OutPoint(tx6.GetHash(), 0), new Script(OpcodeType.OP_11)));
tx7.AddOutput(new TxOut(new Money(10 * Money.COIN), new Script(OpcodeType.OP_11, OpcodeType.OP_EQUAL)));
tx7.AddOutput(new TxOut(new Money(1 * Money.COIN), new Script(OpcodeType.OP_11, OpcodeType.OP_EQUAL)));

TxMemPool.SetEntries setAncestorsCalculated = new TxMemPool.SetEntries();
string dummy;
Assert.Equal(pool.CalculateMemPoolAncestors(entry.Fee(2000000L).FromTx(tx7), setAncestorsCalculated, 100, 1000000, 1000, 1000000, out dummy), true);
Assert.True(setAncestorsCalculated.Equals(setAncestors));

pool.AddUnchecked(tx7.GetHash(), entry.FromTx(tx7), setAncestors);
Assert.Equal(pool.Size, 7);

// Now tx6 should be sorted higher (high fee child): tx7, tx6, tx2, ...
sortedOrder.RemoveAt(0);
sortedOrder.Add(tx6.GetHash().ToString());
sortedOrder.Add(tx7.GetHash().ToString());
CheckSort(pool, pool.MapTx.DescendantScore.ToList(), sortedOrder);

/* low fee child of tx7 */
Transaction tx8 = new Transaction();
tx8.AddInput(new TxIn(new OutPoint(tx7.GetHash(), 0), new Script(OpcodeType.OP_11)));
tx8.AddOutput(new TxOut(new Money(10 * Money.COIN), new Script(OpcodeType.OP_11, OpcodeType.OP_EQUAL)));
setAncestors.Add(pool.MapTx.TryGet(tx7.GetHash()));
pool.AddUnchecked(tx8.GetHash(), entry.Fee(0L).Time(2).FromTx(tx8), setAncestors);

// Now tx8 should be sorted low, but tx6/tx both high
sortedOrder.Insert(0, tx8.GetHash().ToString());
CheckSort(pool, pool.MapTx.DescendantScore.ToList(), sortedOrder);

/* low fee child of tx7 */
Transaction tx9 = new Transaction();
tx9.AddInput(new TxIn(new OutPoint(tx7.GetHash(), 1), new Script(OpcodeType.OP_11)));
tx9.AddOutput(new TxOut(new Money(1 * Money.COIN), new Script(OpcodeType.OP_11, OpcodeType.OP_EQUAL)));
pool.AddUnchecked(tx9.GetHash(), entry.Fee(0L).Time(3).FromTx(tx9), setAncestors);

// tx9 should be sorted low
Assert.Equal(pool.Size, 9);

sortedOrder.Insert(0, tx9.GetHash().ToString());
CheckSort(pool, pool.MapTx.DescendantScore.ToList(), sortedOrder);

List<string> snapshotOrder = sortedOrder.ToList();

setAncestors.Add(pool.MapTx.TryGet(tx8.GetHash()));
setAncestors.Add(pool.MapTx.TryGet(tx9.GetHash()));
/* tx10 depends on tx8 and tx9 and has a high fee*/
Transaction tx10 = new Transaction();
tx10.AddInput(new TxIn(new OutPoint(tx8.GetHash(), 0), new Script(OpcodeType.OP_11)));
tx10.AddInput(new TxIn(new OutPoint(tx9.GetHash(), 0), new Script(OpcodeType.OP_11)));
tx10.AddOutput(new TxOut(new Money(10 * Money.COIN), new Script(OpcodeType.OP_11, OpcodeType.OP_EQUAL)));

setAncestorsCalculated.Clear();
Assert.Equal(pool.CalculateMemPoolAncestors(entry.Fee(200000L).Time(4).FromTx(tx10), setAncestorsCalculated, 100, 1000000, 1000, 1000000, out dummy), true);
Assert.True(setAncestorsCalculated.Equals(setAncestors));

pool.AddUnchecked(tx10.GetHash(), entry.FromTx(tx10), setAncestors);

/**
* tx8 and tx9 should both now be sorted higher
* Final order after tx10 is added:
*
* tx3 = 0 (1)
* tx5 = 10000 (1)
* tx1 = 10000 (1)
* tx4 = 15000 (1)
* tx2 = 20000 (1)
* tx9 = 200k (2 txs)
* tx8 = 200k (2 txs)
* tx10 = 200k (1 tx)
* tx6 = 2.2M (5 txs)
* tx7 = 2.2M (4 txs)
*/
sortedOrder.RemoveRange(0, 2); // take out tx9, tx8 from the beginning
sortedOrder.Insert( 5, tx9.GetHash().ToString());
sortedOrder.Insert( 6, tx8.GetHash().ToString());
sortedOrder.Insert( 7, tx10.GetHash().ToString()); // tx10 is just before tx6
CheckSort(pool, pool.MapTx.DescendantScore.ToList(), sortedOrder);

// there should be 10 transactions in the mempool
Assert.Equal(pool.Size, 10);

// Now try removing tx10 and verify the sort order returns to normal
pool.RemoveRecursive(pool.MapTx.TryGet(tx10.GetHash()).Transaction);
CheckSort(pool, pool.MapTx.DescendantScore.ToList(), snapshotOrder);

//pool.removeRecursive(pool.MapTx.find(tx9.GetHash())->GetTx());
//pool.removeRecursive(pool.MapTx.find(tx8.GetHash())->GetTx());
///* Now check the sort on the mining score index.
// * Final order should be:
// *
// * tx7 (2M)
// * tx2 (20k)
// * tx4 (15000)
// * tx1/tx5 (10000)
// * tx3/6 (0)
// * (Ties resolved by hash)
// */
//sortedOrder.clear();
//sortedOrder.push_back(tx7.GetHash().ToString());
//sortedOrder.push_back(tx2.GetHash().ToString());
//sortedOrder.push_back(tx4.GetHash().ToString());
//if (tx1.GetHash() < tx5.GetHash())
//{
// sortedOrder.push_back(tx5.GetHash().ToString());
// sortedOrder.push_back(tx1.GetHash().ToString());
//}
//else
//{
// sortedOrder.push_back(tx1.GetHash().ToString());
// sortedOrder.push_back(tx5.GetHash().ToString());
//}
//if (tx3.GetHash() < tx6.GetHash())
//{
// sortedOrder.push_back(tx6.GetHash().ToString());
// sortedOrder.push_back(tx3.GetHash().ToString());
//}
//else
//{
// sortedOrder.push_back(tx3.GetHash().ToString());
// sortedOrder.push_back(tx6.GetHash().ToString());
//}
//CheckSort<mining_score>(pool, sortedOrder);
}
}
}
52 changes: 34 additions & 18 deletions Stratis.Bitcoin/MemoryPool/TxMemPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,11 @@ public int Compare(TxMemPoolEntry a, TxMemPoolEntry b)
if (f1 == f2)
{
if (a.Time >= b.Time)
return 1;
return -1;
return -1;
return 1;
}

if (f1 < f2)
if (f1 <= f2)
return -1;
return 1;
}
Expand Down Expand Up @@ -275,11 +275,27 @@ public class TxLinks
public SetEntries Children;
};

public class SetEntries : SortedSet<TxMemPoolEntry>
public class SetEntries : SortedSet<TxMemPoolEntry>, IEquatable<SetEntries>, IEqualityComparer<TxMemPoolEntry>

{
public SetEntries() : base(new CompareIteratorByHash())
{
}

public bool Equals(SetEntries other)
{
return this.SequenceEqual(other, this);
}

public bool Equals(TxMemPoolEntry x, TxMemPoolEntry y)
{
return x.TransactionHash == y.TransactionHash;
}

public int GetHashCode(TxMemPoolEntry obj)
{
return obj?.TransactionHash?.GetHashCode() ?? 0;
}
}

public class TxlinksMap : SortedList<TxMemPoolEntry, TxLinks>
Expand All @@ -301,7 +317,7 @@ public class NextTxPair
public Transaction Transaction;
}

IndexedTransactionSet mapTx = new IndexedTransactionSet();
public IndexedTransactionSet MapTx = new IndexedTransactionSet();
TxlinksMap mapLinks = new TxlinksMap();
List<NextTxPair> mapNextTx = new List<NextTxPair>();
private Dictionary<uint256, DeltaPair> mapDeltas = new Dictionary<uint256, DeltaPair>();
Expand Down Expand Up @@ -329,7 +345,7 @@ public TxMemPool(FeeRate minReasonableRelayFee)
private void InnerClear()
{
mapLinks.Clear();
mapTx.Clear();
MapTx.Clear();
mapNextTx.Clear();
totalTxSize = 0;
cachedInnerUsage = 0;
Expand Down Expand Up @@ -385,13 +401,13 @@ public bool AddUnchecked(uint256 hash, TxMemPoolEntry entry, bool validFeeEstima

}

bool AddUnchecked(uint256 hash, TxMemPoolEntry entry, SetEntries setAncestors, bool validFeeEstimate)
public bool AddUnchecked(uint256 hash, TxMemPoolEntry entry, SetEntries setAncestors, bool validFeeEstimate = true)
{
// Add to memory pool without checking anything.
// Used by main.cpp AcceptToMemoryPool(), which DOES do
// all the appropriate checks.
//LOCK(cs);
mapTx.Add(entry);
MapTx.Add(entry);
mapLinks.Add(entry, new TxLinks {Parents = new SetEntries(), Children = new SetEntries()});

// Update transaction for any feeDelta created by PrioritiseTransaction
Expand Down Expand Up @@ -429,7 +445,7 @@ bool AddUnchecked(uint256 hash, TxMemPoolEntry entry, SetEntries setAncestors, b
// Update ancestors with information about this tx
foreach (var phash in setParentTransactions)
{
var pit = mapTx.TryGet(phash);
var pit = MapTx.TryGet(phash);
if (pit != null)
UpdateParent(entry, pit, true);
}
Expand Down Expand Up @@ -489,7 +505,7 @@ private SetEntries GetMemPoolParents(TxMemPoolEntry entry)
if(entry == null)
throw new ArgumentNullException(nameof(entry));

Check.Assert(mapTx.ContainsKey(entry.TransactionHash));
Check.Assert(MapTx.ContainsKey(entry.TransactionHash));
var it = mapLinks.TryGet(entry);
Check.Assert(it != null);
return it.Parents;
Expand All @@ -500,7 +516,7 @@ private SetEntries GetMemPoolChildren(TxMemPoolEntry entry)
if (entry == null)
throw new ArgumentNullException(nameof(entry));

Check.Assert(mapTx.ContainsKey(entry.TransactionHash));
Check.Assert(MapTx.ContainsKey(entry.TransactionHash));
var it = mapLinks.TryGet(entry);
Check.Assert(it != null);
return it.Children;
Expand Down Expand Up @@ -544,7 +560,7 @@ private void UpdateParent(TxMemPoolEntry entry, TxMemPoolEntry parent, bool add)
* fSearchForParents = whether to search a tx's vin for in-mempool parents, or
* look up parents from mapLinks. Must be true for entries not in the mempool
*/
private bool CalculateMemPoolAncestors(TxMemPoolEntry entry, SetEntries setAncestors, long limitAncestorCount,
public bool CalculateMemPoolAncestors(TxMemPoolEntry entry, SetEntries setAncestors, long limitAncestorCount,
long limitAncestorSize, long limitDescendantCount, long limitDescendantSize, out string errString,
bool fSearchForParents = true)
{
Expand All @@ -559,7 +575,7 @@ private bool CalculateMemPoolAncestors(TxMemPoolEntry entry, SetEntries setAnces
// iterate mapTx to find parents.
foreach (var txInput in tx.Inputs)
{
var piter = mapTx.TryGet(txInput.PrevOut.Hash);
var piter = MapTx.TryGet(txInput.PrevOut.Hash);
if (piter != null)
{
parentHashes.Add(piter);
Expand Down Expand Up @@ -641,13 +657,13 @@ public bool HasNoInputsOf(Transaction tx)
public bool Exists(uint256 hash)
{
//LOCK(cs);
return mapTx.ContainsKey(hash);
return MapTx.ContainsKey(hash);
}

public long Size
{
//LOCK(cs);
get { return this.mapTx.Count; }
get { return this.MapTx.Count; }
}

public void RemoveRecursive(Transaction origTx)
Expand All @@ -658,7 +674,7 @@ public void RemoveRecursive(Transaction origTx)

//LOCK(cs);
SetEntries txToRemove = new SetEntries();
var origit = mapTx.TryGet(origHahs);
var origit = MapTx.TryGet(origHahs);
if (origit != null)
{
txToRemove.Add(origit);
Expand All @@ -674,7 +690,7 @@ public void RemoveRecursive(Transaction origTx)
var it = mapNextTx.FirstOrDefault(w => w.OutPoint == new OutPoint(origHahs, i));
if (it == null)
continue;
var nextit = mapTx.TryGet(it.Transaction.GetHash());
var nextit = MapTx.TryGet(it.Transaction.GetHash());
Check.Assert(nextit != null);
txToRemove.Add(nextit);
}
Expand Down Expand Up @@ -742,7 +758,7 @@ private void RemoveUnchecked(TxMemPoolEntry it)
cachedInnerUsage -= 1; //it->DynamicMemoryUsage();
cachedInnerUsage -= mapLinks[it]?.Parents.Count ?? 0 + mapLinks[it]?.Children.Count ?? 0;
mapLinks.Remove(it);
mapTx.Remove(it);
MapTx.Remove(it);
nTransactionsUpdated++;
//minerPolicyEstimator->removeTx(hash);
}
Expand Down
Loading