From aea69c8fb06cb41c3c027475b639ff7acafe10d2 Mon Sep 17 00:00:00 2001 From: Mike Dennis Date: Sat, 19 Aug 2017 21:46:53 -0600 Subject: [PATCH 1/4] XML doc for memory pool classes. --- Stratis.Bitcoin.IntegrationTests/FeeTests.cs | 2 +- .../MemoryPool/MempoolPersistenceTest.cs | 2 +- .../MemoryPool/Fee/BlockPolicyEstimator.cs | 284 ++-- .../Features/MemoryPool/Fee/TxConfirmStats.cs | 212 +-- .../Features/MemoryPool/MempoolPersistence.cs | 118 +- .../MemoryPool/MempoolValidationContext.cs | 118 +- .../Features/MemoryPool/TxMemPool.cs | 1209 +++++++++++------ .../Features/MemoryPool/TxMemPoolEntry.cs | 221 ++- 8 files changed, 1452 insertions(+), 714 deletions(-) diff --git a/Stratis.Bitcoin.IntegrationTests/FeeTests.cs b/Stratis.Bitcoin.IntegrationTests/FeeTests.cs index 652785f7ca7..016b3d3bc33 100644 --- a/Stratis.Bitcoin.IntegrationTests/FeeTests.cs +++ b/Stratis.Bitcoin.IntegrationTests/FeeTests.cs @@ -224,7 +224,7 @@ public void BlockPolicyEstimates() { Assert.True(mpool.EstimateSmartFee(i, out answerFound).FeePerK >= mpool.EstimateFee(i).FeePerK); Assert.True(mpool.EstimateSmartFee(i, out answerFound).FeePerK >= mpool.GetMinFee(1).FeePerK); - Assert.True(mpool.EstimateSmartPriority(i, out answerFound) == BlockPolicyEstimator.INF_PRIORITY); + Assert.True(mpool.EstimateSmartPriority(i, out answerFound) == BlockPolicyEstimator.InfPriority); } } } diff --git a/Stratis.Bitcoin.Tests/MemoryPool/MempoolPersistenceTest.cs b/Stratis.Bitcoin.Tests/MemoryPool/MempoolPersistenceTest.cs index 6e6b3c4b9e6..55352d63c34 100644 --- a/Stratis.Bitcoin.Tests/MemoryPool/MempoolPersistenceTest.cs +++ b/Stratis.Bitcoin.Tests/MemoryPool/MempoolPersistenceTest.cs @@ -179,7 +179,7 @@ public void SaveStreamTest() } Assert.True(actualStreamLength > 0); - Assert.Equal(MempoolPersistence.MEMPOOL_DUMP_VERSION, actualVersion); + Assert.Equal(MempoolPersistence.MempoolDumpVersion, actualVersion); Assert.Equal(numTx, actualCount); Assert.Equal(loaded, toSave.ToArray()); } diff --git a/Stratis.Bitcoin/Features/MemoryPool/Fee/BlockPolicyEstimator.cs b/Stratis.Bitcoin/Features/MemoryPool/Fee/BlockPolicyEstimator.cs index 009bf90b5e9..30283a392ba 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/Fee/BlockPolicyEstimator.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/Fee/BlockPolicyEstimator.cs @@ -1,107 +1,132 @@ -using System.Collections.Generic; -using System.IO; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using NBitcoin; using Stratis.Bitcoin.Configuration; +using System.Collections.Generic; +using System.IO; namespace Stratis.Bitcoin.Features.MemoryPool.Fee { - // CBlockPolicyEstimator - - // The BlockPolicyEstimator is used for estimating the feerate needed - // for a transaction to be included in a block within a certain number of - // blocks. - // - // At a high level the algorithm works by grouping transactions into buckets - // based on having similar feerates and then tracking how long it - // takes transactions in the various buckets to be mined. It operates under - // the assumption that in general transactions of higher feerate will be - // included in blocks before transactions of lower feerate. So for - // example if you wanted to know what feerate you should put on a transaction to - // be included in a block within the next 5 blocks, you would start by looking - // at the bucket with the highest feerate transactions and verifying that a - // sufficiently high percentage of them were confirmed within 5 blocks and - // then you would look at the next highest feerate bucket, and so on, stopping at - // the last bucket to pass the test. The average feerate of transactions in this - // bucket will give you an indication of the lowest feerate you can put on a - // transaction and still have a sufficiently high chance of being confirmed - // within your desired 5 blocks. - // - // Here is a brief description of the implementation: - // When a transaction enters the mempool, we - // track the height of the block chain at entry. Whenever a block comes in, - // we count the number of transactions in each bucket and the total amount of feerate - // paid in each bucket. Then we calculate how many blocks Y it took each - // transaction to be mined and we track an array of counters in each bucket - // for how long it to took transactions to get confirmed from 1 to a max of 25 - // and we increment all the counters from Y up to 25. This is because for any - // number Z>=Y the transaction was successfully mined within Z blocks. We - // want to save a history of this information, so at any time we have a - // counter of the total number of transactions that happened in a given feerate - // bucket and the total number that were confirmed in each number 1-25 blocks - // or less for any bucket. We save this history by keeping an exponentially - // decaying moving average of each one of these stats. Furthermore we also - // keep track of the number unmined (in mempool) transactions in each bucket - // and for how many blocks they have been outstanding and use that to increase - // the number of transactions we've seen in that feerate bucket when calculating - // an estimate for any number of confirmations below the number of blocks - // they've been outstanding. - - // We will instantiate an instance of this class to track transactions that were - // included in a block. We will lump transactions into a bucket according to their - // approximate feerate and then track how long it took for those txs to be included in a block - // - // The tracking of unconfirmed (mempool) transactions is completely independent of the - // historical tracking of transactions that have been confirmed in a block. - - // We want to be able to estimate feerates that are needed on tx's to be included in - // a certain number of blocks.Every time a block is added to the best chain, this class records - // stats on the transactions included in that block + /// + /// The BlockPolicyEstimator is used for estimating the feerate needed + /// for a transaction to be included in a block within a certain number of + /// blocks. + /// + /// + /// At a high level the algorithm works by grouping transactions into buckets + /// based on having similar feerates and then tracking how long it + /// takes transactions in the various buckets to be mined. It operates under + /// the assumption that in general transactions of higher feerate will be + /// included in blocks before transactions of lower feerate. So for + /// example if you wanted to know what feerate you should put on a transaction to + /// be included in a block within the next 5 blocks, you would start by looking + /// at the bucket with the highest feerate transactions and verifying that a + /// sufficiently high percentage of them were confirmed within 5 blocks and + /// then you would look at the next highest feerate bucket, and so on, stopping at + /// the last bucket to pass the test. The average feerate of transactions in this + /// bucket will give you an indication of the lowest feerate you can put on a + /// transaction and still have a sufficiently high chance of being confirmed + /// within your desired 5 blocks. + /// + /// Here is a brief description of the implementation: + /// When a transaction enters the mempool, we + /// track the height of the block chain at entry. Whenever a block comes in, + /// we count the number of transactions in each bucket and the total amount of feerate + /// paid in each bucket. Then we calculate how many blocks Y it took each + /// transaction to be mined and we track an array of counters in each bucket + /// for how long it to took transactions to get confirmed from 1 to a max of 25 + /// and we increment all the counters from Y up to 25. This is because for any + /// number Z>=Y the transaction was successfully mined within Z blocks. We + /// want to save a history of this information, so at any time we have a + /// counter of the total number of transactions that happened in a given feerate + /// bucket and the total number that were confirmed in each number 1-25 blocks + /// or less for any bucket. We save this history by keeping an exponentially + /// decaying moving average of each one of these stats. Furthermore we also + /// keep track of the number unmined (in mempool) transactions in each bucket + /// and for how many blocks they have been outstanding and use that to increase + /// the number of transactions we've seen in that feerate bucket when calculating + /// an estimate for any number of confirmations below the number of blocks + /// they've been outstanding. + /// + /// We will instantiate an instance of this class to track transactions that were + /// included in a block. We will lump transactions into a bucket according to their + /// approximate feerate and then track how long it took for those txs to be included in a block + /// + /// The tracking of unconfirmed (mempool) transactions is completely independent of the + /// historical tracking of transactions that have been confirmed in a block. + /// + /// We want to be able to estimate feerates that are needed on tx's to be included in + /// a certain number of blocks.Every time a block is added to the best chain, this class records + /// stats on the transactions included in that block + /// public class BlockPolicyEstimator { - // Require an avg of 1 tx in the combined feerate bucket per block to have stat significance - private const double SUFFICIENT_FEETXS = 1; + /// Require an avg of 1 tx in the combined feerate bucket per block to have stat significance. + private const double SufficientFeeTxs = 1; - // Require greater than 95% of X feerate transactions to be confirmed within Y blocks for X to be big enough - private const double MIN_SUCCESS_PCT = .95; + /// Require greater than 95% of X feerate transactions to be confirmed within Y blocks for X to be big enough. + private const double MinSuccessPct = .95; - // Minimum and Maximum values for tracking feerates - private const long MIN_FEERATE = 10; + /// Minimum value for tracking feerates. + private const long MinFeeRate = 10; - private const double MAX_FEERATE = 1e7; + /// Maximum value for tracking feerates. + private const double MaxFeeRate = 1e7; + + /// + /// Spacing of FeeRate buckets. + /// + /// + /// We have to lump transactions into buckets based on feerate, but we want to be able + /// to give accurate estimates over a large range of potential feerates. + /// Therefore it makes sense to exponentially space the buckets. + /// + private const double FeeSpacing = 1.1; - // We have to lump transactions into buckets based on feerate, but we want to be able - // to give accurate estimates over a large range of potential feerates - // Therefore it makes sense to exponentially space the buckets - /** Spacing of FeeRate buckets */ - private const double FEE_SPACING = 1.1; + /// Track confirm delays up to 25 blocks, can't estimate beyond that. + private const int MaxBlockConfirms = 25; - /** Track confirm delays up to 25 blocks, can't estimate beyond that */ - private const int MAX_BLOCK_CONFIRMS = 25; + /// Decay of .998 is a half-life of 346 blocks or about 2.4 days. + private const double DefaultDecay = .998; - /** Decay of .998 is a half-life of 346 blocks or about 2.4 days */ - private const double DEFAULT_DECAY = .998; + /// Value for infinite priority. + public const double InfPriority = 1e9 * 21000000ul * Money.COIN; - public const double INF_PRIORITY = 1e9 * 21000000ul * Money.COIN; + /// Maximum money value. + private static readonly Money MaxMoney = new Money(21000000 * Money.COIN); - private static readonly Money MAX_MONEY = new Money(21000000 * Money.COIN); - private static readonly double INF_FEERATE = MAX_MONEY.Satoshi; + /// Value for infinite fee rate. + private static readonly double InfFeeRate = MaxMoney.Satoshi; - // Classes to track historical data on transaction confirmations + /// Classes to track historical data on transaction confirmations. private readonly TxConfirmStats feeStats; - // map of txids to information about that transaction + /// Map of txids to information about that transaction. private readonly Dictionary mapMemPoolTxs; - private readonly FeeRate minTrackedFee; //!< Passed to constructor to avoid dependency on main + /// Minimum tracked Fee. Passed to constructor to avoid dependency on main./// + private readonly FeeRate minTrackedFee; + + /// Best seen block height. private int nBestSeenHeight; + /// Setting for the node. private readonly NodeSettings nodeArgs; + + /// Logger for logging on this object. private readonly ILogger logger; + /// Count of tracked transactions. private int trackedTxs; + + /// Count of untracked transactions. private int untrackedTxs; + /// + /// Constructs an instance of the block policy estimator object. + /// + /// Minimum relay fee rate. + /// Node settings. + /// Factory for creating loggers. public BlockPolicyEstimator(FeeRate minRelayFee, NodeSettings nodeArgs, ILoggerFactory loggerFactory) { this.mapMemPoolTxs = new Dictionary(); @@ -111,20 +136,24 @@ public BlockPolicyEstimator(FeeRate minRelayFee, NodeSettings nodeArgs, ILoggerF this.untrackedTxs = 0; this.logger = loggerFactory.CreateLogger(this.GetType().FullName); - this.minTrackedFee = minRelayFee < new FeeRate(new Money(MIN_FEERATE)) - ? new FeeRate(new Money(MIN_FEERATE)) + this.minTrackedFee = minRelayFee < new FeeRate(new Money(MinFeeRate)) + ? new FeeRate(new Money(MinFeeRate)) : minRelayFee; var vfeelist = new List(); for (double bucketBoundary = this.minTrackedFee.FeePerK.Satoshi; - bucketBoundary <= MAX_FEERATE; - bucketBoundary *= FEE_SPACING) + bucketBoundary <= MaxFeeRate; + bucketBoundary *= FeeSpacing) vfeelist.Add(bucketBoundary); - vfeelist.Add(INF_FEERATE); + vfeelist.Add(InfFeeRate); this.feeStats = new TxConfirmStats(this.logger); - this.feeStats.Initialize(vfeelist, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY); + this.feeStats.Initialize(vfeelist, MaxBlockConfirms, DefaultDecay); } - // Process all the transactions that have been included in a block + /// + /// Process all the transactions that have been included in a block. + /// + /// The block height for the block. + /// Collection of memory pool entries. public void ProcessBlock(int nBlockHeight, List entries) { if (nBlockHeight <= this.nBestSeenHeight) @@ -138,9 +167,9 @@ public void ProcessBlock(int nBlockHeight, List entries) // Clear the current block state and update unconfirmed circular buffer this.feeStats.ClearCurrent(nBlockHeight); - var countedTxs = 0; + int countedTxs = 0; // Repopulate the current block states - for (var i = 0; i < entries.Count; i++) + for (int i = 0; i < entries.Count; i++) if (this.ProcessBlockTx(nBlockHeight, entries[i])) countedTxs++; @@ -155,7 +184,12 @@ public void ProcessBlock(int nBlockHeight, List entries) this.untrackedTxs = 0; } - // Process a transaction confirmed in a block + /// + /// Process a transaction confirmed in a block. + /// + /// Height of the block. + /// The memory pool entry. + /// Whether it was able to successfully process the transaction. private bool ProcessBlockTx(int nBlockHeight, TxMempoolEntry entry) { if (!this.RemoveTx(entry.TransactionHash)) @@ -164,7 +198,7 @@ private bool ProcessBlockTx(int nBlockHeight, TxMempoolEntry entry) // How many blocks did it take for miners to include this transaction? // blocksToConfirm is 1-based, so a transaction included in the earliest // possible block has confirmation count of 1 - var blocksToConfirm = nBlockHeight - entry.EntryHeight; + int blocksToConfirm = nBlockHeight - entry.EntryHeight; if (blocksToConfirm <= 0) { // This can't happen because we don't process transactions from a block with a height @@ -174,17 +208,21 @@ private bool ProcessBlockTx(int nBlockHeight, TxMempoolEntry entry) } // Feerates are stored and reported as BTC-per-kb: - var feeRate = new FeeRate(entry.Fee, (int) entry.GetTxSize()); + FeeRate feeRate = new FeeRate(entry.Fee, (int) entry.GetTxSize()); this.feeStats.Record(blocksToConfirm, feeRate.FeePerK.Satoshi); return true; } - // Process a transaction accepted to the mempool + /// + /// Process a transaction accepted to the mempool. + /// + /// Memory pool entry. + /// Whether to update fee estimate. public void ProcessTransaction(TxMempoolEntry entry, bool validFeeEstimate) { - var txHeight = entry.EntryHeight; - var hash = entry.TransactionHash; + int txHeight = entry.EntryHeight; + uint256 hash = entry.TransactionHash; if (this.mapMemPoolTxs.ContainsKey(hash)) { this.logger.LogInformation($"Blockpolicy error mempool tx {hash} already being tracked"); @@ -204,22 +242,28 @@ public void ProcessTransaction(TxMempoolEntry entry, bool validFeeEstimate) this.trackedTxs++; // Feerates are stored and reported as BTC-per-kb: - var feeRate = new FeeRate(entry.Fee, (int) entry.GetTxSize()); + FeeRate feeRate = new FeeRate(entry.Fee, (int) entry.GetTxSize()); this.mapMemPoolTxs.Add(hash, new TxStatsInfo()); this.mapMemPoolTxs[hash].blockHeight = txHeight; this.mapMemPoolTxs[hash].bucketIndex = this.feeStats.NewTx(txHeight, feeRate.FeePerK.Satoshi); } - // Remove a transaction from the mempool tracking stats - // This function is called from CTxMemPool::removeUnchecked to ensure - // txs removed from the mempool for any reason are no longer - // tracked. Txs that were part of a block have already been removed in - // processBlockTx to ensure they are never double tracked, but it is - // of no harm to try to remove them again. + /// + /// Remove a transaction from the mempool tracking stats. + /// + /// Transaction hash. + /// Whether successfully removed transaction. + /// + /// This function is called from TxMemPool.RemoveUnchecked to ensure + /// txs removed from the mempool for any reason are no longer + /// tracked. Txs that were part of a block have already been removed in + /// ProcessBlockTx to ensure they are never double tracked, but it is + /// of no harm to try to remove them again. + /// public bool RemoveTx(uint256 hash) { - var pos = this.mapMemPoolTxs.TryGet(hash); + TxStatsInfo pos = this.mapMemPoolTxs.TryGet(hash); if (pos != null) { this.feeStats.RemoveTx(pos.blockHeight, this.nBestSeenHeight, pos.bucketIndex); @@ -240,7 +284,7 @@ public FeeRate EstimateFee(int confTarget) if (confTarget <= 1 || confTarget > this.feeStats.GetMaxConfirms()) return new FeeRate(0); - var median = this.feeStats.EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, + double median = this.feeStats.EstimateMedianVal(confTarget, SufficientFeeTxs, MinSuccessPct, true, this.nBestSeenHeight); if (median < 0) @@ -268,7 +312,7 @@ public FeeRate EstimateSmartFee(int confTarget, TxMempool pool, out int answerFo double median = -1; while (median < 0 && confTarget <= this.feeStats.GetMaxConfirms()) - median = this.feeStats.EstimateMedianVal(confTarget++, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, + median = this.feeStats.EstimateMedianVal(confTarget++, SufficientFeeTxs, MinSuccessPct, true, this.nBestSeenHeight); answerFoundAtTarget = confTarget - 1; @@ -276,7 +320,7 @@ public FeeRate EstimateSmartFee(int confTarget, TxMempool pool, out int answerFo // If mempool is limiting txs , return at least the min feerate from the mempool if (pool != null) { - var minPoolFee = pool.GetMinFee(this.nodeArgs.Mempool.MaxMempool * 1000000).FeePerK; + Money minPoolFee = pool.GetMinFee(this.nodeArgs.Mempool.MaxMempool * 1000000).FeePerK; if (minPoolFee > 0 && minPoolFee.Satoshi > median) return new FeeRate(minPoolFee); } @@ -287,39 +331,67 @@ public FeeRate EstimateSmartFee(int confTarget, TxMempool pool, out int answerFo return new FeeRate((int) median); } - // Write estimation data to a file + /// + /// Write estimation data to a file. + /// + /// Stream to write to. public void Write(Stream fileout) { } - // Read estimation data from a file + /// + /// Read estimation data from a file. + /// + /// Stream to read data from. + /// Version number of the file. public void Read(Stream filein, int nFileVersion) { } + /// + /// Return an estimate of the priority. + /// + /// The desired number of confirmations to be included in a block. + /// Estiamte of the priority. public double EstimatePriority(int confTarget) { return -1; } + /// + /// Return an estimated smart priority. + /// + /// The desired number of confirmations to be included in a block. + /// Memory pool transactions. + /// Block height where answer was found. + /// The smart priority. public double EstimateSmartPriority(int confTarget, TxMempool pool, out int answerFoundAtTarget) { answerFoundAtTarget = confTarget; // If mempool is limiting txs, no priority txs are allowed - var minPoolFee = pool.GetMinFee(this.nodeArgs.Mempool.MaxMempool * 1000000).FeePerK; + Money minPoolFee = pool.GetMinFee(this.nodeArgs.Mempool.MaxMempool * 1000000).FeePerK; if (minPoolFee > 0) - return INF_PRIORITY; + return InfPriority; return -1; } + /// + /// Transaction statistics information. + /// public class TxStatsInfo { + /// The block height. public int blockHeight; + + /// The index into the confirmed transactions bucket map. public int bucketIndex; + /// + /// Constructs a instance of a transaction stats info object. + /// public TxStatsInfo() { this.blockHeight = 0; diff --git a/Stratis.Bitcoin/Features/MemoryPool/Fee/TxConfirmStats.cs b/Stratis.Bitcoin/Features/MemoryPool/Fee/TxConfirmStats.cs index 42473428e33..cc1b7b99f26 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/Fee/TxConfirmStats.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/Fee/TxConfirmStats.cs @@ -1,73 +1,102 @@ +using Microsoft.Extensions.Logging; +using NBitcoin; using System.Collections.Generic; using System.IO; using System.Linq; -using Microsoft.Extensions.Logging; -using NBitcoin; namespace Stratis.Bitcoin.Features.MemoryPool.Fee { + /// + /// Transation confirmation statistics. + /// public class TxConfirmStats { + /// Logger for logging messages for this object. private readonly ILogger logger; - // Sum the total feerate of all tx's in each bucket - // Track the historical moving average of this total over blocks + /// + /// Sum the total feerate of all tx's in each bucket. + /// + /// + /// Track the historical moving average of this total over blocks. + /// private List avg; - private Dictionary bucketMap; // Map of bucket upper-bound to index into all vectors by bucket + /// Map of bucket upper-bound to index into all vectors by bucket. + private Dictionary bucketMap; + + //Define the buckets we will group transactions into. - //Define the buckets we will group transactions into - private List buckets; // The upper-bound of the range for the bucket (inclusive) + /// The upper-bound of the range for the bucket (inclusive). + private List buckets; - // Count the total # of txs confirmed within Y blocks in each bucket - // Track the historical moving average of theses totals over blocks - private List> confAvg; // confAvg[Y][X] + // Count the total # of txs confirmed within Y blocks in each bucket. + // Track the historical moving average of theses totals over blocks. - // and calculate the totals for the current block to update the moving averages - private List> curBlockConf; // curBlockConf[Y][X] + /// Confirmation average. confAvg[Y][X]. + private List> confAvg; - // and calculate the total for the current block to update the moving average + /// Current block confirmations. curBlockConf[Y][X]. + private List> curBlockConf; + + /// Current block transaction count. private List curBlockTxCt; - // and calculate the total for the current block to update the moving average + /// Current block fee rate. private List curBlockVal; // Combine the conf counts with tx counts to calculate the confirmation % for each Y,X // Combine the total value with the tx counts to calculate the avg feerate per bucket + /// Decay value to use. private double decay; - // transactions still unconfirmed after MAX_CONFIRMS for each bucket + /// Transactions still unconfirmed after MAX_CONFIRMS for each bucket private List oldUnconfTxs; - // For each bucket X: - // Count the total # of txs in each bucket - // Track the historical moving average of this total over blocks + /// + /// Historical moving average of transaction counts. + /// + /// + /// For each bucket X: + /// Count the total # of txs in each bucket. + /// Track the historical moving average of this total over blocks + /// private List txCtAvg; - // Mempool counts of outstanding transactions - // For each bucket X, track the number of transactions in the mempool - // that are unconfirmed for each possible confirmation value Y - private List> unconfTxs; //unconfTxs[Y][X] - + /// + /// Mempool counts of outstanding transactions. + /// + /// + /// For each bucket X, track the number of transactions in the mempool + /// that are unconfirmed for each possible confirmation value Y + /// unconfTxs[Y][X] + /// + private List> unconfTxs; + + /// + /// Constructs an instance of the transaction confirmation stats object. + /// + /// Logger to use for message logging. public TxConfirmStats(ILogger logger) { this.logger = logger; } - // Initialize the data structures. This is called by BlockPolicyEstimator's - // constructor with default values. - // @param defaultBuckets contains the upper limits for the bucket boundaries - // @param maxConfirms max number of confirms to track - // @param decay how much to decay the historical moving average per block - + /// + /// Initialize the data structures. This is called by BlockPolicyEstimator's + /// constructor with default values. + /// + /// Contains the upper limits for the bucket boundaries. + /// Max number of confirms to track. + /// How much to decay the historical moving average per block. public void Initialize(List defaultBuckets, int maxConfirms, double decay) { this.buckets = new List(); this.bucketMap = new Dictionary(); this.decay = decay; - for (var i = 0; i < defaultBuckets.Count; i++) + for (int i = 0; i < defaultBuckets.Count; i++) { this.buckets.Add(defaultBuckets[i]); this.bucketMap[defaultBuckets[i]] = i; @@ -76,7 +105,7 @@ public void Initialize(List defaultBuckets, int maxConfirms, double deca this.curBlockConf = new List>(); this.unconfTxs = new List>(); - for (var i = 0; i < maxConfirms; i++) + for (int i = 0; i < maxConfirms; i++) { this.confAvg.Insert(i, Enumerable.Repeat(default(double), this.buckets.Count).ToList()); this.curBlockConf.Insert(i, Enumerable.Repeat(default(int), this.buckets.Count).ToList()); @@ -90,51 +119,64 @@ public void Initialize(List defaultBuckets, int maxConfirms, double deca this.avg = new List(Enumerable.Repeat(default(double), this.buckets.Count)); } - // Clear the state of the curBlock variables to start counting for the new block + /// + /// Clear the state of the curBlock variables to start counting for the new block. + /// + /// Block height. public void ClearCurrent(int nBlockHeight) { for (var j = 0; j < this.buckets.Count; j++) { this.oldUnconfTxs[j] += this.unconfTxs[nBlockHeight % this.unconfTxs.Count][j]; this.unconfTxs[nBlockHeight % this.unconfTxs.Count][j] = 0; - for (var i = 0; i < this.curBlockConf.Count; i++) + for (int i = 0; i < this.curBlockConf.Count; i++) this.curBlockConf[i][j] = 0; this.curBlockTxCt[j] = 0; this.curBlockVal[j] = 0; } } - - // Record a new transaction data point in the current block stats - // @param blocksToConfirm the number of blocks it took this transaction to confirm - // @param val the feerate of the transaction - // @warning blocksToConfirm is 1-based and has to be >= 1 + /// + /// Record a new transaction data point in the current block stats. + /// + /// The number of blocks it took this transaction to confirm. blocksToConfirm is 1-based and has to be >= 1. + /// The feerate of the transaction. public void Record(int blocksToConfirm, double val) { // blocksToConfirm is 1-based if (blocksToConfirm < 1) return; - var bucketindex = this.bucketMap.FirstOrDefault(k => k.Key > val).Value; - for (var i = blocksToConfirm; i <= this.curBlockConf.Count; i++) + int bucketindex = this.bucketMap.FirstOrDefault(k => k.Key > val).Value; + for (int i = blocksToConfirm; i <= this.curBlockConf.Count; i++) this.curBlockConf[i - 1][bucketindex]++; this.curBlockTxCt[bucketindex]++; this.curBlockVal[bucketindex] += val; } - // Record a new transaction entering the mempool + /// + /// Record a new transaction entering the mempool. + /// + /// The block height. + /// + /// The feerate of the transaction. public int NewTx(int nBlockHeight, double val) { - var bucketindex = this.bucketMap.FirstOrDefault(k => k.Key > val).Value; - var blockIndex = nBlockHeight % this.unconfTxs.Count; + int bucketindex = this.bucketMap.FirstOrDefault(k => k.Key > val).Value; + int blockIndex = nBlockHeight % this.unconfTxs.Count; this.unconfTxs[blockIndex][bucketindex]++; return bucketindex; } - // Remove a transaction from mempool tracking stats + /// + /// Remove a transaction from mempool tracking stats. + /// + /// The height of the mempool entry. + /// The best sceen height. + /// The bucket index. public void RemoveTx(int entryHeight, int nBestSeenHeight, int bucketIndex) { //nBestSeenHeight is not updated yet for the new block - var blocksAgo = nBestSeenHeight - entryHeight; + int blocksAgo = nBestSeenHeight - entryHeight; if (nBestSeenHeight == 0) // the BlockPolicyEstimator hasn't seen any blocks yet blocksAgo = 0; if (blocksAgo < 0) @@ -153,7 +195,7 @@ public void RemoveTx(int entryHeight, int nBestSeenHeight, int bucketIndex) } else { - var blockIndex = entryHeight % this.unconfTxs.Count; + int blockIndex = entryHeight % this.unconfTxs.Count; if (this.unconfTxs[blockIndex][bucketIndex] > 0) this.unconfTxs[blockIndex][bucketIndex]--; else @@ -162,9 +204,10 @@ public void RemoveTx(int entryHeight, int nBestSeenHeight, int bucketIndex) } } - // Update our estimates by decaying our historical moving average and updating - // with the data gathered from the current block - + /// + /// Update our estimates by decaying our historical moving average and updating + // with the data gathered from the current block. + /// public void UpdateMovingAverages() { for (var j = 0; j < this.buckets.Count; j++) @@ -176,15 +219,17 @@ public void UpdateMovingAverages() } } - // Calculate a feerate estimate. Find the lowest value bucket (or range of buckets - // to make sure we have enough data points) whose transactions still have sufficient likelihood - // of being confirmed within the target number of confirmations - // @param confTarget target number of confirmations - // @param sufficientTxVal required average number of transactions per block in a bucket range - // @param minSuccess the success probability we require - // @param requireGreater return the lowest feerate such that all higher values pass minSuccess OR - // return the highest feerate such that all lower values fail minSuccess - // @param nBlockHeight the current block height + /// + /// Calculate a feerate estimate. Find the lowest value bucket (or range of buckets + /// to make sure we have enough data points) whose transactions still have sufficient likelihood + /// of being confirmed within the target number of confirmations. + /// + /// Target number of confirmations. + /// Required average number of transactions per block in a bucket range. + /// The success probability we require. + /// Return the lowest feerate such that all higher values pass minSuccess OR return the highest feerate such that all lower values fail minSuccess. + /// The current block height. + /// public double EstimateMedianVal(int confTarget, double sufficientTxVal, double successBreakPoint, bool requireGreater, int nBlockHeight) @@ -192,37 +237,37 @@ public double EstimateMedianVal(int confTarget, double sufficientTxVal, double s // Counters for a bucket (or range of buckets) double nConf = 0; // Number of tx's confirmed within the confTarget double totalNum = 0; // Total number of tx's that were ever confirmed - var extraNum = 0; // Number of tx's still in mempool for confTarget or longer + int extraNum = 0; // Number of tx's still in mempool for confTarget or longer - var maxbucketindex = this.buckets.Count - 1; + int maxbucketindex = this.buckets.Count - 1; // requireGreater means we are looking for the lowest feerate such that all higher // values pass, so we start at maxbucketindex (highest feerate) and look at successively // smaller buckets until we reach failure. Otherwise, we are looking for the highest // feerate such that all lower values fail, and we go in the opposite direction. - var startbucket = requireGreater ? maxbucketindex : 0; - var step = requireGreater ? -1 : 1; + int startbucket = requireGreater ? maxbucketindex : 0; + int step = requireGreater ? -1 : 1; // We'll combine buckets until we have enough samples. // The near and far variables will define the range we've combined // The best variables are the last range we saw which still had a high // enough confirmation rate to count as success. // The cur variables are the current range we're counting. - var curNearBucket = startbucket; - var bestNearBucket = startbucket; - var curFarBucket = startbucket; - var bestFarBucket = startbucket; + int curNearBucket = startbucket; + int bestNearBucket = startbucket; + int curFarBucket = startbucket; + int bestFarBucket = startbucket; - var foundAnswer = false; - var bins = this.unconfTxs.Count; + bool foundAnswer = false; + int bins = this.unconfTxs.Count; // Start counting from highest(default) or lowest feerate transactions - for (var bucket = startbucket; bucket >= 0 && bucket <= maxbucketindex; bucket += step) + for (int bucket = startbucket; bucket >= 0 && bucket <= maxbucketindex; bucket += step) { curFarBucket = bucket; nConf += this.confAvg[confTarget - 1][bucket]; totalNum += this.txCtAvg[bucket]; - for (var confct = confTarget; confct < this.GetMaxConfirms(); confct++) + for (int confct = confTarget; confct < this.GetMaxConfirms(); confct++) extraNum += this.unconfTxs[(nBlockHeight - confct) % bins][bucket]; extraNum += this.oldUnconfTxs[bucket]; // If we have enough transaction data points in this range of buckets, @@ -231,7 +276,7 @@ public double EstimateMedianVal(int confTarget, double sufficientTxVal, double s // will be looking at the same amount of data and same bucket breaks) if (totalNum >= sufficientTxVal / (1 - this.decay)) { - var curPct = nConf / (totalNum + extraNum); + double curPct = nConf / (totalNum + extraNum); // Check to see if we are no longer getting confirmed at the success rate if (requireGreater && curPct < successBreakPoint) @@ -258,14 +303,14 @@ public double EstimateMedianVal(int confTarget, double sufficientTxVal, double s // Find the bucket with the median transaction and then report the average feerate from that bucket // This is a compromise between finding the median which we can't since we don't save all tx's // and reporting the average which is less accurate - var minBucket = bestNearBucket < bestFarBucket ? bestNearBucket : bestFarBucket; - var maxBucket = bestNearBucket > bestFarBucket ? bestNearBucket : bestFarBucket; - for (var j = minBucket; j <= maxBucket; j++) + int minBucket = bestNearBucket < bestFarBucket ? bestNearBucket : bestFarBucket; + int maxBucket = bestNearBucket > bestFarBucket ? bestNearBucket : bestFarBucket; + for (int j = minBucket; j <= maxBucket; j++) txSum += this.txCtAvg[j]; if (foundAnswer && txSum != 0) { txSum = txSum / 2; - for (var j = minBucket; j <= maxBucket; j++) + for (int j = minBucket; j <= maxBucket; j++) if (this.txCtAvg[j] < txSum) { txSum -= this.txCtAvg[j]; @@ -284,19 +329,28 @@ public double EstimateMedianVal(int confTarget, double sufficientTxVal, double s return median; } - // Return the max number of confirms we're tracking + /// + /// Return the max number of confirms we're tracking. + /// + /// The max number of confirms. public int GetMaxConfirms() { return this.confAvg.Count; } - // Write state of estimation data to a file + /// + /// Write state of estimation data to a file. + /// + /// Stream to write to. public void Write(BitcoinStream stream) { } - // Read saved state of estimation data from a file and replace all internal data structures and - // variables with this state. + /// + /// Read saved state of estimation data from a file and replace all internal data structures and + /// variables with this state. + /// + /// Stream to read from. public void Read(Stream filein) { } diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolPersistence.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolPersistence.cs index d5a4b6bc6a7..b3cb3e45d0f 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolPersistence.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolPersistence.cs @@ -1,39 +1,85 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using NBitcoin; using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; namespace Stratis.Bitcoin.Features.MemoryPool { + /// + /// Public interface for persisting the memory pool. + /// public interface IMempoolPersistence { + /// + /// Persists the memory pool to a file. + /// + /// The transaction memory pool. + /// The filename to persist to. Default filename is used if null. + /// Result of saving the memory pool. MemPoolSaveResult Save(TxMempool memPool, string fileName = null); + + /// + /// Loads the memory pool from a persisted file. + /// + /// Filename to load from. Default filename is used if null. + /// List of persistence entries. IEnumerable Load(string fileName = null); } + /// + /// The result of a memory pool save. + /// public struct MemPoolSaveResult { + /// Gets a non successful save result. public static MemPoolSaveResult NonSuccess { get { return new MemPoolSaveResult { Succeeded = false }; } } + + /// + /// Defines a successful save result. + /// + /// The transaction that was saved. + /// A successful save result. public static MemPoolSaveResult Success(uint trxSaved) { return new MemPoolSaveResult { Succeeded = true, TrxSaved = trxSaved }; } + /// Whether the file save was successful. public bool Succeeded { get; private set; } + + /// The transaction id that was saved. public uint TrxSaved { get; private set; } } + /// + /// A memory pool entry to be persisted. + /// public class MempoolPersistenceEntry : IBitcoinSerializable { - Transaction tx; - uint time; - uint feeDelta; + /// Memory pool transaction to persist. + private Transaction tx; + /// The transaction time. + private uint time; + + /// The transaction fee difference. + private uint feeDelta; + + /// Gets or set the transation for persistence. public Transaction Tx { get { return this.tx; } set { this.tx = value; } } + + /// Gets or sets the memory pools time for the transaction for persistence. public long Time { get { return (long)this.time; } set { this.time = (uint)value; } } + + /// Gets or sets the transaction fee difference for persistence. public long FeeDelta { get { return (long)this.feeDelta; } set { this.feeDelta = (uint)value; } } + /// + /// Creates a persistence entry from a memory pool transaction entry. + /// + /// Memory pool transaction entry. + /// Persistence entry. public static MempoolPersistenceEntry FromTxMempoolEntry(TxMempoolEntry tx) { return new MempoolPersistenceEntry() @@ -44,6 +90,10 @@ public static MempoolPersistenceEntry FromTxMempoolEntry(TxMempoolEntry tx) }; } + /// + /// Does a readwrite to the stream of this persistence entry. + /// + /// Stream to do readwrite to. public void ReadWrite(BitcoinStream stream) { stream.ReadWrite(ref this.tx); @@ -51,9 +101,14 @@ public void ReadWrite(BitcoinStream stream) stream.ReadWriteAsCompactVarInt(ref this.feeDelta); } + /// + /// Compares whether two persistence entries are equal. + /// + /// Object to compare this persistence entry to. + /// Whether the objects are equal. public override bool Equals(object obj) { - var toCompare = obj as MempoolPersistenceEntry; + MempoolPersistenceEntry toCompare = obj as MempoolPersistenceEntry; if (toCompare == null) return false; if ((this.tx == null) != (toCompare.tx == null)) @@ -68,27 +123,42 @@ public override bool Equals(object obj) return this.tx.ToHex().Equals(toCompare.tx.ToHex()); } + /// public override int GetHashCode() { return this.tx.GetHashCode(); } - } + /// + /// Object used for persisting memory pool transactions. + /// internal class MempoolPersistence : IMempoolPersistence { - public const ulong MEMPOOL_DUMP_VERSION = 0; + /// Current memory pool version number for persistence. + public const ulong MempoolDumpVersion = 0; + + /// The default filename used for memory pool persistence. public const string defaultFilename = "mempool.dat"; + /// Data directory to save persisted memory pool to. private readonly string dataDir; + + /// Logger for the memory pool persistence object. private readonly ILogger mempoolLogger; + /// + /// Constructs an instance of an object for persisting memory pool transactions. + /// + /// Node settings used for getting the data directory. + /// Logger factory for creating logger for this object. public MempoolPersistence(NodeSettings settings, ILoggerFactory loggerFactory) { this.dataDir = settings?.DataDir; this.mempoolLogger = loggerFactory.CreateLogger(this.GetType().FullName); } + /// public MemPoolSaveResult Save(TxMempool memPool, string fileName = null) { fileName = fileName ?? defaultFilename; @@ -96,6 +166,12 @@ public MemPoolSaveResult Save(TxMempool memPool, string fileName = null) return this.Save(toSave, fileName); } + /// + /// Saves a list of memory pool transaction entries to a persistence file. + /// + /// List of persistence transactions to save. + /// The filename to persist transactions to. + /// The save result. internal MemPoolSaveResult Save(IEnumerable toSave, string fileName) { Guard.NotEmpty(this.dataDir, nameof(this.dataDir)); @@ -114,7 +190,7 @@ internal MemPoolSaveResult Save(IEnumerable toSave, str } else { - using (var fs = new FileStream(tempFilePath, FileMode.Create)) + using (FileStream fs = new FileStream(tempFilePath, FileMode.Create)) { this.DumpToStream(toSave, fs); } @@ -133,11 +209,16 @@ internal MemPoolSaveResult Save(IEnumerable toSave, str return MemPoolSaveResult.NonSuccess; } + /// + /// Writes a collection of memory pool transactions to a stream. + /// + /// Collection of memory pool transations to save. + /// Stream to write transactions to. internal void DumpToStream(IEnumerable toSave, Stream stream) { var bitcoinWriter = new BitcoinStream(stream, true); - bitcoinWriter.ReadWrite(MEMPOOL_DUMP_VERSION); + bitcoinWriter.ReadWrite(MempoolDumpVersion); bitcoinWriter.ReadWrite(toSave.LongCount()); foreach (MempoolPersistenceEntry entry in toSave) @@ -146,6 +227,7 @@ internal void DumpToStream(IEnumerable toSave, Stream s } } + /// public IEnumerable Load(string fileName = null) { fileName = fileName ?? defaultFilename; @@ -157,7 +239,7 @@ public IEnumerable Load(string fileName = null) return null; try { - using (var fs = new FileStream(filePath, FileMode.Open)) + using (FileStream fs = new FileStream(filePath, FileMode.Open)) return this.LoadFromStream(fs); } catch (Exception ex) @@ -167,6 +249,11 @@ public IEnumerable Load(string fileName = null) } } + /// + /// Loads a collection of memory pool transactions from a persistence stream. + /// + /// Stream to load transactions from. + /// Collection of memory pool transactions. internal IEnumerable LoadFromStream(Stream stream) { var toReturn = new List(); @@ -179,7 +266,7 @@ internal IEnumerable LoadFromStream(Stream stream) try { bitcoinReader.ReadWrite(ref version); - if (version != MEMPOOL_DUMP_VERSION) + if (version != MempoolDumpVersion) { this.mempoolLogger.LogWarning($"Memorypool data is wrong version ({version}) aborting..."); return null; @@ -208,7 +295,6 @@ internal IEnumerable LoadFromStream(Stream stream) toReturn.Add(entry); } - return toReturn; } diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolValidationContext.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolValidationContext.cs index ea71b988cae..5d3293ad4fc 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolValidationContext.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolValidationContext.cs @@ -3,12 +3,25 @@ namespace Stratis.Bitcoin.Features.MemoryPool { + /// + /// Current state of memory pool validation. + /// public class MempoolValidationState { + /// + /// Constructs an instance of the memory pool validation state object. + /// + /// Whether free transactions were limited. public MempoolValidationState(bool limitFree) : this(limitFree, false, Money.Zero) { } + /// + /// Constructs and instance of the memory pool validation state object. + /// + /// Whether free transactions were limited. + /// Whether the memory pool limit was overridden. + /// The amount that was used for calculating an absurdly high fee. public MempoolValidationState(bool limitFree, bool overrideMempoolLimit, Money absurdFee) { this.LimitFree = limitFree; @@ -16,10 +29,46 @@ public MempoolValidationState(bool limitFree, bool overrideMempoolLimit, Money a this.OverrideMempoolLimit = overrideMempoolLimit; } + /// Get or sets the current error status for memory pool. public MempoolError Error { get; set; } + /// Gets or sets the current error message for memory pool. public string ErrorMessage { get; set; } + /// Gets or sets the value for an absurdly high transaction fee. + public Money AbsurdFee { get; set; } + + /// Gets or sets whether there are missing inputs on the transaction. + public bool MissingInputs { get; set; } + + /// Gets or sets whether transaction pool could be in a corrupted state. + public bool CorruptionPossible { get; set; } + + /// Gets or sets whether the validation state is in an invalid state. + public bool IsInvalid { get; set; } + + /// Gets or sets whether the memory pool limit has been overridden. + public bool OverrideMempoolLimit { get; set; } + + /// Gets or sets the acceptance time of the transaction to the memory pool. + public long AcceptTime { get; set; } + + /// Gets or sets whether free transactions are being limited. + public bool LimitFree { get; set; } + + // variables helpful for logging. + + /// Gets or sets the current number of transactions in the memory pool. + public long MempoolSize { get; set; } + + /// Gets or sets the memory pools dynamic size in bytes. + public long MempoolDynamicSize { get; set; } + + /// + /// Sets the memory pool validation state to invalid. + /// + /// The current error. + /// The current validation state of the memory pool. public MempoolValidationState Invalid(MempoolError error) { this.Error = error; @@ -27,6 +76,12 @@ public MempoolValidationState Invalid(MempoolError error) return this; } + /// + /// Sets the memory pool validation state to invalid, with an error message. + /// + /// The current error. + /// The current error message. + /// The current validation state of the memory pool. public MempoolValidationState Invalid(MempoolError error, string errorMessage) { this.Error = error; @@ -35,12 +90,23 @@ public MempoolValidationState Invalid(MempoolError error, string errorMessage) return this; } + /// + /// Sets the memory pool validation state to fail. + /// + /// The current error. + /// The current validation state of the memory pool. public MempoolValidationState Fail(MempoolError error) { this.Error = error; return this; } + /// + /// Sets the memory pool validation state to fail, with an error message. + /// + /// The current error. + /// The current error message. + /// The current validation state of the memory pool. public MempoolValidationState Fail(MempoolError error, string errorMessage) { this.Error = error; @@ -48,28 +114,19 @@ public MempoolValidationState Fail(MempoolError error, string errorMessage) return this; } - public Money AbsurdFee { get; set; } - - public bool MissingInputs { get; set; } - - public bool CorruptionPossible { get; set; } - public bool IsInvalid { get; set; } - - public bool OverrideMempoolLimit { get; set; } - - public long AcceptTime { get; set; } - - public bool LimitFree { get; set; } - - // variables helpful for logging - public long MempoolSize { get; set; } - public long MempoolDynamicSize { get; set; } - + /// + /// Throws a for the current error state. + /// + /// Current error state. public void Throw() { throw new MempoolErrorException(this); } + /// + /// Gets a string formatted error message with code. + /// + /// The error message as a string. public override string ToString() { return $"{this.Error?.RejectCode}{this.ErrorMessage} (code {this.Error?.Code})"; @@ -82,35 +139,62 @@ public override string ToString() /// public class MempoolValidationContext { + /// Gets the validation state of the memory pool. public MempoolValidationState State { get; } + /// Gets or sets the collection of transaction set conflicts. public List SetConflicts { get; set; } + /// Gets the current transaction being validated. public Transaction Transaction { get; } + /// Gets the hash of the current transaction being validated. public uint256 TransactionHash { get; } + /// Gets or sets the current entry in the memory pool. public TxMempoolEntry Entry { get; set; } + /// Gets or sets the current coin view of the transaction. public MempoolCoinView View { get; set; } + /// Gets or sets the size of the current entry in the memory pool. public int EntrySize { get; set; } + /// Gets or sets the set of all memory pool entries that are conflicting. public TxMempool.SetEntries AllConflicting { get; set; } + /// Gets or sets the transaction set's ancestors. public TxMempool.SetEntries SetAncestors { get; set; } + /// Gets or sets the lock points for the memory pool. public LockPoints LockPoints { get; set; } + /// Gets or sets the conflicting fees for the memory pool. public Money ConflictingFees { get; set; } + + /// Gets or sets the memory pool entry size of the conflicting transaction. public long ConflictingSize { get; set; } + + /// Gets or sets the number of other entries that are conflicting transactions. public long ConflictingCount { get; set; } + /// Value of the output of the transaction. public Money ValueOut { get; set; } + + /// Amount of the fees for the transaction. public Money Fees { get; set; } + + /// The Amount of the fees for the transaction after they have been modified. public Money ModifiedFees { get; set; } + + /// The total cost of the signature operations for the transaction. public long SigOpsCost { get; set; } + /// + /// Constructs a memory pool validation context object. + /// + /// The current transaction being validated. + /// The current memory pool validation state. public MempoolValidationContext(Transaction transaction, MempoolValidationState state) { this.Transaction = transaction; diff --git a/Stratis.Bitcoin/Features/MemoryPool/TxMemPool.cs b/Stratis.Bitcoin/Features/MemoryPool/TxMemPool.cs index aae013d60be..64da56b05d3 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/TxMemPool.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/TxMemPool.cs @@ -10,358 +10,183 @@ namespace Stratis.Bitcoin.Features.MemoryPool { - // Information about a mempool transaction. + /// + /// Information about a memory pool transaction. + /// public class TxMempoolInfo { - // The transaction itself + /// The transaction itself. public Transaction Trx { get; set; } - - // Time the transaction entered the mempool. + + /// Time the transaction entered the mempool. public long Time { get; set; } - // Feerate of the transaction. + /// Fee rate of the transaction. public FeeRate FeeRate { get; set; } - // The fee delta. + /// The fee delta. public long FeeDelta { get; set; } }; - /** - * CTxMemPool stores valid-according-to-the-current-best-chain transactions - * that may be included in the next block. - * - * Transactions are added when they are seen on the network (or created by the - * local node), but not all transactions seen are added to the pool. For - * example, the following new transactions will not be added to the mempool: - * - a transaction which doesn't make the mimimum fee requirements. - * - a new transaction that double-spends an input of a transaction already in - * the pool where the new transaction does not meet the Replace-By-Fee - * requirements as defined in BIP 125. - * - a non-standard transaction. - * - * CTxMemPool::mapTx, and CTxMemPoolEntry bookkeeping: - * - * mapTx is a boost::multi_index that sorts the mempool on 4 criteria: - * - transaction hash - * - feerate [we use max(feerate of tx, feerate of Transaction with all descendants)] - * - time in mempool - * - mining score (feerate modified by any fee deltas from PrioritiseTransaction) - * - * Note: the term "descendant" refers to in-mempool transactions that depend on - * this one, while "ancestor" refers to in-mempool transactions that a given - * transaction depends on. - * - * In order for the feerate sort to remain correct, we must update transactions - * in the mempool when new descendants arrive. To facilitate this, we track - * the set of in-mempool direct parents and direct children in mapLinks. Within - * each CTxMemPoolEntry, we track the size and fees of all descendants. - * - * Usually when a new transaction is added to the mempool, it has no in-mempool - * children (because any such children would be an orphan). So in - * addUnchecked(), we: - * - update a new entry's setMemPoolParents to include all in-mempool parents - * - update the new entry's direct parents to include the new tx as a child - * - update all ancestors of the transaction to include the new tx's size/fee - * - * When a transaction is removed from the mempool, we must: - * - update all in-mempool parents to not track the tx in setMemPoolChildren - * - update all ancestors to not include the tx's size/fees in descendant state - * - update all in-mempool children to not include it as a parent - * - * These happen in UpdateForRemoveFromMempool(). (Note that when removing a - * transaction along with its descendants, we must calculate that set of - * transactions to be removed before doing the removal, or else the mempool can - * be in an inconsistent state where it's impossible to walk the ancestors of - * a transaction.) - * - * In the event of a reorg, the assumption that a newly added tx has no - * in-mempool children is false. In particular, the mempool is in an - * inconsistent state while new transactions are being added, because there may - * be descendant transactions of a tx coming from a disconnected block that are - * unreachable from just looking at transactions in the mempool (the linking - * transactions may also be in the disconnected block, waiting to be added). - * Because of this, there's not much benefit in trying to search for in-mempool - * children in addUnchecked(). Instead, in the special case of transactions - * being added from a disconnected block, we require the caller to clean up the - * state, to account for in-mempool, out-of-block descendants for all the - * in-block transactions by calling UpdateTransactionsFromBlock(). Note that - * until this is called, the mempool state is not consistent, and in particular - * mapLinks may not be correct (and therefore functions like - * CalculateMemPoolAncestors() and CalculateDescendants() that rely - * on them to walk the mempool are not generally safe to use). - * - * Computational limits: - * - * Updating all in-mempool ancestors of a newly added transaction can be slow, - * if no bound exists on how many in-mempool ancestors there may be. - * CalculateMemPoolAncestors() takes configurable limits that are designed to - * prevent these calculations from being too CPU intensive. - * - * Adding transactions from a disconnected block can be very time consuming, - * because we don't have a way to limit the number of in-mempool descendants. - * To bound CPU processing, we limit the amount of work we're willing to do - * to properly update the descendant information for a tx being added from - * a disconnected block. If we would exceed the limit, then we instead mark - * the entry as "dirty", and set the feerate for sorting purposes to be equal - * the feerate of the transaction without any descendants. - * - */ + + /// + /// Memory pool of pending transactions. + /// + /// + /// + /// TxMempool stores valid-according-to-the-current-best-chain transactions + /// that may be included in the next block. + /// + /// Transactions are added when they are seen on the network(or created by the + /// local node), but not all transactions seen are added to the pool.For + /// example, the following new transactions will not be added to the mempool: + /// - a transaction which doesn't make the mimimum fee requirements. + /// - a new transaction that double-spends an input of a transaction already in + /// the pool where the new transaction does not meet the Replace-By-Fee + /// requirements as defined in BIP 125. + /// - a non-standard transaction. + /// + /// , and bookkeeping: + /// + /// is a collection that sorts the mempool on 4 criteria: + /// - transaction hash + /// - feerate[we use max(feerate of tx, feerate of Transaction with all descendants)] + /// - time in mempool + /// - mining score (feerate modified by any fee deltas from PrioritiseTransaction) + /// + /// Note: the term "descendant" refers to in-mempool transactions that depend on + /// this one, while "ancestor" refers to in-mempool transactions that a given + /// transaction depends on. + /// + /// In order for the feerate sort to remain correct, we must update transactions + /// in the mempool when new descendants arrive. To facilitate this, we track + /// the set of in-mempool direct parents and direct children in + /// each TxMempoolEntry, we track the size and fees of all descendants. + /// + /// Usually when a new transaction is added to the mempool, it has no in-mempool + /// children(because any such children would be an orphan). So in + /// , we: + /// - update a new entry's setMemPoolParents to include all in-mempool parents + /// - update the new entry's direct parents to include the new tx as a child + /// - update all ancestors of the transaction to include the new tx's size/fee + /// + /// When a transaction is removed from the mempool, we must: + /// - update all in-mempool parents to not track the tx in setMemPoolChildren + /// - update all ancestors to not include the tx's size/fees in descendant state + /// - update all in-mempool children to not include it as a parent + /// + /// These happen in . + /// (Note that when removing a + /// transaction along with its descendants, we must calculate that set of + /// transactions to be removed before doing the removal, or else the mempool can + /// be in an inconsistent state where it's impossible to walk the ancestors of + /// a transaction.) + /// + /// In the event of a reorg, the assumption that a newly added tx has no + /// in-mempool children is false. In particular, the mempool is in an + /// inconsistent state while new transactions are being added, because there may + /// be descendant transactions of a tx coming from a disconnected block that are + /// unreachable from just looking at transactions in the mempool(the linking + /// transactions may also be in the disconnected block, waiting to be added). + /// Because of this, there's not much benefit in trying to search for in-mempool + /// children in . + /// Instead, in the special case of transactions + /// being added from a disconnected block, we require the caller to clean up the + /// state, to account for in-mempool, out-of-block descendants for all the + /// in-block transactions by calling . Note that + /// until this is called, the mempool state is not consistent, and in particular + /// may not be correct (and therefore functions like + /// + /// and that rely + /// on them to walk the mempool are not generally safe to use). + /// + /// Computational limits: + /// + /// Updating all in-mempool ancestors of a newly added transaction can be slow, + /// if no bound exists on how many in-mempool ancestors there may be. + /// + /// takes configurable limits that are designed to + /// prevent these calculations from being too CPU intensive. + /// + /// Adding transactions from a disconnected block can be very time consuming, + /// because we don't have a way to limit the number of in-mempool descendants. + /// To bound CPU processing, we limit the amount of work we're willing to do + /// to properly update the descendant information for a tx being added from + /// a disconnected block. If we would exceed the limit, then we instead mark + /// the entry as "dirty", and set the feerate for sorting purposes to be equal + /// the feerate of the transaction without any descendants. + /// public class TxMempool { - // Fake height value used in CCoins to signify they are only in the memory pool (since 0.8) + /// Fake height value used in Coins to signify they are only in the memory pool (since 0.8). public const int MempoolHeight = 0x7FFFFFFF; - private double checkFrequency; //!< Value n means that n times in 2^32 we check. - private int nTransactionsUpdated; - public BlockPolicyEstimator MinerPolicyEstimator { get; } - - long totalTxSize; //!< sum of all mempool tx's virtual sizes. Differs from serialized Transaction size since witness data is discounted. Defined in BIP 141. - long cachedInnerUsage; //!< sum of dynamic memory usage of all the map elements (NOT the maps themselves) - - readonly FeeRate minReasonableRelayFee; - - long lastRollingFeeUpdate; - bool blockSinceLastRollingFeeBump; - double rollingMinimumFeeRate; //!< minimum fee to get into the pool, decreases exponentially - - public const int RollingFeeHalflife = 60 * 60 * 12; // public only for testing - - public class IndexedTransactionSet : Dictionary - { - public IndexedTransactionSet() : base(new SaltedTxidHasher()) - { - } + /// The rolling fee's half life. + public const int RollingFeeHalflife = 60 * 60 * 12; // public only for testing. - public void Add(TxMempoolEntry entry) - { - this.Add(entry.TransactionHash, entry); - } - - public void Remove(TxMempoolEntry entry) - { - this.Remove(entry.TransactionHash); - } - - public IEnumerable DescendantScore - { - get { return this.Values.OrderBy(o => o, new CompareTxMemPoolEntryByDescendantScore()); } - } - - public IEnumerable EntryTime - { - get { return this.Values.OrderBy(o => o, new CompareTxMemPoolEntryByEntryTime()); } - } + /// The indexed transaction set in the memory pool. + public IndexedTransactionSet MapTx; - public IEnumerable MiningScore - { - get { return this.Values.OrderBy(o => o, new CompareTxMemPoolEntryByScore()); } - } + /// Collection of transaction inputs. + public List MapNextTx; - public IEnumerable AncestorScore - { - get { return this.Values.OrderBy(o => o, new CompareTxMemPoolEntryByAncestorFee()); } - } + /// Value n means that n times in 2^32 we check. + private double checkFrequency; - private class SaltedTxidHasher : IEqualityComparer - { - public bool Equals(uint256 x, uint256 y) - { - return x == y; - } - - public int GetHashCode(uint256 obj) - { - // todo: need to compare with the c++ implementation - return obj.GetHashCode(); - } - } - - /** \class CompareTxMemPoolEntryByDescendantScore - * - * Sort an entry by max(score/size of entry's tx, score/size with all descendants). - */ - - private class CompareTxMemPoolEntryByDescendantScore : IComparer - { - public int Compare(TxMempoolEntry a, TxMempoolEntry b) - { - bool fUseADescendants = this.UseDescendantScore(a); - bool fUseBDescendants = this.UseDescendantScore(b); - - double aModFee = fUseADescendants ? a.ModFeesWithDescendants.Satoshi : a.ModifiedFee; - double aSize = fUseADescendants ? a.SizeWithDescendants : a.GetTxSize(); - - double bModFee = fUseBDescendants ? b.ModFeesWithDescendants.Satoshi : b.ModifiedFee; - double bSize = fUseBDescendants ? b.SizeWithDescendants : b.GetTxSize(); - - // Avoid division by rewriting (a/b > c/d) as (a*d > c*b). - double f1 = aModFee*bSize; - double f2 = aSize*bModFee; - - if (f1 == f2) - { - if (a.Time >= b.Time) - return -1; - return 1; - } - - if (f1 <= f2) - return -1; - return 1; - } - - // Calculate which score to use for an entry (avoiding division). - - bool UseDescendantScore(TxMempoolEntry a) - { - double f1 = (double) a.ModifiedFee*a.SizeWithDescendants; - double f2 = (double) a.ModFeesWithDescendants.Satoshi*a.GetTxSize(); - return f2 > f1; - } - } - - private class CompareTxMemPoolEntryByEntryTime : IComparer - { - public int Compare(TxMempoolEntry a, TxMempoolEntry b) - { - if (a.Time < b.Time) - return -1; - return 1; - } - } - - /** \class CompareTxMemPoolEntryByScore - * - * Sort by score of entry ((fee+delta)/size) in descending order - */ - private class CompareTxMemPoolEntryByScore : IComparer - { - public int Compare(TxMempoolEntry a, TxMempoolEntry b) - { - double f1 = (double) a.ModifiedFee*b.GetTxSize(); - double f2 = (double) b.ModifiedFee*a.GetTxSize(); - if (f1 == f2) - { - if (a.TransactionHash < b.TransactionHash) - return 1; - return -1; - } - if (f1 > f2) - return -1; - return 1; - } - } - - private class CompareTxMemPoolEntryByAncestorFee : IComparer - { - public int Compare(TxMempoolEntry a, TxMempoolEntry b) - { - double aFees = a.ModFeesWithAncestors.Satoshi; - double aSize = a.SizeWithAncestors; - - double bFees = b.ModFeesWithAncestors.Satoshi; - double bSize = b.SizeWithAncestors; - - // Avoid division by rewriting (a/b > c/d) as (a*d > c*b). - double f1 = aFees*bSize; - double f2 = aSize*bFees; - - if (f1 == f2) - { - if (a.TransactionHash < b.TransactionHash) - return -1; - return 1; - } - - if (f1 > f2) - return -1; - return 1; - } - } - } - - public class CompareIteratorByHash : IComparer - { - public int Compare(TxMempoolEntry a, TxMempoolEntry b) - { - return InnerCompare(a, b); - } - public static int InnerCompare(TxMempoolEntry a, TxMempoolEntry b) - { - if (a.TransactionHash == b.TransactionHash) return 0; - if (a.TransactionHash < b.TransactionHash) return -1; - return 1; - } - } - public class TxLinks - { - public SetEntries Parents; - public SetEntries Children; - }; - - public class SetEntries : SortedSet, IEquatable, IEqualityComparer - - { - public SetEntries() : base(new CompareIteratorByHash()) - { - } + /// Number of transactions updated. + private int nTransactionsUpdated; - public bool Equals(SetEntries other) - { - return this.SequenceEqual(other, this); - } + /// + /// Sum of all mempool tx's virtual sizes. + /// Differs from serialized Transaction size since witness data is discounted. Defined in BIP 141. + /// + private long totalTxSize; - public bool Equals(TxMempoolEntry x, TxMempoolEntry y) - { - return x.TransactionHash == y.TransactionHash; - } + /// Sum of dynamic memory usage of all the map elements (NOT the maps themselves). + private long cachedInnerUsage; - public int GetHashCode(TxMempoolEntry obj) - { - return obj?.TransactionHash?.GetHashCode() ?? 0; - } - } + /// + /// minReasonableRelayFee should be a feerate which is, roughly, somewhere + /// around what it "costs" to relay a transaction around the network and + /// below which we would reasonably say a transaction has 0-effective-fee. + /// + private readonly FeeRate minReasonableRelayFee; - public class TxlinksMap : SortedList - { - public TxlinksMap() : base(new CompareIteratorByHash()) - { - } - } + /// Time when the last rolling fee was updated. + private long lastRollingFeeUpdate; - public class DeltaPair - { - public double Delta; - public Money Amount; - } + /// Whether are new blocks since last rolling fee update. + private bool blockSinceLastRollingFeeBump; - public class NextTxPair - { - public OutPoint OutPoint; - public Transaction Transaction; - } + /// minimum fee to get into the pool, decreases exponentially. + private double rollingMinimumFeeRate; - public IndexedTransactionSet MapTx; + /// Collection of transaction links. private TxlinksMap mapLinks; - public List MapNextTx; + + /// Dictionary of indexed by transaction hash. private Dictionary mapDeltas; - private Dictionary vTxHashes; //!< All tx witness hashes/entries in mapTx, in random order - private IDateTimeProvider TimeProvider { get; } + /// All tx witness hashes/entries in mapTx, in random order. + private Dictionary vTxHashes; + + /// Logger for the memory pool. private readonly ILogger logger; - /** Create a new CTxMemPool. - * minReasonableRelayFee should be a feerate which is, roughly, somewhere - * around what it "costs" to relay a transaction around the network and - * below which we would reasonably say a transaction has 0-effective-fee. - */ + /// + /// Constructs a new TxMempool object. + /// + /// The fee rate for a minimum reasonable relay fee. + /// The data and time provider for accessing current date and time. + /// The block policy estimator object. + /// Factory for creating loggers. public TxMempool(FeeRate minReasonableRelayFee, IDateTimeProvider dateTimeProvider, BlockPolicyEstimator blockPolicyEstimator, ILoggerFactory loggerFactory) { this.MapTx = new IndexedTransactionSet(); this.mapLinks = new TxlinksMap(); this.MapNextTx = new List(); this.mapDeltas = new Dictionary(); - this.vTxHashes = new Dictionary(); //!< All tx witness hashes/entries in mapTx, in random order + this.vTxHashes = new Dictionary(); // All tx witness hashes/entries in mapTx, in random order. this.logger = loggerFactory.CreateLogger(this.GetType().FullName); this.TimeProvider = dateTimeProvider; this.InnerClear(); //lock free clear @@ -375,6 +200,19 @@ public TxMempool(FeeRate minReasonableRelayFee, IDateTimeProvider dateTimeProvid this.minReasonableRelayFee = minReasonableRelayFee; } + /// Gets the miner policy estimator. + public BlockPolicyEstimator MinerPolicyEstimator { get; } + + /// Get the number of transactions in the memory pool. + public long Size { get { return this.MapTx.Count; } } + + /// Gets the date and time provider. + private IDateTimeProvider TimeProvider { get; } + + /// + /// Clears the collections that contain the memory pool transactions, + /// and increments the running total of transactions updated. + /// private void InnerClear() { this.mapLinks.Clear(); @@ -388,12 +226,19 @@ private void InnerClear() ++this.nTransactionsUpdated; } + /// + /// + /// public void Clear() { //LOCK(cs); this.InnerClear(); } + /// + /// Set the new memory pools min fee to the fee rate of the removed set. + /// + /// Fee rate of the removed set private void trackPackageRemoved(FeeRate rate) { // candidate for async @@ -406,12 +251,14 @@ private void trackPackageRemoved(FeeRate rate) } } - /** - * If sanity-checking is turned on, check makes sure the pool is - * consistent (does not contain two transactions that spend the same inputs, - * all inputs are in the mapNextTx array). If sanity-checking is turned off, - * check does nothing. - */ + /// + /// If sanity-checking is turned on, check makes sure the pool is consistent. + /// (does not contain two transactions that spend the same inputs, + /// all inputs are in the mapNextTx array). If sanity-checking is turned off, + /// check does nothing. + /// + /// Coin view of the transaction. + /// public void Check(CoinView pcoins) { if (this.checkFrequency == 0) @@ -425,41 +272,80 @@ public void Check(CoinView pcoins) throw new NotImplementedException(); } + /// + /// Gets the transaction from the memory pool based upon the transaction hash. + /// + /// Transaction hash. + /// The transaction. public Transaction Get(uint256 hash) { return this.MapTx.TryGet(hash)?.Transaction; } + /// + /// Gets the estimated fee using . + /// + /// The confirmation target blocks. + /// The fee rate estimate. public FeeRate EstimateFee(int nBlocks) { return this.MinerPolicyEstimator.EstimateFee(nBlocks); } + /// + /// Estimates the smart fee using . + /// + /// The confirmation target blocks. + /// The block where the fee was found. + /// The fee rate estimate. public FeeRate EstimateSmartFee(int nBlocks, out int answerFoundAtBlocks) { return this.MinerPolicyEstimator.EstimateSmartFee(nBlocks, this, out answerFoundAtBlocks); } + /// + /// Estimates the priority using . + /// + /// The confirmation target blocks. + /// The estimated priority. public double EstimatePriority(int nBlocks) { return this.MinerPolicyEstimator.EstimatePriority(nBlocks); } + /// + /// Estimates the smart priority using . + /// + /// The confirmation target blocks. + /// The block where the priority was found. + /// The estimated priority. public double EstimateSmartPriority(int nBlocks, out int answerFoundAtBlocks) { return this.MinerPolicyEstimator.EstimateSmartPriority(nBlocks, this, out answerFoundAtBlocks); } + /// + /// Set how frequent the sanity check is executed. + /// + /// The frequency of the sanity check. public void SetSanityCheck(double dFrequency = 1.0) { this.checkFrequency = dFrequency * 4294967295.0; } - // addUnchecked must updated state for all ancestors of a given transaction, - // to track size/count of descendant transactions. First version of - // addUnchecked can be used to have it call CalculateMemPoolAncestors(), and - // then invoke the second version. + /// + /// Add to memory pool without checking anything after calculating transaction ancestors. + /// Must update state for all ancestors of a given transaction, to track size/count of descendant transactions. + /// + /// Transaction hash. + /// Memory pool entry. + /// Whether to update fee estimate. + /// Whether transaction was added successfully. + /// + /// First version of AddUnchecked can be used to have it call CalculateMemPoolAncestors(), and + /// then invoke the second version. + /// public bool AddUnchecked(uint256 hash, TxMempoolEntry entry, bool validFeeEstimate = true) { //LOCK(cs); @@ -471,11 +357,19 @@ public bool AddUnchecked(uint256 hash, TxMempoolEntry entry, bool validFeeEstima } + /// + /// Add to memory pool without checking anything. + /// + /// Transaction hash. + /// Memory pool entry. + /// Transaction ancestors. + /// Whether to update fee estimate. + /// Whether transaction was added successfully. + /// + /// Used by AcceptToMemoryPool(), which DOES do all the appropriate checks. + /// 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); this.MapTx.Add(entry); this.mapLinks.Add(entry, new TxLinks {Parents = new SetEntries(), Children = new SetEntries()}); @@ -483,7 +377,7 @@ public bool AddUnchecked(uint256 hash, TxMempoolEntry entry, SetEntries setAnces // Update transaction for any feeDelta created by PrioritiseTransaction // TODO: refactor so that the fee delta is calculated before inserting // into mapTx. - var pos = this.mapDeltas.TryGet(hash); + DeltaPair pos = this.mapDeltas.TryGet(hash); if (pos != null) { if (pos.Amount != null) @@ -497,9 +391,9 @@ public bool AddUnchecked(uint256 hash, TxMempoolEntry entry, SetEntries setAnces // further updated.) this.cachedInnerUsage += entry.DynamicMemoryUsage(); - var tx = entry.Transaction; + Transaction tx = entry.Transaction; HashSet setParentTransactions = new HashSet(); - foreach (var txInput in tx.Inputs) + foreach (TxIn txInput in tx.Inputs) { this.MapNextTx.Add(new NextTxPair {OutPoint = txInput.PrevOut, Transaction = tx}); @@ -513,9 +407,9 @@ public bool AddUnchecked(uint256 hash, TxMempoolEntry entry, SetEntries setAnces // to clean up the mess we're leaving here. // Update ancestors with information about this tx - foreach (var phash in setParentTransactions) + foreach (uint256 phash in setParentTransactions) { - var pit = this.MapTx.TryGet(phash); + TxMempoolEntry pit = this.MapTx.TryGet(phash); if (pit != null) this.UpdateParent(entry, pit, true); } @@ -534,15 +428,18 @@ public bool AddUnchecked(uint256 hash, TxMempoolEntry entry, SetEntries setAnces return true; } - /** Set ancestor state for an entry */ - + /// + /// Set ancestor state for an entry. + /// + /// Memory pool entry. + /// Transaction ancestors. void UpdateEntryForAncestors(TxMempoolEntry it, SetEntries setAncestors) { long updateCount = setAncestors.Count; long updateSize = 0; Money updateFee = 0; long updateSigOpsCost = 0; - foreach (var ancestorIt in setAncestors) + foreach (TxMempoolEntry ancestorIt in setAncestors) { updateSize += ancestorIt.GetTxSize(); @@ -552,43 +449,64 @@ void UpdateEntryForAncestors(TxMempoolEntry it, SetEntries setAncestors) it.UpdateAncestorState(updateSize, updateFee, updateCount, updateSigOpsCost); } - /** Update ancestors of hash to add/remove it as a descendant transaction. */ + /// + /// Update ancestors of hash to add/remove it as a descendant transaction. + /// + /// Whether to add or remove. + /// Memory pool entry. + /// Transaction ancestors. private void UpdateAncestorsOf(bool add, TxMempoolEntry it, SetEntries setAncestors) { SetEntries parentIters = this.GetMemPoolParents(it); // add or remove this tx as a child of each parent - foreach (var piter in parentIters) + foreach (TxMempoolEntry piter in parentIters) this.UpdateChild(piter, it, add); long updateCount = (add ? 1 : -1); long updateSize = updateCount*it.GetTxSize(); Money updateFee = updateCount*it.ModifiedFee; - foreach (var ancestorIt in setAncestors) + foreach (TxMempoolEntry ancestorIt in setAncestors) { ancestorIt.UpdateDescendantState(updateSize, updateFee, updateCount); } } + /// + /// Gets the parents of a memory pool entry. + /// + /// Memory pool entry. + /// Set of parent entries. private SetEntries GetMemPoolParents(TxMempoolEntry entry) { Guard.NotNull(entry, nameof(entry)); Utilities.Guard.Assert(this.MapTx.ContainsKey(entry.TransactionHash)); - var it = this.mapLinks.TryGet(entry); + TxLinks it = this.mapLinks.TryGet(entry); Utilities.Guard.Assert(it != null); return it.Parents; } + /// + /// Gets the children of a memory pool entry. + /// + /// Memory pool entry. + /// Set of child entries. private SetEntries GetMemPoolChildren(TxMempoolEntry entry) { Guard.NotNull(entry, nameof(entry)); Utilities.Guard.Assert(this.MapTx.ContainsKey(entry.TransactionHash)); - var it = this.mapLinks.TryGet(entry); + TxLinks it = this.mapLinks.TryGet(entry); Utilities.Guard.Assert(it != null); return it.Children; } + /// + /// Updates memory pool entry with a child. + /// + /// Memory pool entry. + /// Child entry to add/remove. + /// Whether to add or remove entry. private void UpdateChild(TxMempoolEntry entry, TxMempoolEntry child, bool add) { // todo: find how to take a memory size of SetEntries @@ -603,6 +521,12 @@ private void UpdateChild(TxMempoolEntry entry, TxMempoolEntry child, bool add) } } + /// + /// Updates memory pool entry with a parent. + /// + /// Memory pool entry. + /// Parent entry to add/remove. + /// Whether to add or remove entry. private void UpdateParent(TxMempoolEntry entry, TxMempoolEntry parent, bool add) { // todo: find how to take a memory size of SetEntries @@ -617,32 +541,35 @@ private void UpdateParent(TxMempoolEntry entry, TxMempoolEntry parent, bool add) } } - /** Try to calculate all in-mempool ancestors of entry. - * (these are all calculated including the tx itself) - * limitAncestorCount = max number of ancestorsUpdateTransactionsFromBlock - * limitAncestorSize = max size of ancestors - * limitDescendantCount = max number of descendants any ancestor can have - * limitDescendantSize = max size of descendants any ancestor can have - * errString = populated with error reason if any limits are hit - * 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 - */ + /// + /// Try to calculate all in-mempool ancestors of entry. + /// (these are all calculated including the tx itself) + /// + /// Memory pool entry. + /// Set of ancestors that the ancestors are added to. + /// Sax number of ancestorsUpdateTransactionsFromBlock. + /// Max size of ancestors. + /// Max number of descendants any ancestor can have. + /// Max size of descendants any ancestor can have. + /// Populated with error reason if any limits are hit. + /// 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. + /// Whether operation was successful. public bool CalculateMemPoolAncestors(TxMempoolEntry entry, SetEntries setAncestors, long limitAncestorCount, long limitAncestorSize, long limitDescendantCount, long limitDescendantSize, out string errString, bool fSearchForParents = true) { errString = string.Empty; SetEntries parentHashes = new SetEntries(); - var tx = entry.Transaction; + Transaction tx = entry.Transaction; if (fSearchForParents) { // Get parents of this transaction that are in the mempool // GetMemPoolParents() is only valid for entries in the mempool, so we // iterate mapTx to find parents. - foreach (var txInput in tx.Inputs) + foreach (TxIn txInput in tx.Inputs) { - var piter = this.MapTx.TryGet(txInput.PrevOut.Hash); + TxMempoolEntry piter = this.MapTx.TryGet(txInput.PrevOut.Hash); if (piter != null) { parentHashes.Add(piter); @@ -659,16 +586,16 @@ public bool CalculateMemPoolAncestors(TxMempoolEntry entry, SetEntries setAncest // If we're not searching for parents, we require this to be an // entry in the mempool already. //var it = mapTx.Txids.TryGet(entry.TransactionHash); - var memPoolParents = this.GetMemPoolParents(entry); - foreach (var item in memPoolParents) + SetEntries memPoolParents = this.GetMemPoolParents(entry); + foreach (TxMempoolEntry item in memPoolParents) parentHashes.Add(item); } - var totalSizeWithAncestors = entry.GetTxSize(); + long totalSizeWithAncestors = entry.GetTxSize(); while (parentHashes.Any()) { - var stageit = parentHashes.First(); + TxMempoolEntry stageit = parentHashes.First(); setAncestors.Add(stageit); parentHashes.Remove(stageit); @@ -690,8 +617,8 @@ public bool CalculateMemPoolAncestors(TxMempoolEntry entry, SetEntries setAncest return false; } - var setMemPoolParents = this.GetMemPoolParents(stageit); - foreach (var phash in setMemPoolParents) + SetEntries setMemPoolParents = this.GetMemPoolParents(stageit); + foreach (TxMempoolEntry phash in setMemPoolParents) { // If this is a new ancestor, add it. if (!setAncestors.Contains(phash)) @@ -709,34 +636,41 @@ public bool CalculateMemPoolAncestors(TxMempoolEntry entry, SetEntries setAncest return true; } - - // Check that none of this transactions inputs are in the mempool, and thus - // the tx is not dependent on other mempool transactions to be included in a block. - public bool HasNoInputsOf(Transaction tx) + /// + /// Check that none of this transactions inputs are in the mempool, and thus + /// the tx is not dependent on other mempool transactions to be included in a block. + /// + /// The transaction to check. + /// Whether the transaction is not dependent on other transaction. + public bool HasNoInputsOf(Transaction tx) { - foreach (var txInput in tx.Inputs) + foreach (TxIn txInput in tx.Inputs) if (this.Exists(txInput.PrevOut.Hash)) return false; return true; } + /// + /// Whether the transaction hash exists in the memory pool. + /// + /// Transaction hash. + /// Whether the transaction exists. public bool Exists(uint256 hash) { return this.MapTx.ContainsKey(hash); } - public long Size - { - get { return this.MapTx.Count; } - } - + /// + /// Removes the transaction from the memory pool recursively. + /// + /// The original transaction to remove. public void RemoveRecursive(Transaction origTx) { // Remove transaction from memory pool var origHahs = origTx.GetHash(); SetEntries txToRemove = new SetEntries(); - var origit = this.MapTx.TryGet(origHahs); + TxMempoolEntry origit = this.MapTx.TryGet(origHahs); if (origit != null) { txToRemove.Add(origit); @@ -749,56 +683,64 @@ public void RemoveRecursive(Transaction origTx) // the mempool for any reason. for (int i = 0; i < origTx.Outputs.Count; i++) { - var it = this.MapNextTx.FirstOrDefault(w => w.OutPoint == new OutPoint(origHahs, i)); + NextTxPair it = this.MapNextTx.FirstOrDefault(w => w.OutPoint == new OutPoint(origHahs, i)); if (it == null) continue; - var nextit = this.MapTx.TryGet(it.Transaction.GetHash()); + TxMempoolEntry nextit = this.MapTx.TryGet(it.Transaction.GetHash()); Utilities.Guard.Assert(nextit != null); txToRemove.Add(nextit); } } SetEntries setAllRemoves = new SetEntries(); - foreach (var item in txToRemove) + foreach (TxMempoolEntry item in txToRemove) { - this.CalculateDescendants(item, setAllRemoves); } this.RemoveStaged(setAllRemoves, false); } - /** Remove a set of transactions from the mempool. - * If a transaction is in this set, then all in-mempool descendants must - * also be in the set, unless this transaction is being removed for being - * in a block. - * Set updateDescendants to true when removing a tx that was in a block, so - * that any in-mempool descendants have their ancestor state updated. - */ + /// + /// Remove a set of transactions from the mempool. + /// + /// Staged transactions. + /// Whether to update decendants. + /// + /// If a transaction is in this set, then all in-mempool descendants must + /// also be in the set, unless this transaction is being removed for being + /// in a block. + /// Set updateDescendants to true when removing a tx that was in a block, so + /// that any in-mempool descendants have their ancestor state updated. + /// public void RemoveStaged(SetEntries stage, bool updateDescendants) { //AssertLockHeld(cs); this.UpdateForRemoveFromMempool(stage, updateDescendants); - foreach (var it in stage) + foreach (TxMempoolEntry it in stage) { this.RemoveUnchecked(it); } } - // Expire all transaction (and their dependencies) in the mempool older than time. Return the number of removed transactions. + /// + /// Expire all transaction (and their dependencies) in the mempool older than time. + /// + /// Expiry time. + /// Return the number of removed transactions. public int Expire(long time) { //LOCK(cs); SetEntries toremove = new SetEntries(); - foreach (var entry in this.MapTx.EntryTime) + foreach (TxMempoolEntry entry in this.MapTx.EntryTime) { if (!(entry.Time < time)) break; toremove.Add(entry); } SetEntries stage = new SetEntries(); - foreach (var removeit in toremove) + foreach (TxMempoolEntry removeit in toremove) { this.CalculateDescendants(removeit, stage); } @@ -806,18 +748,23 @@ public int Expire(long time) return stage.Count; } - /** Before calling removeUnchecked for a given transaction, - * UpdateForRemoveFromMempool must be called on the entire (dependent) set - * of transactions being removed at the same time. We use each - * CTxMemPoolEntry's setMemPoolParents in order to walk ancestors of a - * given transaction that is removed, so we can't remove intermediate - * transactions in a chain before we've updated all the state for the - * removal. - */ + /// + /// Removes a memory pool entry without checking. + /// + /// Memory pool entry. + /// + /// Before calling removeUnchecked for a given transaction, + /// UpdateForRemoveFromMempool must be called on the entire(dependent) set + /// of transactions being removed at the same time. We use each + /// TxMemPoolEntry's SetMemPoolParents in order to walk ancestors of a + /// given transaction that is removed, so we can't remove intermediate + /// transactions in a chain before we've updated all the state for the + /// removal. + /// private void RemoveUnchecked(TxMempoolEntry it) { - var hash = it.TransactionHash; - foreach (var txin in it.Transaction.Inputs) + uint256 hash = it.TransactionHash; + foreach (TxIn txin in it.Transaction.Inputs) { this.MapNextTx.Remove(this.MapNextTx.FirstOrDefault(w => w.OutPoint == txin.PrevOut)); } @@ -844,12 +791,18 @@ private void RemoveUnchecked(TxMempoolEntry it) this.MinerPolicyEstimator.RemoveTx(hash); } - // Calculates descendants of entry that are not already in setDescendants, and adds to - // setDescendants. Assumes entryit is already a tx in the mempool and setMemPoolChildren - // is correct for tx and all descendants. - // Also assumes that if an entry is in setDescendants already, then all - // in-mempool descendants of it are already in setDescendants as well, so that we - // can save time by not iterating over those entries. + /// + /// Calculates descendants of entry that are not already in setDescendants, and adds to setDecendants. + /// + /// Memory pool entry. + /// Set of entry decendants to add to. + /// + /// Assumes entryit is already a tx in the mempool and setMemPoolChildren + /// is correct for tx and all descendants. + /// Also assumes that if an entry is in setDescendants already, then all + /// in-mempool descendants of it are already in setDescendants as well, so that we + /// can save time by not iterating over those entries. + /// public void CalculateDescendants(TxMempoolEntry entryit, SetEntries setDescendants) { SetEntries stage = new SetEntries(); @@ -862,12 +815,12 @@ public void CalculateDescendants(TxMempoolEntry entryit, SetEntries setDescendan // already been walked, or will be walked in this iteration). while (stage.Any()) { - var it = stage.First(); + TxMempoolEntry it = stage.First(); setDescendants.Add(it); stage.Remove(it); - var setChildren = this.GetMemPoolChildren(it); - foreach (var childiter in setChildren) + SetEntries setChildren = this.GetMemPoolChildren(it); + foreach (TxMempoolEntry childiter in setChildren) { if (!setDescendants.Contains(childiter)) { @@ -877,15 +830,16 @@ public void CalculateDescendants(TxMempoolEntry entryit, SetEntries setDescendan } } - /** For each transaction being removed, update ancestors and any direct children. - * If updateDescendants is true, then also update in-mempool descendants' - * ancestor state. */ - + /// + /// For each transaction being removed, update ancestors and any direct children. + /// + /// Memory pool entries to remove. + /// If updateDescendants is true, then also update in-mempool descendants' ancestor state. private void UpdateForRemoveFromMempool(SetEntries entriesToRemove, bool updateDescendants) { // For each entry, walk back all ancestors and decrement size associated with this // transaction - var nNoLimit = long.MaxValue; + long nNoLimit = long.MaxValue; if (updateDescendants) { @@ -895,22 +849,22 @@ private void UpdateForRemoveFromMempool(SetEntries entriesToRemove, bool updateD // Here we only update statistics and not data in mapLinks (which // we need to preserve until we're finished with all operations that // need to traverse the mempool). - foreach (var removeIt in entriesToRemove) + foreach (TxMempoolEntry removeIt in entriesToRemove) { SetEntries setDescendants = new SetEntries(); this.CalculateDescendants(removeIt, setDescendants); setDescendants.Remove(removeIt); // don't update state for self - var modifySize = -removeIt.GetTxSize(); - var modifyFee = -removeIt.ModifiedFee; - var modifySigOps = -removeIt.SigOpCost; + long modifySize = -removeIt.GetTxSize(); + long modifyFee = -removeIt.ModifiedFee; + long modifySigOps = -removeIt.SigOpCost; - foreach (var dit in setDescendants) + foreach (TxMempoolEntry dit in setDescendants) dit.UpdateAncestorState(modifySize, modifyFee, -1, modifySigOps); } } - foreach (var entry in entriesToRemove) + foreach (TxMempoolEntry entry in entriesToRemove) { SetEntries setAncestors = new SetEntries(); string dummy = string.Empty; @@ -940,42 +894,46 @@ private void UpdateForRemoveFromMempool(SetEntries entriesToRemove, bool updateD // After updating all the ancestor sizes, we can now sever the link between each // transaction being removed and any mempool children (ie, update setMemPoolParents // for each direct child of a transaction being removed). - foreach (var removeIt in entriesToRemove) + foreach (TxMempoolEntry removeIt in entriesToRemove) { this.UpdateChildrenForRemoval(removeIt); } } - /** Sever link between specified transaction and direct children. */ + /// + /// Sever link between specified transaction and direct children. + /// + /// Memory pool entry. private void UpdateChildrenForRemoval(TxMempoolEntry it) { - var setMemPoolChildren = this.GetMemPoolChildren(it); - foreach (var updateIt in setMemPoolChildren) + SetEntries setMemPoolChildren = this.GetMemPoolChildren(it); + foreach (TxMempoolEntry updateIt in setMemPoolChildren) this.UpdateParent(updateIt, it, false); } - /** - * Called when a block is connected. Removes from mempool and updates the miner fee estimator. - */ - + /// + /// Called when a block is connected. Removes transactions from mempool and updates the miner fee estimator. + /// + /// Collection of transactions. + /// Height to connect the block. public void RemoveForBlock(IEnumerable vtx, int blockHeight) { var entries = new List(); - foreach (var tx in vtx) + foreach (Transaction tx in vtx) { uint256 hash = tx.GetHash(); - var entry = this.MapTx.TryGet(hash); + TxMempoolEntry entry = this.MapTx.TryGet(hash); if (entry != null) entries.Add(entry); } // Before the txs in the new block have been removed from the mempool, update policy estimates this.MinerPolicyEstimator.ProcessBlock(blockHeight, entries); - foreach (var tx in vtx) + foreach (Transaction tx in vtx) { uint256 hash = tx.GetHash(); - var entry = this.MapTx.TryGet(hash); + TxMempoolEntry entry = this.MapTx.TryGet(hash); if (entry != null) { SetEntries stage = new SetEntries(); @@ -990,16 +948,20 @@ public void RemoveForBlock(IEnumerable vtx, int blockHeight) this.blockSinceLastRollingFeeBump = true; } + /// + /// Removes conflicting transactions. + /// + /// Transaction to remove conflicts from. private void RemoveConflicts(Transaction tx) { // Remove transactions which depend on inputs of tx, recursively //LOCK(cs); - foreach (var txInput in tx.Inputs) + foreach (TxIn txInput in tx.Inputs) { - var it = this.MapNextTx.FirstOrDefault(p => p.OutPoint == txInput.PrevOut); + NextTxPair it = this.MapNextTx.FirstOrDefault(p => p.OutPoint == txInput.PrevOut); if (it != null) { - var txConflict = it.Transaction; + Transaction txConflict = it.Transaction; if (txConflict != tx) { this.ClearPrioritisation(txConflict.GetHash()); @@ -1009,12 +971,20 @@ private void RemoveConflicts(Transaction tx) } } + /// + /// Clears the prioritisation for a transaction. + /// + /// Transaction hash. private void ClearPrioritisation(uint256 hash) { //LOCK(cs); this.mapDeltas.Remove(hash); } + /// + /// Get the amount of dynamic memory being used by the memory pool. + /// + /// Number of bytes in use by memory pool. public long DynamicMemoryUsage() { // TODO : calculate roughly the size of each element in its list @@ -1037,6 +1007,11 @@ public long DynamicMemoryUsage() return this.MapTx.Values.Sum(m => m.DynamicMemoryUsage()) + this.cachedInnerUsage; } + /// + /// Trims the memory pool to a size limite. + /// + /// Size limit to trim memory pool to. + /// Collection of no spends transactions remaining. public void TrimToSize(long sizelimit, List pvNoSpendsRemaining = null) { //LOCK(cs); @@ -1045,7 +1020,7 @@ public void TrimToSize(long sizelimit, List pvNoSpendsRemaining = null) FeeRate maxFeeRateRemoved = new FeeRate(0); while (this.MapTx.Any() && this.DynamicMemoryUsage() > sizelimit) { - var it = this.MapTx.DescendantScore.First(); + TxMempoolEntry it = this.MapTx.DescendantScore.First(); // We set the new mempool min fee to the feerate of the removed set, plus the // "minimum reasonable fee rate" (ie some value under which we consider txn @@ -1064,19 +1039,19 @@ public void TrimToSize(long sizelimit, List pvNoSpendsRemaining = null) List txn = new List(); if (pvNoSpendsRemaining != null) { - foreach (var setEntry in stage) + foreach (TxMempoolEntry setEntry in stage) txn.Add(setEntry.Transaction); } this.RemoveStaged(stage, false); if (pvNoSpendsRemaining != null) { - foreach (var tx in txn) { - foreach (var txin in tx.Inputs) + foreach (Transaction tx in txn) { + foreach (TxIn txin in tx.Inputs) { if (this.Exists(txin.PrevOut.Hash)) continue; - var iter = this.MapNextTx.FirstOrDefault(p => p.OutPoint == new OutPoint(txin.PrevOut.Hash, 0)); + NextTxPair iter = this.MapNextTx.FirstOrDefault(p => p.OutPoint == new OutPoint(txin.PrevOut.Hash, 0)); if (iter == null || iter.OutPoint.Hash != txin.PrevOut.Hash) pvNoSpendsRemaining.Add(txin.PrevOut.Hash); } @@ -1088,19 +1063,23 @@ public void TrimToSize(long sizelimit, List pvNoSpendsRemaining = null) this.logger.LogInformation($"Removed {nTxnRemoved} txn, rolling minimum fee bumped to {maxFeeRateRemoved}"); } - /** The minimum fee to get into the mempool, which may itself not be enough - * for larger-sized transactions. - * The minReasonableRelayFee constructor arg is used to bound the time it - * takes the fee rate to go back down all the way to 0. When the feerate - * would otherwise be half of this, it is set to 0 instead. - */ + /// + /// The minimum fee to get into the mempool, which may itself not be enough for larger-sized transactions. + /// + /// Size limit of the memory pool in bytes. + /// The minimum fee. + /// + /// The minReasonableRelayFee constructor arg is used to bound the time it + /// takes the fee rate to go back down all the way to 0. When the feerate + /// would otherwise be half of this, it is set to 0 instead. + /// public FeeRate GetMinFee(long sizelimit) { //LOCK(cs); if (!this.blockSinceLastRollingFeeBump || this.rollingMinimumFeeRate == 0) return new FeeRate(new Money((int)this.rollingMinimumFeeRate)); - var time = this.TimeProvider.GetTime(); + long time = this.TimeProvider.GetTime(); if (time > this.lastRollingFeeUpdate + 10) { double halflife = RollingFeeHalflife; @@ -1119,10 +1098,16 @@ public FeeRate GetMinFee(long sizelimit) } } - var ret = Math.Max(this.rollingMinimumFeeRate, this.minReasonableRelayFee.FeePerK.Satoshi); + double ret = Math.Max(this.rollingMinimumFeeRate, this.minReasonableRelayFee.FeePerK.Satoshi); return new FeeRate(new Money((int)ret)); } + /// + /// Apply transaction priority and fee deltas. + /// + /// Hash of the transaction. + /// Priority delta to update. + /// Fee delta to update. public void ApplyDeltas(uint256 hash, ref double dPriorityDelta, ref Money nFeeDelta) { //LOCK(cs); @@ -1134,11 +1119,20 @@ public void ApplyDeltas(uint256 hash, ref double dPriorityDelta, ref Money nFeeD nFeeDelta += delta.Amount; } + /// + /// Gets the threshold for allowing free transactions. + /// + /// The priority threshold. public static double AllowFreeThreshold() { return Money.COIN * 144 / 250; } + /// + /// Whether the priority meets the fresh threshold. + /// + /// Priority to check. + /// Whether it meets the free threshold. public static bool AllowFree(double dPriority) { // Large (in bytes) low-priority (new, small-coin) transactions @@ -1146,24 +1140,349 @@ public static bool AllowFree(double dPriority) return dPriority > AllowFreeThreshold(); } + /// + /// Write fee estimates to a stream. + /// + /// Stream to write to. public void WriteFeeEstimates(BitcoinStream stream) { } + /// + /// Read fee estimates from a stream. + /// + /// Stream to read from. public void ReadFeeEstimates(BitcoinStream stream) { } + /// + /// Get number of transactions that have been updated. + /// + /// Number of transactions. public int GetTransactionsUpdated() { return this.nTransactionsUpdated; } + /// + /// Increments number of transaction that have been updated counter. + /// + /// Number of transactions to increment by. public void AddTransactionsUpdated(int n) { this.nTransactionsUpdated += n; } + + #region Classes + + /// + /// Indexed transaction set used to store memory pool transactions. + /// + public class IndexedTransactionSet : Dictionary + { + /// + /// Constructs a indexed transaction set. + /// + public IndexedTransactionSet() : base(new SaltedTxidHasher()) + { + } + + /// Gets a collection of memory pool entries ordered by descendant score. + public IEnumerable DescendantScore + { + get { return this.Values.OrderBy(o => o, new CompareTxMemPoolEntryByDescendantScore()); } + } + + /// Gets a collection of memory pool entries ordered by entry time. + public IEnumerable EntryTime + { + get { return this.Values.OrderBy(o => o, new CompareTxMemPoolEntryByEntryTime()); } + } + + /// Gets a collection of memory pool entries ordered by mining score. + public IEnumerable MiningScore + { + get { return this.Values.OrderBy(o => o, new CompareTxMemPoolEntryByScore()); } + } + + /// Gets a collection of memory pool entries ordered by ancestor score. + public IEnumerable AncestorScore + { + get { return this.Values.OrderBy(o => o, new CompareTxMemPoolEntryByAncestorFee()); } + } + + /// + /// Adds an entry to the transaction set. + /// + /// Entry to add. + public void Add(TxMempoolEntry entry) + { + this.Add(entry.TransactionHash, entry); + } + + /// + /// Removes an entry from the transaction set. + /// + /// Transaction to remove. + public void Remove(TxMempoolEntry entry) + { + this.Remove(entry.TransactionHash); + } + + /// + /// Salted transaction id hasher for comparing transaction hash codes. + /// + private class SaltedTxidHasher : IEqualityComparer + { + /// + /// Whether two transaction hashes are equal. + /// + /// First hash. + /// Second hash. + /// Whether the hashes are equal. + public bool Equals(uint256 x, uint256 y) + { + return x == y; + } + + /// + /// Gets the hash code for the transaction hash. + /// + /// Transaction hash. + /// + public int GetHashCode(uint256 obj) + { + // todo: need to compare with the c++ implementation + return obj.GetHashCode(); + } + } + + /// + /// Sort an entry by max(score/size of entry's tx, score/size with all descendants). + /// + private class CompareTxMemPoolEntryByDescendantScore : IComparer + { + /// + public int Compare(TxMempoolEntry a, TxMempoolEntry b) + { + bool fUseADescendants = this.UseDescendantScore(a); + bool fUseBDescendants = this.UseDescendantScore(b); + + double aModFee = fUseADescendants ? a.ModFeesWithDescendants.Satoshi : a.ModifiedFee; + double aSize = fUseADescendants ? a.SizeWithDescendants : a.GetTxSize(); + + double bModFee = fUseBDescendants ? b.ModFeesWithDescendants.Satoshi : b.ModifiedFee; + double bSize = fUseBDescendants ? b.SizeWithDescendants : b.GetTxSize(); + + // Avoid division by rewriting (a/b > c/d) as (a*d > c*b). + double f1 = aModFee * bSize; + double f2 = aSize * bModFee; + + if (f1 == f2) + { + if (a.Time >= b.Time) + return -1; + return 1; + } + + if (f1 <= f2) + return -1; + return 1; + } + + /// + /// Calculate which score to use for an entry (avoiding division). + /// + /// Memory pool entry. + /// Whether to use descendant score. + bool UseDescendantScore(TxMempoolEntry a) + { + double f1 = (double)a.ModifiedFee * a.SizeWithDescendants; + double f2 = (double)a.ModFeesWithDescendants.Satoshi * a.GetTxSize(); + return f2 > f1; + } + } + + /// + /// Sort by entry time. + /// + private class CompareTxMemPoolEntryByEntryTime : IComparer + { + /// + public int Compare(TxMempoolEntry a, TxMempoolEntry b) + { + if (a.Time < b.Time) + return -1; + return 1; + } + } + + /// + /// Sort by score of entry ((fee+delta)/size) in descending order. + /// + private class CompareTxMemPoolEntryByScore : IComparer + { + /// + public int Compare(TxMempoolEntry a, TxMempoolEntry b) + { + double f1 = (double)a.ModifiedFee * b.GetTxSize(); + double f2 = (double)b.ModifiedFee * a.GetTxSize(); + if (f1 == f2) + { + if (a.TransactionHash < b.TransactionHash) + return 1; + return -1; + } + if (f1 > f2) + return -1; + return 1; + } + } + + /// + /// Sort by ancestor fee. + /// + private class CompareTxMemPoolEntryByAncestorFee : IComparer + { + /// + public int Compare(TxMempoolEntry a, TxMempoolEntry b) + { + double aFees = a.ModFeesWithAncestors.Satoshi; + double aSize = a.SizeWithAncestors; + + double bFees = b.ModFeesWithAncestors.Satoshi; + double bSize = b.SizeWithAncestors; + + // Avoid division by rewriting (a/b > c/d) as (a*d > c*b). + double f1 = aFees * bSize; + double f2 = aSize * bFees; + + if (f1 == f2) + { + if (a.TransactionHash < b.TransactionHash) + return -1; + return 1; + } + + if (f1 > f2) + return -1; + return 1; + } + } + } + + /// + /// Sort by transaction hash. + /// + public class CompareIteratorByHash : IComparer + { + /// + public int Compare(TxMempoolEntry a, TxMempoolEntry b) + { + return InnerCompare(a, b); + } + + /// + /// Compares transaction hash of two memory pool entries. + /// + /// Memory pool entry. + /// Memory pool entry. + /// Result of comparison function. + public static int InnerCompare(TxMempoolEntry a, TxMempoolEntry b) + { + if (a.TransactionHash == b.TransactionHash) return 0; + if (a.TransactionHash < b.TransactionHash) return -1; + return 1; + } + } + + /// + /// Transaction links to parent and child sets for a given transaction. + /// + public class TxLinks + { + /// Parent memory pool entries + public SetEntries Parents; + + /// Child memory pool entries + public SetEntries Children; + }; + + /// + /// Set of memory pool entries. + /// + public class SetEntries : SortedSet, IEquatable, IEqualityComparer + { + /// + /// Constructs a set of memory pool entries. + /// + 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; + } + + /// + /// Gets the hash code for a memory pool entry. + /// + /// Memory pool entry. + /// Hash code. + public int GetHashCode(TxMempoolEntry obj) + { + return obj?.TransactionHash?.GetHashCode() ?? 0; + } + } + + /// + /// Sorted list of transaction parent/child links. + /// + public class TxlinksMap : SortedList + { + /// + /// Constructs a new transaction links collection. + /// + public TxlinksMap() : base(new CompareIteratorByHash()) + { + } + } + + /// + /// A pair of delta, amount pairs. + /// + public class DeltaPair + { + /// The value of the delta. + public double Delta; + + /// The amount. + public Money Amount; + } + + /// + /// Next transaction pair. + /// + public class NextTxPair + { + /// The outpoint of the transaction. + public OutPoint OutPoint; + + /// The next transaction. + public Transaction Transaction; + } + + #endregion } } diff --git a/Stratis.Bitcoin/Features/MemoryPool/TxMemPoolEntry.cs b/Stratis.Bitcoin/Features/MemoryPool/TxMemPoolEntry.cs index eabbd8a704c..85accba9865 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/TxMemPoolEntry.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/TxMemPoolEntry.cs @@ -5,56 +5,63 @@ namespace Stratis.Bitcoin.Features.MemoryPool { + /// + /// Track the height and time at which transaction was final. + /// + /// + /// Will be set to the blockchain height and median time past + /// values that would be necessary to satisfy all relative locktime + /// raints (BIP68) of this tx given our view of block chain history. + /// public class LockPoints // todo: replace with SequenceLock { - // Will be set to the blockchain height and median time past - // values that would be necessary to satisfy all relative locktime - // raints (BIP68) of this tx given our view of block chain history + /// Block chain height. public int Height; + + /// Median time past values. public long Time; - // As long as the current chain descends from the highest height block - // containing one of the inputs used in the calculation, then the cached - // values are still valid even after a reorg. + + /// + /// The block with the highest height of all the blocks which have sequence locked prevouts. + /// + /// + /// As long as the current chain descends from the highest height block + /// containing one of the inputs used in the calculation, then the cached + /// values are still valid even after a reorg. + /// public ChainedBlock MaxInputBlock; }; + /// + /// A transaction entry in the memory pool. + /// public class TxMempoolEntry { - public Transaction Transaction { get; private set; } - public uint256 TransactionHash { get; private set; } - - public Money Fee { get; private set; } //!< Cached to avoid expensive parent-transaction lookups - public long TxWeight { get; private set; } //!< ... and avoid recomputing tx weight (also used for GetTxSize()) - private long nModSize; //!< ... and modified size for priority - private long nUsageSize; //!< ... and total memory usage - public long Time { get; private set; } //!< Local time when entering the mempool - private double entryPriority; //!< Priority when entering the mempool - public int EntryHeight { get; private set; } //!< Chain height when entering the mempool - public Money InChainInputValue { get; private set; } //!< Sum of all txin values that are already in blockchain - public bool SpendsCoinbase { get; private set; } //!< keep track of transactions that spend a coinbase - public long SigOpCost { get; private set; } //!< Total sigop cost - internal long feeDelta { get; private set; } //!< Used for determining the priority of the transaction for mining in a block - public LockPoints LockPoints { get; private set; } //!< Track the height and time at which tx was final - - // Information about descendants of this transaction that are in the - // mempool; if we remove this transaction we must remove all of these - // descendants as well. if nCountWithDescendants is 0, treat this entry as - // dirty, and nSizeWithDescendants and nModFeesWithDescendants will not be - // correct. - public long CountWithDescendants { get; private set; } //!< number of descendant transactions - public long SizeWithDescendants { get; private set; } //!< ... and size - public Money ModFeesWithDescendants { get; private set; } //!< ... and total fees (all including us) - - // Analogous statistics for ancestor transactions - public long CountWithAncestors { get; private set; } - public long SizeWithAncestors { get; private set; } - public Money ModFeesWithAncestors { get; private set; } - public long SigOpCostWithAncestors { get; private set; } + /// Index in memory pools vTxHashes. + public volatile uint vTxHashesIdx; + /// The modified size of the transaction used for priority. + private long nModSize; + /// The total memory usage. + private long nUsageSize; - + /// Priority when entering the memory pool. + private double entryPriority; + /// + /// Constructs a transaction memory pool entry. + /// + /// Transaction for the entry. + /// Fee for the transaction in the entry in the memory pool. + /// The local time when entering the memory pool. + /// Priority when entering the memory pool. + /// The chain height when entering the mempool. + /// The sum of all txin values that are already in blockchain. + /// Whether the transaction spends a coinbase. + /// The total signature operations cost. + /// Tthe lock points that track the height and time at which tx was final. + /// Proof of work consensus options used to compute transaction weight and modified size. public TxMempoolEntry(Transaction transaction, Money nFee, long nTime, double entryPriority, int entryHeight, Money inChainInputValue, bool spendsCoinbase, @@ -90,13 +97,103 @@ public TxMempoolEntry(Transaction transaction, Money nFee, this.SigOpCostWithAncestors = this.SigOpCost; } + /// + /// Copy constructor for a transaction memory pool entry. + /// + /// Entry to copy. + /// public TxMempoolEntry(TxMempoolEntry other) { throw new NotImplementedException(); } - - // Fast calculation of lower bound of current priority as update - // from entry priority. Only inputs that were originally in-chain will age. + + /// Gets the transaction from the entry in the memory pool. + public Transaction Transaction { get; private set; } + + /// Gets the hash of the transaction in the entry in the memory pool. + public uint256 TransactionHash { get; private set; } + + /// Gets the fee for the transaction in the entry in the memory pool. + /// Cached to avoid expensive parent-transaction lookups. + public Money Fee { get; private set; } + + /// + /// Gets the transaction weight of the transaction in the entry in the memory pool. + /// + /// + /// Cached to avoid recomputing transaction weight. + /// Also used for GetTxSize(). + /// + public long TxWeight { get; private set; } + + /// Gets the local time when entering the memory pool. + public long Time { get; private set; } + + /// Gets the chain height when entering the mempool. + public int EntryHeight { get; private set; } + + /// Gets the sum of all txin values that are already in blockchain. + public Money InChainInputValue { get; private set; } + + /// Gets whether the transaction spends a coinbase. + public bool SpendsCoinbase { get; private set; } + + /// Gets the total signature operations cost. + public long SigOpCost { get; private set; } + + /// Gets the lock points that track the height and time at which tx was final. + public LockPoints LockPoints { get; private set; } + + // Information about descendants of this transaction that are in the + // mempool; if we remove this transaction we must remove all of these + // descendants as well. if is 0, treat this entry as + // dirty, and and will not be + // correct. + + /// Gets the number of descendant transactions. + public long CountWithDescendants { get; private set; } + + /// Gets the size of the transaction with it's decendants. + public long SizeWithDescendants { get; private set; } + + /// Gets the total fees of transaction including it's decendants. + public Money ModFeesWithDescendants { get; private set; } + + // Analogous statistics for ancestor transactions + + /// Gets the number of ancestor transactions. + public long CountWithAncestors { get; private set; } + + /// + /// Gets the size of the transaction with it's ancestors. + public long SizeWithAncestors { get; private set; } + + /// Gets the total fees of the transaction including it's ancestors. + public Money ModFeesWithAncestors { get; private set; } + + /// Gets the total cost of the signature operations for the transaction including it's ancestors. + public long SigOpCostWithAncestors { get; private set; } + + /// Gets the modified fee which is the sum of the transaction and the . + public long ModifiedFee => this.Fee + this.feeDelta; + + /// + /// Gets the difference between transactions fees. + /// + /// + /// Used for determining the priority of the transaction for mining in a block. + /// + internal long feeDelta { get; private set; } + + /// + /// Gets the priority of the memory pool entry given the current chain height. + /// + /// Current chain height. + /// Transaction priority. + /// + /// Fast calculation of lower bound of current priority as update + /// from entry priority. Only inputs that were originally in-chain will age. + /// public double GetPriority(int currentHeight) { double deltaPriority = ((double) (currentHeight - this.EntryHeight)* this.InChainInputValue.Satoshi)/ this.nModSize; @@ -106,19 +203,30 @@ public double GetPriority(int currentHeight) return dResult; } + /// + /// Gets the transaction size. See . + /// + /// The transaction size. public long GetTxSize() { return (long) this.Transaction.GetVirtualSize(); } - public long ModifiedFee => this.Fee + this.feeDelta; - + /// + /// Gets the dynamic memory usage in bytes. + /// + /// The dynamic memory usage value. public long DynamicMemoryUsage() { return this.nUsageSize; } - // Adjusts the descendant state, if this entry is not dirty. + /// + /// Adjusts the descendant state, if this entry is not dirty. + /// + /// Amount to add to the decendant size of this entry. + /// Amount to add to the total of the decendants modify fees for this entry. + /// Count of transactions to add to the total count of descendant transactions for this entry. public void UpdateDescendantState(long modifySize, Money modifyFee, long modifyCount) { this.SizeWithDescendants += modifySize; @@ -128,7 +236,13 @@ public void UpdateDescendantState(long modifySize, Money modifyFee, long modifyC Guard.Assert(this.CountWithDescendants > 0); } - // Adjusts the ancestor state + /// + /// Adjusts the ancestor state. + /// + /// Amount to add to the ancestor size of this entry. + /// Amount to add to the total of the ancestor modify fees for this entry. + /// Count of transactions to add to the total count of ancestor transactions for this entry. + /// Cost to add to the total signature operators cost for all ancestor transactions for this entry. public void UpdateAncestorState(long modifySize, Money modifyFee, long modifyCount, long modifySigOps) { this.SizeWithAncestors += modifySize; @@ -140,8 +254,11 @@ public void UpdateAncestorState(long modifySize, Money modifyFee, long modifyCou Guard.Assert(this.SigOpCostWithAncestors >= 0); } - // Updates the fee delta used for mining priority score, and the - // modified fees with descendants. + /// + /// Updates the fee delta used for mining priority score, and the + /// modified fees with descendants. + /// + /// New fee delta to use. public void UpdateFeeDelta(long newFeeDelta) { this.ModFeesWithDescendants += newFeeDelta - this.feeDelta; @@ -149,14 +266,20 @@ public void UpdateFeeDelta(long newFeeDelta) this.feeDelta = newFeeDelta; } - // Update the LockPoints after a reorg + /// + /// Update the after a reorg. + /// + /// New lockpoints. public void UpdateLockPoints(LockPoints lp) { this.LockPoints = lp; } - public volatile uint vTxHashesIdx; //!< Index in mempool's vTxHashes - + /// + /// String representation of the memory pool entry. + /// Prepends the transaction hash for this entry to the string. + /// + /// The string representation of the memory pool entry. public override string ToString() { return $"{this.TransactionHash} - {base.ToString()}"; From 63b331638c3603d31bb1fa9262728d2424524968 Mon Sep 17 00:00:00 2001 From: Mike Dennis Date: Mon, 21 Aug 2017 18:52:48 -0600 Subject: [PATCH 2/4] Fix code review issues. --- .../Features/MemoryPool/Fee/BlockPolicyEstimator.cs | 3 +++ .../Features/MemoryPool/MempoolValidationContext.cs | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Stratis.Bitcoin/Features/MemoryPool/Fee/BlockPolicyEstimator.cs b/Stratis.Bitcoin/Features/MemoryPool/Fee/BlockPolicyEstimator.cs index 30283a392ba..f3ca0a9bd89 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/Fee/BlockPolicyEstimator.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/Fee/BlockPolicyEstimator.cs @@ -335,6 +335,7 @@ public FeeRate EstimateSmartFee(int confTarget, TxMempool pool, out int answerFo /// Write estimation data to a file. /// /// Stream to write to. + /// TODO: Implement write estimation public void Write(Stream fileout) { } @@ -344,6 +345,7 @@ public void Write(Stream fileout) /// /// Stream to read data from. /// Version number of the file. + /// TODO: Implement read estimation public void Read(Stream filein, int nFileVersion) { } @@ -353,6 +355,7 @@ public void Read(Stream filein, int nFileVersion) /// /// The desired number of confirmations to be included in a block. /// Estiamte of the priority. + /// TODO: Implement priroity estimation public double EstimatePriority(int confTarget) { return -1; diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolValidationContext.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolValidationContext.cs index 5d3293ad4fc..ababa992f3d 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolValidationContext.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolValidationContext.cs @@ -4,7 +4,8 @@ namespace Stratis.Bitcoin.Features.MemoryPool { /// - /// Current state of memory pool validation. + /// A state used when validating a new transaction. + /// A transaction must be validated before being added to the memory pool. /// public class MempoolValidationState { From d9c917c11c2a7267fb9d848f6d82ef54049876a9 Mon Sep 17 00:00:00 2001 From: Mike Dennis Date: Mon, 21 Aug 2017 19:44:26 -0600 Subject: [PATCH 3/4] Remove regions not required. --- .../Features/MemoryPool/MempoolBehavior.cs | 28 ------------------- .../Features/MemoryPool/MempoolFeature.cs | 12 -------- .../Features/MemoryPool/MempoolManager.cs | 16 ----------- .../Features/MemoryPool/MempoolOrphans.cs | 16 ----------- 4 files changed, 72 deletions(-) diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolBehavior.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolBehavior.cs index 38b06d1e75a..548b046d2fc 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolBehavior.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolBehavior.cs @@ -19,8 +19,6 @@ namespace Stratis.Bitcoin.Features.MemoryPool /// public class MempoolBehavior : NodeBehavior { - #region Fields - /// /// Average delay between trickled inventory transmissions in seconds. /// Blocks and whitelisted receivers bypass this, outbound peers get half this delay. @@ -66,10 +64,6 @@ public class MempoolBehavior : NodeBehavior /// private readonly Dictionary filterInventoryKnown; - #endregion - - #region Constructors - /// /// Constructs an instance of memory pool behavior. /// @@ -124,10 +118,6 @@ public MempoolBehavior( { } - #endregion - - #region Properties - /// Time of last memory pool request in unix time. public long LastMempoolReq { get; private set; } @@ -157,10 +147,6 @@ public bool CanSend } } - #endregion - - #region NodeBehavior Overrides - /// protected override void AttachCore() { @@ -179,10 +165,6 @@ public override object Clone() return new MempoolBehavior(this.validator, this.manager, this.orphans, this.connectionManager, this.chainState, this.signals, this.logger); } - #endregion - - #region Message Handlers - /// /// Handler for processing incoming message from node. /// @@ -247,10 +229,6 @@ private Task AttachedNode_MessageReceivedAsync(Node node, IncomingMessage messag return Task.CompletedTask; } - #endregion - - #region Message Processing - /// /// Send the memory pool payload to the attached node. /// Gets the transaction info from the memory pool and sends to the attached node. @@ -445,10 +423,6 @@ private async Task SendAsTxInventory(Node node, IEnumerable trxList) } } - #endregion - - #region Operations - /// /// Relays a transaction to the connected nodes. /// @@ -516,7 +490,5 @@ public async Task SendTrickle() if (sends.Any()) await this.SendAsTxInventory(this.AttachedNode, sends); } - - #endregion } } diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolFeature.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolFeature.cs index 6facfd84593..e4149969e62 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolFeature.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolFeature.cs @@ -15,8 +15,6 @@ namespace Stratis.Bitcoin.Features.MemoryPool /// public class MempoolFeature : FullNodeFeature, IFeatureStats { - #region Fields - /// Node notifications available to subscribe to. private readonly Signals.Signals signals; @@ -35,10 +33,6 @@ public class MempoolFeature : FullNodeFeature, IFeatureStats /// Logger for the memory pool component. private readonly ILogger mempoolLogger; - #endregion - - #region Constructors - /// /// Constructs a memory pool feature. /// @@ -74,10 +68,6 @@ public void AddFeatureStats(StringBuilder benchLogs) } } - #endregion - - #region FullNodeFeature Overrides - /// public override void Start() { @@ -105,8 +95,6 @@ public override void Stop() } } } - - #endregion } /// diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolManager.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolManager.cs index d1f625366e6..1b74a79f5b1 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolManager.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolManager.cs @@ -22,8 +22,6 @@ public class MempoolAsyncLock : AsyncLock /// public class MempoolManager { - #region Fields - /// Memory pool persistence methods for loading and saving from storage. private IMempoolPersistence mempoolPersistence; @@ -33,10 +31,6 @@ public class MempoolManager /// Transaction memory pool for managing transactions in the memory pool. private readonly TxMempool memPool; - #endregion - - #region Constructors - /// /// Constructs an instance of a memory pool manager object. /// @@ -68,10 +62,6 @@ public MempoolManager( this.mempoolLogger = loggerFactory.CreateLogger(this.GetType().FullName); } - #endregion - - #region Properties - /// Lock for memory pool access. public MempoolAsyncLock MempoolLock { get; } @@ -90,10 +80,6 @@ public MempoolManager( /// Access to memory pool validator performance counter. public MempoolPerformanceCounter PerformanceCounter => this.Validator.PerformanceCounter; - #endregion - - #region Operations - /// /// Gets the memory pool transactions. /// @@ -249,7 +235,5 @@ public Task RemoveForBlock(Block block, int blockHeight) this.Validator.PerformanceCounter.SetMempoolDynamicSize(this.memPool.DynamicMemoryUsage()); }); } - - #endregion } } diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolOrphans.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolOrphans.cs index cd6aaeffe5b..d4b1a746013 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolOrphans.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolOrphans.cs @@ -17,8 +17,6 @@ namespace Stratis.Bitcoin.Features.MemoryPool /// public class MempoolOrphans { - #region Fields - /// Expiration time for orphan transactions in seconds. private const long OrphanTxExpireTime = 20 * 60; @@ -70,10 +68,6 @@ public class MempoolOrphans /// Location on chain when rejects are validated. private uint256 hashRecentRejectsChainTip; - #endregion - - #region Constructors - /// /// Constructs a memory pool orphan manager object. /// @@ -116,20 +110,12 @@ public MempoolOrphans( this.mempoolLogger = loggerFactory.CreateLogger(this.GetType().FullName); } - #endregion - - #region Properties - /// A lock for managing asynchronous access to memory pool. public MempoolAsyncLock MempoolLock { get; } /// Memory pool validator for validating transactions. public IMempoolValidator Validator { get; } // public for testing - #endregion - - #region Operations - /// /// Object representing an orphan transaction information. /// When modifying, adapt the copy of this definition in tests/DoS_tests. @@ -450,7 +436,5 @@ public Task EraseOrphansFor(ulong peer) //return true; }).Unwrap(); } - - #endregion } } From 26f26bf291b59bfc904fbf1746fb57943d436959 Mon Sep 17 00:00:00 2001 From: Mike Dennis Date: Wed, 23 Aug 2017 12:04:56 -0600 Subject: [PATCH 4/4] Fix code review issues. Fix stylecop warnings. --- .../MemoryPool/Fee/BlockPolicyEstimator.cs | 11 ++- .../Features/MemoryPool/Fee/TxConfirmStats.cs | 8 +- .../Features/MemoryPool/MempoolBehavior.cs | 22 +++--- .../Features/MemoryPool/MempoolErrors.cs | 1 - .../Features/MemoryPool/MempoolFeature.cs | 4 +- .../Features/MemoryPool/MempoolManager.cs | 6 +- .../Features/MemoryPool/MempoolOrphans.cs | 6 +- .../Features/MemoryPool/MempoolPersistence.cs | 10 +-- .../Features/MemoryPool/MempoolValidator.cs | 74 +++++++++---------- .../Features/MemoryPool/TxMemPool.cs | 15 ++-- 10 files changed, 76 insertions(+), 81 deletions(-) diff --git a/Stratis.Bitcoin/Features/MemoryPool/Fee/BlockPolicyEstimator.cs b/Stratis.Bitcoin/Features/MemoryPool/Fee/BlockPolicyEstimator.cs index f3ca0a9bd89..cb3b0bb93ce 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/Fee/BlockPolicyEstimator.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/Fee/BlockPolicyEstimator.cs @@ -177,8 +177,8 @@ public void ProcessBlock(int nBlockHeight, List entries) this.feeStats.UpdateMovingAverages(); // TODO: this makes too much noise right now, put it back when logging is can be switched on by categories (and also consider disabling during IBD) - //Logging.Logs.EstimateFee.LogInformation( - // $"Blockpolicy after updating estimates for {countedTxs} of {entries.Count} txs in block, since last block {trackedTxs} of {trackedTxs + untrackedTxs} tracked, new mempool map size {mapMemPoolTxs.Count}"); + // Logging.Logs.EstimateFee.LogInformation( + // $"Blockpolicy after updating estimates for {countedTxs} of {entries.Count} txs in block, since last block {trackedTxs} of {trackedTxs + untrackedTxs} tracked, new mempool map size {mapMemPoolTxs.Count}"); this.trackedTxs = 0; this.untrackedTxs = 0; @@ -253,7 +253,7 @@ public void ProcessTransaction(TxMempoolEntry entry, bool validFeeEstimate) /// Remove a transaction from the mempool tracking stats. /// /// Transaction hash. - /// Whether successfully removed transaction. + /// Whether the transaction was successfully removed. /// /// This function is called from TxMemPool.RemoveUnchecked to ensure /// txs removed from the mempool for any reason are no longer @@ -354,8 +354,8 @@ public void Read(Stream filein, int nFileVersion) /// Return an estimate of the priority. /// /// The desired number of confirmations to be included in a block. - /// Estiamte of the priority. - /// TODO: Implement priroity estimation + /// Estimate of the priority. + /// TODO: Implement priority estimation public double EstimatePriority(int confTarget) { return -1; @@ -372,7 +372,6 @@ public double EstimateSmartPriority(int confTarget, TxMempool pool, out int answ { answerFoundAtTarget = confTarget; - // If mempool is limiting txs, no priority txs are allowed Money minPoolFee = pool.GetMinFee(this.nodeArgs.Mempool.MaxMempool * 1000000).FeePerK; if (minPoolFee > 0) diff --git a/Stratis.Bitcoin/Features/MemoryPool/Fee/TxConfirmStats.cs b/Stratis.Bitcoin/Features/MemoryPool/Fee/TxConfirmStats.cs index cc1b7b99f26..1dd2741b5c0 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/Fee/TxConfirmStats.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/Fee/TxConfirmStats.cs @@ -11,11 +11,11 @@ namespace Stratis.Bitcoin.Features.MemoryPool.Fee /// public class TxConfirmStats { - /// Logger for logging messages for this object. + /// Instance logger for logging messages. private readonly ILogger logger; /// - /// Sum the total feerate of all tx's in each bucket. + /// Moving average of total fee rate of all transactions in each bucket. /// /// /// Track the historical moving average of this total over blocks. @@ -77,7 +77,7 @@ public class TxConfirmStats /// /// Constructs an instance of the transaction confirmation stats object. /// - /// Logger to use for message logging. + /// Instance logger to use for message logging. public TxConfirmStats(ILogger logger) { this.logger = logger; @@ -206,7 +206,7 @@ public void RemoveTx(int entryHeight, int nBestSeenHeight, int bucketIndex) /// /// Update our estimates by decaying our historical moving average and updating - // with the data gathered from the current block. + /// with the data gathered from the current block. /// public void UpdateMovingAverages() { diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolBehavior.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolBehavior.cs index 548b046d2fc..23c46bd6872 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolBehavior.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolBehavior.cs @@ -49,7 +49,7 @@ public class MempoolBehavior : NodeBehavior /// Node notifications available to subscribe to. private readonly Signals.Signals signals; - /// Logger for the memory pool component. + /// Instance logger for the memory pool component. private readonly ILogger logger; /// @@ -73,7 +73,7 @@ public class MempoolBehavior : NodeBehavior /// Connection manager for managing node connections. /// Current block chain state. /// Node notifications available to subscribe to. - /// Memory pool behavior logger. + /// Instance logger for memory pool behavior. public MempoolBehavior( IMempoolValidator validator, MempoolManager manager, @@ -244,16 +244,16 @@ private async Task SendMempoolPayload(Node node, MempoolPayload message) //if (!(pfrom->GetLocalServices() & NODE_BLOOM) && !pfrom->fWhitelisted) //{ - // LogPrint("net", "mempool request with bloom filters disabled, disconnect peer=%d\n", pfrom->GetId()); - // pfrom->fDisconnect = true; - // return true; + // LogPrint("net", "mempool request with bloom filters disabled, disconnect peer=%d\n", pfrom->GetId()); + // pfrom->fDisconnect = true; + // return true; //} //if (connman.OutboundTargetReached(false) && !pfrom->fWhitelisted) //{ - // LogPrint("net", "mempool request with bandwidth limit reached, disconnect peer=%d\n", pfrom->GetId()); - // pfrom->fDisconnect = true; - // return true; + // LogPrint("net", "mempool request with bandwidth limit reached, disconnect peer=%d\n", pfrom->GetId()); + // pfrom->fDisconnect = true; + // return true; //} List vtxinfo = await this.manager.InfoAllAsync(); @@ -261,8 +261,8 @@ private async Task SendMempoolPayload(Node node, MempoolPayload message) // TODO: implement minFeeFilter //{ - // LOCK(pto->cs_feeFilter); - // filterrate = pto->minFeeFilter; + // LOCK(pto->cs_feeFilter); + // filterrate = pto->minFeeFilter; //} List sends = await this.manager.MempoolLock.WriteAsync(() => @@ -480,7 +480,7 @@ public async Task SendTrickle() continue; //if (filterrate && txinfo.feeRate.GetFeePerK() < filterrate) // TODO:filterrate //{ - // continue; + // continue; //} ret.Add(hash); } diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolErrors.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolErrors.cs index cec8f6593f5..8b8093c0f81 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolErrors.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolErrors.cs @@ -123,7 +123,6 @@ public static class MempoolErrors #endregion - /// 'coinbase' error returns a reject code. public static MempoolError Coinbase = new MempoolError(RejectInvalid, "coinbase"); diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolFeature.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolFeature.cs index e4149969e62..cf7afaab4e2 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolFeature.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolFeature.cs @@ -30,7 +30,7 @@ public class MempoolFeature : FullNodeFeature, IFeatureStats /// Memory pool manager for managing external access to memory pool. private readonly MempoolManager mempoolManager; - /// Logger for the memory pool component. + /// Instance logger for the memory pool component. private readonly ILogger mempoolLogger; /// @@ -41,7 +41,7 @@ public class MempoolFeature : FullNodeFeature, IFeatureStats /// Observes block signal notifications from signals. /// Memory pool node behavior for managing attached node messages. /// Memory pool manager for managing external access to memory pool. - /// Logger factory for creating loggers. + /// Logger factory for creating instance logger. public MempoolFeature( IConnectionManager connectionManager, Signals.Signals signals, diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolManager.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolManager.cs index 1b74a79f5b1..259e74d3c49 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolManager.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolManager.cs @@ -25,7 +25,7 @@ public class MempoolManager /// Memory pool persistence methods for loading and saving from storage. private IMempoolPersistence mempoolPersistence; - /// Memory pool manager logger. + /// Instance logger for memory pool manager. private readonly ILogger mempoolLogger; /// Transaction memory pool for managing transactions in the memory pool. @@ -41,7 +41,7 @@ public class MempoolManager /// Date and time information provider. /// Settings from the node. /// Memory pool persistence methods for loading and saving from storage. - /// Logger factory for creating logger. + /// Logger factory for creating instance logger. public MempoolManager( MempoolAsyncLock mempoolLock, TxMempool memPool, @@ -225,7 +225,7 @@ public Task MempoolDynamicMemoryUsage() public Task RemoveForBlock(Block block, int blockHeight) { //if (this.IsInitialBlockDownload) - // return Task.CompletedTask; + // return Task.CompletedTask; return this.MempoolLock.WriteAsync(() => { diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolOrphans.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolOrphans.cs index d4b1a746013..c3cbdeeb4af 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolOrphans.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolOrphans.cs @@ -47,7 +47,7 @@ public class MempoolOrphans /// Settings from the node. private readonly NodeSettings nodeArgs; - /// Logger for the memory pool. + /// Instance logger for the memory pool. private readonly ILogger mempoolLogger; /// Dictionary of orphan transactions keyed by transaction hash. @@ -80,7 +80,7 @@ public class MempoolOrphans /// Coin view of the memory pool. /// Date and time information provider. /// Settings from the node. - /// Factory for creating logger for this object. + /// Factory for creating instance logger for this object. public MempoolOrphans( MempoolAsyncLock mempoolLock, TxMempool memPool, @@ -274,7 +274,7 @@ public async Task ProcessesOrphansMissingInputs(Node from, Transaction tx) //CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash); //behavior.AttachedNode.Behaviors.Find() pfrom->AddInventoryKnown(_inv); //if (!await this.AlreadyHave(txin.PrevOut.Hash)) - // from. pfrom->AskFor(_inv); + // from. pfrom->AskFor(_inv); } var ret = await this.AddOrphanTx(from.PeerVersion.Nonce, tx); diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolPersistence.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolPersistence.cs index b3cb3e45d0f..c119fc68214 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolPersistence.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolPersistence.cs @@ -139,19 +139,19 @@ internal class MempoolPersistence : IMempoolPersistence public const ulong MempoolDumpVersion = 0; /// The default filename used for memory pool persistence. - public const string defaultFilename = "mempool.dat"; + public const string DefaultFilename = "mempool.dat"; /// Data directory to save persisted memory pool to. private readonly string dataDir; - /// Logger for the memory pool persistence object. + /// Instance logger for the memory pool persistence object. private readonly ILogger mempoolLogger; /// /// Constructs an instance of an object for persisting memory pool transactions. /// /// Node settings used for getting the data directory. - /// Logger factory for creating logger for this object. + /// Logger factory for creating instance logger. public MempoolPersistence(NodeSettings settings, ILoggerFactory loggerFactory) { this.dataDir = settings?.DataDir; @@ -161,7 +161,7 @@ public MempoolPersistence(NodeSettings settings, ILoggerFactory loggerFactory) /// public MemPoolSaveResult Save(TxMempool memPool, string fileName = null) { - fileName = fileName ?? defaultFilename; + fileName = fileName ?? DefaultFilename; IEnumerable toSave = memPool.MapTx.Values.ToArray().Select(tx => MempoolPersistenceEntry.FromTxMempoolEntry(tx)); return this.Save(toSave, fileName); } @@ -230,7 +230,7 @@ internal void DumpToStream(IEnumerable toSave, Stream s /// public IEnumerable Load(string fileName = null) { - fileName = fileName ?? defaultFilename; + fileName = fileName ?? DefaultFilename; Guard.NotEmpty(this.dataDir, nameof(this.dataDir)); Guard.NotEmpty(fileName, nameof(fileName)); diff --git a/Stratis.Bitcoin/Features/MemoryPool/MempoolValidator.cs b/Stratis.Bitcoin/Features/MemoryPool/MempoolValidator.cs index 572316c7377..4a2bf762917 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/MempoolValidator.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/MempoolValidator.cs @@ -138,7 +138,7 @@ public class MempoolValidator : IMempoolValidator /// Proof of work consensus validator. private readonly PowConsensusValidator consensusValidator; - /// Logger for memory pool validator. + /// Instance logger for memory pool validator. private readonly ILogger logger; /// Minimum fee rate for a relay transaction. @@ -149,8 +149,8 @@ public class MempoolValidator : IMempoolValidator //private class FreeLimiterSection //{ - // public double FreeCount; - // public long LastTime; + // public double FreeCount; + // public long LastTime; //} /// @@ -163,7 +163,7 @@ public class MempoolValidator : IMempoolValidator /// Settings from the node. /// Chain of block headers. /// Coin view of the memory pool. - /// Logger factory for creating logger. + /// Logger factory for creating instance logger. public MempoolValidator( TxMempool memPool, MempoolAsyncLock mempoolLock, @@ -630,7 +630,7 @@ private void CheckStandardTransaction(MempoolValidationContext context, bool wit // TODO: fIsBareMultisigStd //else if ((script == PayToMultiSigTemplate.Instance)) (!fIsBareMultisigStd)) //{ - // context.State.Fail(new MempoolError(MempoolErrors.RejectNonstandard, "bare-multisig")).Throw(); + // context.State.Fail(new MempoolError(MempoolErrors.RejectNonstandard, "bare-multisig")).Throw(); //} else if (txout.IsDust(MinRelayTxFee)) { @@ -740,7 +740,7 @@ private void CreateMempoolEntry(MempoolValidationContext context, long acceptTim // TODO: Implement Witness Code //// Check for non-standard witness in P2WSH //if (tx.HasWitness && requireStandard && !IsWitnessStandard(Trx, context.View)) - // state.Invalid(new MempoolError(MempoolErrors.REJECT_NONSTANDARD, "bad-witness-nonstandard")).Throw(); + // state.Invalid(new MempoolError(MempoolErrors.REJECT_NONSTANDARD, "bad-witness-nonstandard")).Throw(); context.SigOpsCost = this.consensusValidator.GetTransactionSigOpCost(context.Transaction, context.View.Set, new ConsensusFlags { ScriptFlags = ScriptVerify.Standard }); @@ -791,7 +791,7 @@ private void CheckReplacment(MempoolValidationContext context) { FeeRate newFeeRate = new FeeRate(context.ModifiedFees, context.EntrySize); List setConflictsParents = new List(); - const int maxDescendantsToVisit = 100; + const int MaxDescendantsToVisit = 100; TxMempool.SetEntries setIterConflicting = new TxMempool.SetEntries(); foreach (uint256 hashConflicting in context.SetConflicts) { @@ -835,7 +835,7 @@ private void CheckReplacment(MempoolValidationContext context) // This potentially overestimates the number of actual descendants // but we just want to be conservative to avoid doing too much // work. - if (context.ConflictingCount <= maxDescendantsToVisit) + if (context.ConflictingCount <= MaxDescendantsToVisit) { // If not too many to replace, then calculate the set of // transactions that would have to be evicted @@ -852,7 +852,7 @@ private void CheckReplacment(MempoolValidationContext context) else { context.State.Fail(MempoolErrors.TooManyPotentialReplacements, - $"rejecting replacement {context.TransactionHash}; too many potential replacements ({context.ConflictingCount} > {maxDescendantsToVisit})").Throw(); + $"rejecting replacement {context.TransactionHash}; too many potential replacements ({context.ConflictingCount} > {MaxDescendantsToVisit})").Throw(); } for (int j = 0; j < context.Transaction.Inputs.Count; j++) @@ -908,19 +908,17 @@ private void CheckRateLimit(MempoolValidationContext context, bool limitFree) // be annoying or make others' transactions take longer to confirm. //if (limitFree && context.ModifiedFees < MinRelayTxFee.GetFee(context.EntrySize)) //{ - // var nNow = this.dateTimeProvider.GetTime(); - - // // Use an exponentially decaying ~10-minute window: - // this.freeLimiter.FreeCount *= Math.Pow(1.0 - 1.0 / 600.0, (double)(nNow - this.freeLimiter.LastTime)); - // this.freeLimiter.LastTime = nNow; - // // -limitfreerelay unit is thousand-bytes-per-minute - // // At default rate it would take over a month to fill 1GB - // if (this.freeLimiter.FreeCount + context.EntrySize >= this.nodeArgs.Mempool.LimitFreeRelay * 10 * 1000) - // context.State.Fail(new MempoolError(MempoolErrors.RejectInsufficientfee, "rate limited free transaction")).Throw(); - - // this.logger.LogInformation( - // $"Rate limit dFreeCount: {this.freeLimiter.FreeCount} => {this.freeLimiter.FreeCount + context.EntrySize}"); - // this.freeLimiter.FreeCount += context.EntrySize; + // var nNow = this.dateTimeProvider.GetTime(); + // // Use an exponentially decaying ~10-minute window: + // this.freeLimiter.FreeCount *= Math.Pow(1.0 - 1.0 / 600.0, (double)(nNow - this.freeLimiter.LastTime)); + // this.freeLimiter.LastTime = nNow; + // // -limitfreerelay unit is thousand-bytes-per-minute + // // At default rate it would take over a month to fill 1GB + // if (this.freeLimiter.FreeCount + context.EntrySize >= this.nodeArgs.Mempool.LimitFreeRelay * 10 * 1000) + // context.State.Fail(new MempoolError(MempoolErrors.RejectInsufficientfee, "rate limited free transaction")).Throw(); + // this.logger.LogInformation( + // $"Rate limit dFreeCount: {this.freeLimiter.FreeCount} => {this.freeLimiter.FreeCount + context.EntrySize}"); + // this.freeLimiter.FreeCount += context.EntrySize; //} } @@ -977,7 +975,7 @@ private void LimitMempoolSize(long limit, long age) this.memPool.TrimToSize(limit, vNoSpendsRemaining); //foreach(var removed in vNoSpendsRemaining) - // pcoinsTip->Uncache(removed); + // pcoinsTip->Uncache(removed); } /// @@ -990,11 +988,11 @@ private bool IsCurrentForFeeEstimation() // TODO: implement method (find a way to know if in IBD) //if (IsInitialBlockDownload()) - // return false; + // return false; if (this.chain.Tip.Header.BlockTime.ToUnixTimeMilliseconds() < (this.dateTimeProvider.GetTime() - MaxFeeEstimationTipAge)) return false; //if (chainActive.Height() < pindexBestHeader->nHeight - 1) - // return false; + // return false; return true; } @@ -1022,10 +1020,10 @@ private void CheckAllInputs(MempoolValidationContext context) //// need to turn both off, and compare against just turning off CLEANSTACK //// to see if the failure is specifically due to witness validation. //if (!tx.HasWitness() && CheckInputs(Trx, state, view, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, txdata) && - // !CheckInputs(tx, state, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, txdata)) + // !CheckInputs(tx, state, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, txdata)) //{ - // // Only the witness is missing, so the transaction itself may be fine. - // state.SetCorruptionPossible(); + // // Only the witness is missing, so the transaction itself may be fine. + // state.SetCorruptionPossible(); //} context.State.Fail(new MempoolError()).Throw(); @@ -1091,16 +1089,16 @@ private bool CheckInputs(MempoolValidationContext context, ScriptVerify scriptVe //if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) //{ - // // Check whether the failure was caused by a - // // non-mandatory script verification check, such as - // // non-standard DER encodings or non-null dummy - // // arguments; if so, don't trigger DoS protection to - // // avoid splitting the network between upgraded and - // // non-upgraded nodes. - // CScriptCheck check2(*coins, tx, i, - // flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore, &txdata); - // if (check2()) - // return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); + // // Check whether the failure was caused by a + // // non-mandatory script verification check, such as + // // non-standard DER encodings or non-null dummy + // // arguments; if so, don't trigger DoS protection to + // // avoid splitting the network between upgraded and + // // non-upgraded nodes. + // CScriptCheck check2(*coins, tx, i, + // flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore, &txdata); + // if (check2()) + // return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); //} //// Failures of other flags indicate a transaction that is //// invalid in new blocks, e.g. a invalid P2SH. We DoS ban diff --git a/Stratis.Bitcoin/Features/MemoryPool/TxMemPool.cs b/Stratis.Bitcoin/Features/MemoryPool/TxMemPool.cs index 64da56b05d3..a3199473f0f 100644 --- a/Stratis.Bitcoin/Features/MemoryPool/TxMemPool.cs +++ b/Stratis.Bitcoin/Features/MemoryPool/TxMemPool.cs @@ -28,7 +28,6 @@ public class TxMempoolInfo public long FeeDelta { get; set; } }; - /// /// Memory pool of pending transactions. /// @@ -170,7 +169,7 @@ public class TxMempool /// All tx witness hashes/entries in mapTx, in random order. private Dictionary vTxHashes; - /// Logger for the memory pool. + /// Instance logger for the memory pool. private readonly ILogger logger; /// @@ -179,7 +178,7 @@ public class TxMempool /// The fee rate for a minimum reasonable relay fee. /// The data and time provider for accessing current date and time. /// The block policy estimator object. - /// Factory for creating loggers. + /// Factory for creating instance logger. public TxMempool(FeeRate minReasonableRelayFee, IDateTimeProvider dateTimeProvider, BlockPolicyEstimator blockPolicyEstimator, ILoggerFactory loggerFactory) { this.MapTx = new IndexedTransactionSet(); @@ -433,7 +432,7 @@ public bool AddUnchecked(uint256 hash, TxMempoolEntry entry, SetEntries setAnces /// /// Memory pool entry. /// Transaction ancestors. - void UpdateEntryForAncestors(TxMempoolEntry it, SetEntries setAncestors) + private void UpdateEntryForAncestors(TxMempoolEntry it, SetEntries setAncestors) { long updateCount = setAncestors.Count; long updateSize = 0; @@ -777,10 +776,10 @@ private void RemoveUnchecked(TxMempoolEntry it) //vTxHashes[it].second->vTxHashesIdx = it->vTxHashesIdx; //vTxHashes.pop_back(); //if (vTxHashes.size() * 2 < vTxHashes.capacity()) - // vTxHashes.shrink_to_fit(); + // vTxHashes.shrink_to_fit(); } //else - // vTxHashes.clear(); + // vTxHashes.clear(); this.totalTxSize -= it.GetTxSize(); this.cachedInnerUsage -= it.DynamicMemoryUsage(); @@ -1098,7 +1097,7 @@ public FeeRate GetMinFee(long sizelimit) } } - double ret = Math.Max(this.rollingMinimumFeeRate, this.minReasonableRelayFee.FeePerK.Satoshi); + double ret = Math.Max(this.rollingMinimumFeeRate, this.minReasonableRelayFee.FeePerK.Satoshi); return new FeeRate(new Money((int)ret)); } @@ -1298,7 +1297,7 @@ public int Compare(TxMempoolEntry a, TxMempoolEntry b) /// /// Memory pool entry. /// Whether to use descendant score. - bool UseDescendantScore(TxMempoolEntry a) + private bool UseDescendantScore(TxMempoolEntry a) { double f1 = (double)a.ModifiedFee * a.SizeWithDescendants; double f2 = (double)a.ModFeesWithDescendants.Satoshi * a.GetTxSize();