/// <summary> /// Overrides the <see cref="AddToBlock(TxMempoolEntry)"/> behaviour of <see cref="BlockDefinition"/>. /// <para> /// Determine whether or not the mempool entry contains smart contract execution /// code. If not, then add to the block as per normal. Else extract and deserialize /// the smart contract code from the TxOut's ScriptPubKey. /// </para> /// </summary> /// <param name="mempoolEntry">The mempool entry containing the transactions to include.</param> public override void AddToBlock(TxMempoolEntry mempoolEntry) { TxOut smartContractTxOut = mempoolEntry.Transaction.TryGetSmartContractTxOut(); if (smartContractTxOut == null) { this.logger.LogDebug("Transaction does not contain smart contract information."); base.AddTransactionToBlock(mempoolEntry.Transaction); base.UpdateBlockStatistics(mempoolEntry); base.UpdateTotalFees(mempoolEntry.Fee); } else { this.logger.LogDebug("Transaction contains smart contract information."); if (this.blockGasConsumed >= GasPerBlockLimit) { this.logger.LogDebug("The gas limit for this block has been reached."); return; } IContractExecutionResult result = this.ExecuteSmartContract(mempoolEntry); // If including this transaction would put us over the block gas limit, then don't include it // and roll back all of the execution we did. if (this.blockGasConsumed > GasPerBlockLimit) { // Remove the last receipt. this.receipts.RemoveAt(this.receipts.Count - 1); // Set our state to where it was before this execution. uint256 lastState = this.receipts.Last().PostState; this.stateSnapshot.SyncToRoot(lastState.ToBytes()); return; } this.AddTransactionToBlock(mempoolEntry.Transaction); this.UpdateBlockStatistics(mempoolEntry); this.UpdateTotalFees(result.Fee); // If there are refunds, add them to the block. if (result.Refund != null) { this.refundOutputs.Add(result.Refund); this.logger.LogDebug("refund was added with value {0}.", result.Refund.Value); } // Add internal transactions made during execution. if (result.InternalTransaction != null) { this.AddTransactionToBlock(result.InternalTransaction); this.logger.LogDebug("Internal {0}:{1} was added.", nameof(result.InternalTransaction), result.InternalTransaction.GetHash()); } } }
/// <inheritdoc/> public override void AddToBlock(TxMempoolEntry mempoolEntry) { this.logger.LogTrace("({0}.{1}:'{2}', {3}:{4})", nameof(mempoolEntry), nameof(mempoolEntry.TransactionHash), mempoolEntry.TransactionHash, nameof(mempoolEntry.ModifiedFee), mempoolEntry.ModifiedFee); this.AddTransactionToBlock(mempoolEntry.Transaction); this.UpdateBlockStatistics(mempoolEntry); this.UpdateTotalFees(mempoolEntry.Fee); this.logger.LogTrace("(-)"); }
/// <summary> /// Updates block statistics from the given mempool entry. /// <para>The block's <see cref="BlockSigOpsCost"/> and <see cref="BlockWeight"/> values are adjusted. /// </para> /// </summary> protected void UpdateBlockStatistics(TxMempoolEntry mempoolEntry) { this.logger.LogTrace("({0}.{1}:{2}, {3}.{4}:{5})", nameof(mempoolEntry), nameof(mempoolEntry.SigOpCost), mempoolEntry.SigOpCost, nameof(mempoolEntry), nameof(mempoolEntry.TxWeight), mempoolEntry.TxWeight); this.BlockSigOpsCost += mempoolEntry.SigOpCost; this.BlockWeight += mempoolEntry.TxWeight; this.inBlock.Add(mempoolEntry); this.logger.LogTrace("(-){0}:{1}, {2}:{3}", nameof(this.BlockWeight), this.BlockWeight, nameof(this.BlockSigOpsCost), this.BlockSigOpsCost); }
private IEnumerable <MempoolPersistenceEntry> CreateTestEntries(int numTx) { var entries = new List <TxMempoolEntry>(numTx); for (int i = 0; i < numTx; i++) { var amountSat = 10 * i; Transaction tx = this.MakeRandomTx(amountSat); var entry = new TxMempoolEntry(tx, Money.FromUnit(0.1m, MoneyUnit.MilliBTC), DateTimeOffset.Now.ToUnixTimeSeconds(), i * 100, i, amountSat, i == 0, 10, null, new PowConsensusOptions()); entry.UpdateFeeDelta(numTx - i); entries.Add(entry); } return(entries.Select(entry => MempoolPersistenceEntry.FromTxMempoolEntry(entry))); }
/// <summary> /// Test if a new package would "fit" in the block. /// </summary> protected virtual bool TestPackage(TxMempoolEntry entry, long packageSize, long packageSigOpsCost) { // TODO: Switch to weight-based accounting for packages instead of vsize-based accounting. if (this.BlockWeight + this.Network.Consensus.Options.WitnessScaleFactor * packageSize >= this.Options.BlockMaxWeight) { return(false); } if (this.BlockSigOpsCost + packageSigOpsCost >= this.Network.Consensus.Options.MaxBlockSigopsCost) { return(false); } return(true); }
/// <inheritdoc/> protected override bool TestPackage(TxMempoolEntry entry, long packageSize, long packageSigOpsCost) { if (this.futureDriftRule == null) { this.futureDriftRule = this.ConsensusManager.ConsensusRules.GetRule <PosFutureDriftRule>(); } long adjustedTime = this.DateTimeProvider.GetAdjustedTimeAsUnixTimestamp(); if (entry.Transaction.Time > adjustedTime + this.futureDriftRule.GetFutureDrift(adjustedTime)) { return(false); } return(base.TestPackage(entry, packageSize, packageSigOpsCost)); }
/// <summary> /// Execute the contract and add all relevant fees and refunds to the block. /// </summary> /// <remarks>TODO: At some point we need to change height to a ulong.</remarks> private ISmartContractExecutionResult ExecuteSmartContract(TxMempoolEntry mempoolEntry) { this.logger.LogTrace("()"); GetSenderUtil.GetSenderResult getSenderResult = GetSenderUtil.GetSender(mempoolEntry.Transaction, this.coinView, this.inBlock.Select(x => x.Transaction).ToList()); if (!getSenderResult.Success) { throw new ConsensusErrorException(new ConsensusError("sc-block-assembler-addcontracttoblock", getSenderResult.Error)); } ISmartContractTransactionContext transactionContext = new SmartContractTransactionContext((ulong)this.height, this.coinbaseAddress, mempoolEntry.Fee, getSenderResult.Sender, mempoolEntry.Transaction); ISmartContractExecutor executor = this.executorFactory.CreateExecutor(this.stateSnapshot, transactionContext); ISmartContractExecutionResult result = executor.Execute(transactionContext); this.logger.LogTrace("(-)"); return(result); }
/// <summary> /// Test if a new package would "fit" in the block. /// </summary> protected virtual bool TestPackage(TxMempoolEntry entry, long packageSize, long packageSigOpsCost) { // TODO: Switch to weight-based accounting for packages instead of vsize-based accounting. if (this.BlockWeight + this.Network.Consensus.Options.WitnessScaleFactor * packageSize >= this.Options.BlockMaxWeight) { this.logger.LogTrace("(-)[MAX_WEIGHT_REACHED]:false"); return(false); } if (this.BlockSigOpsCost + packageSigOpsCost >= this.Network.Consensus.Options.MaxBlockSigopsCost) { this.logger.LogTrace("(-)[MAX_SIGOPS_REACHED]:false"); return(false); } this.logger.LogTrace("(-):true"); return(true); }
/// <summary> /// Adds a transaction to the block from the given mempool entry. /// </summary> private void AddToBlock(TxMempoolEntry mempoolEntry) { this.logger.LogTrace("({0}.{1}:'{2}', {3}:{4}, txSize:{5})", nameof(mempoolEntry), nameof(mempoolEntry.TransactionHash), mempoolEntry.TransactionHash, nameof(mempoolEntry.ModifiedFee), mempoolEntry.ModifiedFee, mempoolEntry.GetTxSize()); this.block.AddTransaction(mempoolEntry.Transaction); if (this.NeedSizeAccounting) { this.BlockSize += mempoolEntry.Transaction.GetSerializedSize(); } this.BlockWeight += mempoolEntry.TxWeight; this.BlockTx++; this.BlockSigOpsCost += mempoolEntry.SigOpCost; this.fees += mempoolEntry.Fee; this.inBlock.Add(mempoolEntry); this.logger.LogTrace("(-)"); }
/// <summary> /// Overrides the <see cref="AddToBlock(TxMempoolEntry)"/> behaviour of <see cref="BlockDefinitionProofOfWork"/>. /// <para> /// Determine whether or not the mempool entry contains smart contract execution /// code. If not, then add to the block as per normal. Else extract and deserialize /// the smart contract code from the TxOut's ScriptPubKey. /// </para> /// </summary> public override void AddToBlock(TxMempoolEntry mempoolEntry) { TxOut smartContractTxOut = mempoolEntry.Transaction.TryGetSmartContractTxOut(); if (smartContractTxOut == null) { this.logger.LogTrace("Transaction does not contain smart contract information."); base.AddTransactionToBlock(mempoolEntry.Transaction); base.UpdateBlockStatistics(mempoolEntry); base.UpdateTotalFees(mempoolEntry.Fee); } else { this.logger.LogTrace("Transaction contains smart contract information."); if (this.blockGasConsumed >= GasPerBlockLimit) { return; } // We HAVE to firstly execute the smart contract contained in the transaction // to ensure its validity before we can add it to the block. IContractExecutionResult result = this.ExecuteSmartContract(mempoolEntry); this.AddTransactionToBlock(mempoolEntry.Transaction); this.UpdateBlockStatistics(mempoolEntry); this.UpdateTotalFees(result.Fee); // If there are refunds, add them to the block. if (result.Refund != null) { this.refundOutputs.Add(result.Refund); this.logger.LogTrace("refund was added with value {0}.", result.Refund.Value); } // Add internal transactions made during execution. if (result.InternalTransaction != null) { this.AddTransactionToBlock(result.InternalTransaction); this.logger.LogTrace("Internal {0}:{1} was added.", nameof(result.InternalTransaction), result.InternalTransaction.GetHash()); } } }
/// <summary> /// Overrides the <see cref="AddToBlock(TxMempoolEntry)"/> behaviour of <see cref="BlockDefinitionProofOfWork"/>. /// <para> /// Determine whether or not the mempool entry contains smart contract execution /// code. If not, then add to the block as per normal. Else extract and deserialize /// the smart contract code from the TxOut's ScriptPubKey. /// </para> /// </summary> public override void AddToBlock(TxMempoolEntry mempoolEntry) { this.logger.LogTrace("()"); TxOut smartContractTxOut = mempoolEntry.Transaction.TryGetSmartContractTxOut(); if (smartContractTxOut == null) { this.logger.LogTrace("Transaction does not contain smart contract information."); base.AddTransactionToBlock(mempoolEntry.Transaction); base.UpdateBlockStatistics(mempoolEntry); base.UpdateTotalFees(mempoolEntry.Fee); } else { this.logger.LogTrace("Transaction contains smart contract information."); // We HAVE to first execute the smart contract contained in the transaction // to ensure its validity before we can add it to the block. ISmartContractExecutionResult result = this.ExecuteSmartContract(mempoolEntry); this.AddTransactionToBlock(mempoolEntry.Transaction); this.UpdateBlockStatistics(mempoolEntry); this.UpdateTotalFees(result.Fee); // If there are refunds, add them to the block. if (result.Refunds.Any()) { this.refundOutputs.AddRange(result.Refunds); this.logger.LogTrace("{0} refunds were added.", result.Refunds.Count); } // Add internal transactions made during execution. if (result.InternalTransaction != null) { this.AddTransactionToBlock(result.InternalTransaction); this.logger.LogTrace("Internal {0}:{1} was added.", nameof(result.InternalTransaction), result.InternalTransaction.GetHash()); } } this.logger.LogTrace("(-)"); }
/// <summary> /// Execute the contract and add all relevant fees and refunds to the block. /// </summary> /// <remarks>TODO: At some point we need to change height to a ulong.</remarks> /// <param name="mempoolEntry">The mempool entry containing the smart contract transaction.</param> private IContractExecutionResult ExecuteSmartContract(TxMempoolEntry mempoolEntry) { // This coinview object can be altered by consensus whilst we're mining. // If this occurred, we would be mining on top of the wrong tip anyway, so // it's okay to throw a ConsensusError which is handled by the miner, and continue. GetSenderResult getSenderResult = this.senderRetriever.GetSender(mempoolEntry.Transaction, this.coinView, this.inBlock.Select(x => x.Transaction).ToList()); if (!getSenderResult.Success) { throw new ConsensusErrorException(new ConsensusError("sc-block-assembler-addcontracttoblock", getSenderResult.Error)); } IContractTransactionContext transactionContext = new ContractTransactionContext((ulong)this.height, this.coinbaseAddress, mempoolEntry.Fee, getSenderResult.Sender, mempoolEntry.Transaction); IContractExecutor executor = this.executorFactory.CreateExecutor(this.stateSnapshot, transactionContext); IContractExecutionResult result = executor.Execute(transactionContext); Result <ContractTxData> deserializedCallData = this.callDataSerializer.Deserialize(transactionContext.Data); this.blockGasConsumed += result.GasConsumed; // Store all fields. We will reuse these in CoinviewRule. var receipt = new Receipt( new uint256(this.stateSnapshot.Root), result.GasConsumed, result.Logs.ToArray(), transactionContext.TransactionHash, transactionContext.Sender, result.To, result.NewContractAddress, !result.Revert, result.Return?.ToString(), result.ErrorMessage, deserializedCallData.Value.GasPrice, transactionContext.TxOutValue, deserializedCallData.Value.IsCreateContract ? null : deserializedCallData.Value.MethodName); this.receipts.Add(receipt); return(result); }
/// <inheritdoc/> protected override bool TestPackage(TxMempoolEntry entry, long packageSize, long packageSigOpsCost) { if (this.futureDriftRule == null) { this.futureDriftRule = this.ConsensusManager.ConsensusRules.GetRule <PosFutureDriftRule>(); } long adjustedTime = this.DateTimeProvider.GetAdjustedTimeAsUnixTimestamp(); long latestValidTime = adjustedTime + this.futureDriftRule.GetFutureDrift(adjustedTime); // We can include txes with timestamp greater than header's timestamp and those txes are invalid to have in block. // However this is needed in order to avoid recreation of block template on every attempt to find kernel. // When kernel is found txes with timestamp greater than header's timestamp are removed. if (entry.Transaction.Time > latestValidTime) { this.logger.LogDebug("Transaction '{0}' has timestamp of {1} but latest valid tx time that can be mined is {2}.", entry.TransactionHash, entry.Transaction.Time, latestValidTime); this.logger.LogTrace("(-)[TOO_EARLY_TO_MINE_TX]:false"); return(false); } return(base.TestPackage(entry, packageSize, packageSigOpsCost)); }
/// <summary> /// Process a transaction accepted to the mempool. /// </summary> /// <param name="entry">Memory pool entry.</param> /// <param name="validFeeEstimate">Whether to update fee estimate.</param> public void ProcessTransaction(TxMempoolEntry entry, bool validFeeEstimate) { lock (this.lockObject) { int txHeight = entry.EntryHeight; uint256 hash = entry.TransactionHash; if (this.mapMemPoolTxs.ContainsKey(hash)) { this.logger.LogInformation($"Blockpolicy error mempool tx {hash} already being tracked"); return; } if (txHeight != this.nBestSeenHeight) { return; } // Only want to be updating estimates when our blockchain is synced, // otherwise we'll miscalculate how many blocks its taking to get included. if (!validFeeEstimate) { this.untrackedTxs++; return; } this.trackedTxs++; FeeRate feeRate = new FeeRate(entry.Fee, (int)entry.GetTxSize()); this.mapMemPoolTxs.Add(hash, new TxStatsInfo()); this.mapMemPoolTxs[hash].blockHeight = txHeight; int bucketIndex = this.feeStats.NewTx(txHeight, feeRate.FeePerK.Satoshi); this.mapMemPoolTxs[hash].bucketIndex = bucketIndex; int bucketIndex2 = this.shortStats.NewTx(txHeight, feeRate.FeePerK.Satoshi); Guard.Assert(bucketIndex == bucketIndex2); int bucketIndex3 = this.longStats.NewTx(txHeight, feeRate.FeePerK.Satoshi); Guard.Assert(bucketIndex == bucketIndex3); } }
/// <summary> /// Execute the contract and add all relevant fees and refunds to the block. /// </summary> /// <remarks>TODO: At some point we need to change height to a ulong.</remarks> private IContractExecutionResult ExecuteSmartContract(TxMempoolEntry mempoolEntry) { GetSenderResult getSenderResult = this.senderRetriever.GetSender(mempoolEntry.Transaction, this.coinView, this.inBlock.Select(x => x.Transaction).ToList()); if (!getSenderResult.Success) { throw new ConsensusErrorException(new ConsensusError("sc-block-assembler-addcontracttoblock", getSenderResult.Error)); } IContractTransactionContext transactionContext = new ContractTransactionContext((ulong)this.height, this.coinbaseAddress, mempoolEntry.Fee, getSenderResult.Sender, mempoolEntry.Transaction); IContractExecutor executor = this.executorFactory.CreateExecutor(this.stateSnapshot, transactionContext); IContractExecutionResult result = executor.Execute(transactionContext); var receipt = new Receipt( new uint256(this.stateSnapshot.Root), result.GasConsumed, result.Logs.ToArray() ); this.receipts.Add(receipt); return(result); }
public async Task LoadPoolTest_WithGoodTransactionsAsync() { string fileName = "mempool.dat"; var tx1_parent = Transaction.Load("0100000001c4fadb806f9679c27c30c11b694523f6ac9614f7a69076b8940082ce636040fb000000006b4830450221009ad4b969a40b95017d133b13f7d465031829731f3b0ae4bcdcb5e393f5e919f902207f33aad2c3af48d6d65aaf5dd15a85a1f588ee3d6f477b2236cda1d81d88c43b012102eb184a906e082db44a95347de64110952b5821c42068a2054947aec4bc60db2fffffffff02685e3e00000000001976a9149ed35c9c42543ec67f9e6d1033e2ac1ac76f86ba88acd33e4500000000001976a9143c88fada9101f660d77feec1dd8db4ee9ea01d6788ac00000000", Network.Main); var tx1 = Transaction.Load("0100000001055c4c42511f9d05f2fa817c7f023df567f3d501bebec14ddce7c05a9d5fda52000000006b483045022100de552f011768887141b9a767ae184f61aa3743a32aad394ac1e1ec35345415420220070b3d0afd28414f188c966e334e9f7b65e7440538d93bc1d61f82067fcfd3fa012103b47b6ffce08f54be286620a29f45407fedb7b33acfec938551938ec96a1e1b0bffffffff019f053e000000000017a91493e31884769545a237f164aa07b3caef6b62f6b68700000000", Network.Main); NodeSettings settings = this.CreateSettings("LoadPoolTest_WithGoodTransactions"); TxMempool txMemPool; MempoolManager mempoolManager = CreateTestMempool(settings, out txMemPool); var fee = Money.Satoshis(0.00001m); txMemPool.AddUnchecked(tx1_parent.GetHash(), new TxMempoolEntry(tx1_parent, fee, 0, 0.0, 0, tx1_parent.TotalOut + fee, false, 0, null, new PowConsensusOptions())); long expectedTx1FeeDelta = 123; // age of tx = 5 hours long txAge = 5 * 60 * 60; List <MempoolPersistenceEntry> toSave = new List <MempoolPersistenceEntry> { new MempoolPersistenceEntry { Tx = tx1, Time = mempoolManager.DateTimeProvider.GetTime() - txAge, FeeDelta = expectedTx1FeeDelta }, }; MemPoolSaveResult result = (new MempoolPersistence(settings, new LoggerFactory())).Save(settings.Network, toSave, fileName); long expectedSize = 2; await mempoolManager.LoadPoolAsync(fileName); long actualSize = await mempoolManager.MempoolSize(); TxMempoolEntry actualEntry = txMemPool.MapTx.TryGet(tx1.GetHash()); long? actualTx1FeedDelta = actualEntry?.feeDelta; Assert.Equal(expectedSize, actualSize); Assert.Equal(expectedTx1FeeDelta, actualTx1FeedDelta); }
// Process a transaction accepted to the mempool public void ProcessTransaction(TxMempoolEntry entry, bool validFeeEstimate) { int txHeight = entry.EntryHeight; uint256 hash = entry.TransactionHash; if (mapMemPoolTxs.ContainsKey(hash)) { Logging.Logs.EstimateFee.LogInformation($"Blockpolicy error mempool tx {hash} already being tracked"); return; } if (txHeight != nBestSeenHeight) { // Ignore side chains and re-orgs; assuming they are random they don't // affect the estimate. We'll potentially double count transactions in 1-block reorgs. // Ignore txs if BlockPolicyEstimator is not in sync with chainActive.Tip(). // It will be synced next time a block is processed. return; } // Only want to be updating estimates when our blockchain is synced, // otherwise we'll miscalculate how many blocks its taking to get included. if (!validFeeEstimate) { untrackedTxs++; return; } trackedTxs++; // Feerates are stored and reported as BTC-per-kb: FeeRate feeRate = new FeeRate(entry.Fee, (int)entry.GetTxSize()); mapMemPoolTxs.Add(hash, new TxStatsInfo()); mapMemPoolTxs[hash].blockHeight = txHeight; mapMemPoolTxs[hash].bucketIndex = feeStats.NewTx(txHeight, (double)feeRate.FeePerK.Satoshi); }
// Methods for how to add transactions to a block. // Add transactions based on feerate including unconfirmed ancestors // Increments nPackagesSelected / nDescendantsUpdated with corresponding // statistics from the package selection (for logging statistics). // This transaction selection algorithm orders the mempool based // on feerate of a transaction including all unconfirmed ancestors. // Since we don't remove transactions from the mempool as we select them // for block inclusion, we need an alternate method of updating the feerate // of a transaction with its not-yet-selected ancestors as we go. // This is accomplished by walking the in-mempool descendants of selected // transactions and storing a temporary modified state in mapModifiedTxs. // Each time through the loop, we compare the best transaction in // mapModifiedTxs with the next transaction in the mempool to decide what // transaction package to work on next. protected virtual void AddTransactions(out int nPackagesSelected, out int nDescendantsUpdated) { nPackagesSelected = 0; nDescendantsUpdated = 0; this.logger.LogTrace("({0}:{1},{2}:{3})", nameof(nPackagesSelected), nPackagesSelected, nameof(nDescendantsUpdated), nDescendantsUpdated); // mapModifiedTx will store sorted packages after they are modified // because some of their txs are already in the block. var mapModifiedTx = new Dictionary <uint256, TxMemPoolModifiedEntry>(); //var mapModifiedTxRes = this.mempoolScheduler.ReadAsync(() => mempool.MapTx.Values).GetAwaiter().GetResult(); // mapModifiedTxRes.Select(s => new TxMemPoolModifiedEntry(s)).OrderBy(o => o, new CompareModifiedEntry()); // Keep track of entries that failed inclusion, to avoid duplicate work. TxMempool.SetEntries failedTx = new TxMempool.SetEntries(); // Start by adding all descendants of previously added txs to mapModifiedTx // and modifying them for their already included ancestors. this.UpdatePackagesForAdded(this.inBlock, mapModifiedTx); List <TxMempoolEntry> ancestorScoreList = this.mempoolLock.ReadAsync(() => this.mempool.MapTx.AncestorScore).GetAwaiter().GetResult().ToList(); TxMempoolEntry iter; int nConsecutiveFailed = 0; while (ancestorScoreList.Any() || mapModifiedTx.Any()) { TxMempoolEntry mi = ancestorScoreList.FirstOrDefault(); if (mi != null) { // Skip entries in mapTx that are already in a block or are present // in mapModifiedTx (which implies that the mapTx ancestor state is // stale due to ancestor inclusion in the block). // Also skip transactions that we've already failed to add. This can happen if // we consider a transaction in mapModifiedTx and it fails: we can then // potentially consider it again while walking mapTx. It's currently // guaranteed to fail again, but as a belt-and-suspenders check we put it in // failedTx and avoid re-evaluation, since the re-evaluation would be using // cached size/sigops/fee values that are not actually correct. // First try to find a new transaction in mapTx to evaluate. if (mapModifiedTx.ContainsKey(mi.TransactionHash) || this.inBlock.Contains(mi) || failedTx.Contains(mi)) { ancestorScoreList.Remove(mi); continue; } } // Now that mi is not stale, determine which transaction to evaluate: // the next entry from mapTx, or the best from mapModifiedTx? bool fUsingModified = false; TxMemPoolModifiedEntry modit; var compare = new CompareModifiedEntry(); if (mi == null) { modit = mapModifiedTx.Values.OrderByDescending(o => o, compare).First(); iter = modit.iter; fUsingModified = true; } else { // Try to compare the mapTx entry to the mapModifiedTx entry iter = mi; modit = mapModifiedTx.Values.OrderByDescending(o => o, compare).FirstOrDefault(); if ((modit != null) && (compare.Compare(modit, new TxMemPoolModifiedEntry(iter)) > 0)) { // The best entry in mapModifiedTx has higher score // than the one from mapTx.. // Switch which transaction (package) to consider. iter = modit.iter; fUsingModified = true; } else { // Either no entry in mapModifiedTx, or it's worse than mapTx. // Increment mi for the next loop iteration. ancestorScoreList.Remove(iter); } } // We skip mapTx entries that are inBlock, and mapModifiedTx shouldn't // contain anything that is inBlock. Guard.Assert(!this.inBlock.Contains(iter)); long packageSize = iter.SizeWithAncestors; Money packageFees = iter.ModFeesWithAncestors; long packageSigOpsCost = iter.SizeWithAncestors; if (fUsingModified) { packageSize = modit.SizeWithAncestors; packageFees = modit.ModFeesWithAncestors; packageSigOpsCost = modit.SigOpCostWithAncestors; } if (packageFees < this.blockMinFeeRate.GetFee((int)packageSize)) { // Everything else we might consider has a lower fee rate return; } if (!this.TestPackage(packageSize, packageSigOpsCost)) { if (fUsingModified) { // Since we always look at the best entry in mapModifiedTx, // we must erase failed entries so that we can consider the // next best entry on the next loop iteration mapModifiedTx.Remove(modit.iter.TransactionHash); failedTx.Add(iter); } nConsecutiveFailed++; if ((nConsecutiveFailed > this.MaxConsecutiveAddTransactionFailures) && (this.blockWeight > this.blockMaxWeight - 4000)) { // Give up if we're close to full and haven't succeeded in a while break; } continue; } TxMempool.SetEntries ancestors = new TxMempool.SetEntries(); long nNoLimit = long.MaxValue; string dummy; this.mempool.CalculateMemPoolAncestors(iter, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, out dummy, false); this.OnlyUnconfirmed(ancestors); ancestors.Add(iter); // Test if all tx's are Final. if (!this.TestPackageTransactions(ancestors)) { if (fUsingModified) { mapModifiedTx.Remove(modit.iter.TransactionHash); failedTx.Add(iter); } continue; } // This transaction will make it in; reset the failed counter. nConsecutiveFailed = 0; // Package can be added. Sort the entries in a valid order. // Sort package by ancestor count // If a transaction A depends on transaction B, then A's ancestor count // must be greater than B's. So this is sufficient to validly order the // transactions for block inclusion. List <TxMempoolEntry> sortedEntries = ancestors.ToList().OrderBy(o => o, new CompareTxIterByAncestorCount()).ToList(); foreach (TxMempoolEntry sortedEntry in sortedEntries) { this.AddToBlock(sortedEntry); // Erase from the modified set, if present mapModifiedTx.Remove(sortedEntry.TransactionHash); } nPackagesSelected++; // Update transactions that depend on each of these nDescendantsUpdated += this.UpdatePackagesForAdded(ancestors, mapModifiedTx); } this.logger.LogTrace("(-)"); }
public int Compare(TxMemPoolModifiedEntry a, TxMemPoolModifiedEntry b) { return(TxMempoolEntry.CompareFees(a, b)); }
/// <summary> /// Updates block statistics from the given mempool entry. /// <para>The block's <see cref="BlockSigOpsCost"/> and <see cref="BlockWeight"/> values are adjusted. /// </para> /// </summary> protected void UpdateBlockStatistics(TxMempoolEntry mempoolEntry) { this.BlockSigOpsCost += mempoolEntry.SigOpCost; this.BlockWeight += mempoolEntry.TxWeight; this.inBlock.Add(mempoolEntry); }
/// <summary> /// Network specific logic to add a transaction to the block from a given mempool entry. /// </summary> public abstract void AddToBlock(TxMempoolEntry mempoolEntry);
/// <inheritdoc/> public override void AddToBlock(TxMempoolEntry mempoolEntry) { this.AddTransactionToBlock(mempoolEntry.Transaction); this.UpdateBlockStatistics(mempoolEntry); this.UpdateTotalFees(mempoolEntry.Fee); }
public override void CheckTransaction(MempoolValidationContext context) { // Check if it's economically rational to mine this transaction rather // than the ones it replaces. context.ConflictingFees = 0; context.ConflictingSize = 0; context.ConflictingCount = 0; context.AllConflicting = new TxMempool.SetEntries(); // If we don't hold the lock allConflicting might be incomplete; the // subsequent RemoveStaged() and addUnchecked() calls don't guarantee // mempool consistency for us. //LOCK(pool.cs); if (context.SetConflicts.Any()) { var newFeeRate = new FeeRate(context.ModifiedFees, context.EntrySize); var setConflictsParents = new List <uint256>(); const int MaxDescendantsToVisit = 100; var setIterConflicting = new TxMempool.SetEntries(); foreach (uint256 hashConflicting in context.SetConflicts) { TxMempoolEntry mi = this.mempool.MapTx.TryGet(hashConflicting); if (mi == null) { continue; } // Save these to avoid repeated lookups setIterConflicting.Add(mi); // Don't allow the replacement to reduce the feerate of the // mempool. // // We usually don't want to accept replacements with lower // feerates than what they replaced as that would lower the // feerate of the next block. Requiring that the feerate always // be increased is also an easy-to-reason about way to prevent // DoS attacks via replacements. // // The mining code doesn't (currently) take children into // account (CPFP) so we only consider the feerates of // transactions being directly replaced, not their indirect // descendants. While that does mean high feerate children are // ignored when deciding whether or not to replace, we do // require the replacement to pay more overall fees too, // mitigating most cases. var oldFeeRate = new FeeRate(mi.ModifiedFee, (int)mi.GetTxSize()); if (newFeeRate <= oldFeeRate) { this.logger.LogTrace("(-)[FAIL_INSUFFICIENT_FEE]"); context.State.Fail(MempoolErrors.InsufficientFee, $"rejecting replacement {context.TransactionHash}; new feerate {newFeeRate} <= old feerate {oldFeeRate}").Throw(); } foreach (TxIn txin in mi.Transaction.Inputs) { setConflictsParents.Add(txin.PrevOut.Hash); } context.ConflictingCount += mi.CountWithDescendants; } // 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 not too many to replace, then calculate the set of // transactions that would have to be evicted foreach (TxMempoolEntry it in setIterConflicting) { this.mempool.CalculateDescendants(it, context.AllConflicting); } foreach (TxMempoolEntry it in context.AllConflicting) { context.ConflictingFees += it.ModifiedFee; context.ConflictingSize += it.GetTxSize(); } } else { this.logger.LogTrace("(-)[FAIL_TOO_MANY_POTENTIAL_REPLACEMENTS]"); context.State.Fail(MempoolErrors.TooManyPotentialReplacements, $"rejecting replacement {context.TransactionHash}; too many potential replacements ({context.ConflictingCount} > {MaxDescendantsToVisit})").Throw(); } for (int j = 0; j < context.Transaction.Inputs.Count; j++) { // We don't want to accept replacements that require low // feerate junk to be mined first. Ideally we'd keep track of // the ancestor feerates and make the decision based on that, // but for now requiring all new inputs to be confirmed works. if (!setConflictsParents.Contains(context.Transaction.Inputs[j].PrevOut.Hash)) { // Rather than check the UTXO set - potentially expensive - // it's cheaper to just check if the new input refers to a // tx that's in the mempool. if (this.mempool.MapTx.ContainsKey(context.Transaction.Inputs[j].PrevOut.Hash)) { this.logger.LogTrace("(-)[FAIL_REPLACEMENT_ADDS_UNCONFIRMED]"); context.State.Fail(MempoolErrors.ReplacementAddsUnconfirmed, $"replacement {context.TransactionHash} adds unconfirmed input, idx {j}").Throw(); } } } // The replacement must pay greater fees than the transactions it // replaces - if we did the bandwidth used by those conflicting // transactions would not be paid for. if (context.ModifiedFees < context.ConflictingFees) { this.logger.LogTrace("(-)[FAIL_INSUFFICIENT_FEE]"); context.State.Fail(MempoolErrors.Insufficientfee, $"rejecting replacement {context.TransactionHash}, less fees than conflicting txs; {context.ModifiedFees} < {context.ConflictingFees}").Throw(); } // Finally in addition to paying more fees than the conflicts the // new transaction must pay for its own bandwidth. Money nDeltaFees = context.ModifiedFees - context.ConflictingFees; if (nDeltaFees < context.MinRelayTxFee.GetFee(context.EntrySize)) { this.logger.LogTrace("(-)[FAIL_INSUFFICIENT_DELTA_FEE]"); context.State.Fail(MempoolErrors.Insufficientfee, $"rejecting replacement {context.TransactionHash}, not enough additional fees to relay; {nDeltaFees} < {context.MinRelayTxFee.GetFee(context.EntrySize)}").Throw(); } } }