Exemple #1
0
        /// <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);
        }
Exemple #6
0
        /// <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));
        }
Exemple #7
0
        /// <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());
                }
            }
        }
Exemple #11
0
        /// <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);
        }
Exemple #13
0
        /// <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));
        }
Exemple #14
0
        /// <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);
Exemple #22
0
 /// <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();
                }
            }
        }