Пример #1
0
        /// <summary>
        /// Build a transaction using these inputs, paying the federation.
        /// </summary>
        /// <param name="coins">The set of coins to be spend.</param>
        /// <returns>The reward transaction.</returns>
        private Transaction BuildRewardTransaction(List <ScriptCoin> coins)
        {
            var builder = new TransactionBuilder(this.network);

            // Add the coins to spend.
            builder.AddCoins(coins);

            // An OP_RETURN for a dummy Cirrus address that tells the sidechain federation they can distribute the transaction.
            builder.Send(StraxCoinstakeRule.CirrusTransactionTag(this.network.CirrusRewardDummyAddress), Money.Zero);

            // The mempool will accept a zero-fee transaction as long as it matches this structure, paying to the federation.
            builder.Send(this.network.Federations.GetOnlyFederation().MultisigScript.PaymentScript, coins.Sum(o => o.Amount));

            Transaction builtTransaction = builder.BuildTransaction(true);

            // Filter out FeeTooLowPolicyError errors as reward transaction's will not contain any fees.
            IEnumerable <TransactionPolicyError> errors = builder.Check(builtTransaction).Where(e => !(e is FeeTooLowPolicyError));

            if (errors.Any())
            {
                foreach (TransactionPolicyError error in errors)
                {
                    this.logger.LogWarning("Unable to validate reward claim transaction '{0}', error: {1}", builtTransaction.ToHex(), error.ToString());
                }

                // Not much further can be done at this point.
                return(null);
            }

            this.logger.LogInformation($"Reward distribution transaction built; sending {builtTransaction.TotalOut} to federation '{this.network.Federations.GetOnlyFederation().MultisigScript.PaymentScript}'.");
            return(builtTransaction);
        }
        public override void CheckTransaction(MempoolValidationContext context)
        {
            // We expect a reward claim transaction to have at least 2 outputs.
            bool federationPayment = !(context.Transaction.Outputs.Count < 2);

            // The OP_RETURN output that marks the transaction as cross-chain (and in particular a reward claiming transaction) must be present.
            if (context.Transaction.Outputs.All(o => o.ScriptPubKey != StraxCoinstakeRule.CirrusTransactionTag(this.network.CirrusRewardDummyAddress)))
            {
                federationPayment = false;
            }

            // At least one other output must be paying to the multisig.
            if (context.Transaction.Outputs.All(o => o.ScriptPubKey != this.network.Federations.GetOnlyFederation().MultisigScript.PaymentScript))
            {
                federationPayment = false;
            }

            // There must be no other spendable scriptPubKeys.
            if (context.Transaction.Outputs.Any(o => o.ScriptPubKey != this.network.Federations.GetOnlyFederation().MultisigScript.PaymentScript&& !o.ScriptPubKey.IsUnspendable))
            {
                federationPayment = false;
            }

            // We need to bypass the fee checking logic for correctly-formed transactions that pay Cirrus rewards to the federation.
            if (federationPayment)
            {
                return;
            }

            base.CheckTransaction(context);
        }
Пример #3
0
        /// <summary>
        /// Method 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.
        /// </summary>
        protected virtual void AddTransactions(out int nPackagesSelected, out int nDescendantsUpdated)
        {
            nPackagesSelected   = 0;
            nDescendantsUpdated = 0;

            // 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.
            var 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.ToList()).ConfigureAwait(false).GetAwaiter().GetResult().ToList();

            TxMempoolEntry mempoolEntry;

            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.OrderBy(o => o, compare).First();
                    mempoolEntry   = modit.MempoolEntry;
                    fUsingModified = true;
                }
                else
                {
                    // Try to compare the mapTx entry to the mapModifiedTx entry
                    mempoolEntry = mi;

                    modit = mapModifiedTx.Values.OrderBy(o => o, compare).FirstOrDefault();
                    if ((modit != null) && (compare.Compare(modit, new TxMemPoolModifiedEntry(mempoolEntry)) < 0))
                    {
                        // The best entry in mapModifiedTx has higher score
                        // than the one from mapTx..
                        // Switch which transaction (package) to consider.

                        mempoolEntry   = modit.MempoolEntry;
                        fUsingModified = true;
                    }
                    else
                    {
                        // Either no entry in mapModifiedTx, or it's worse than mapTx.
                        // Increment mi for the next loop iteration.
                        ancestorScoreList.Remove(mempoolEntry);
                    }
                }

                // We skip mapTx entries that are inBlock, and mapModifiedTx shouldn't
                // contain anything that is inBlock.
                Guard.Assert(!this.inBlock.Contains(mempoolEntry));

                long  packageSize       = mempoolEntry.SizeWithAncestors;
                Money packageFees       = mempoolEntry.ModFeesWithAncestors;
                long  packageSigOpsCost = mempoolEntry.SigOpCostWithAncestors;
                if (fUsingModified)
                {
                    packageSize       = modit.SizeWithAncestors;
                    packageFees       = modit.ModFeesWithAncestors;
                    packageSigOpsCost = modit.SigOpCostWithAncestors;
                }

                // Don't check the package fees if this is a CirrusRewardScript transaction.
                if (!mempoolEntry.Transaction.Outputs.Any(o => o.ScriptPubKey == StraxCoinstakeRule.CirrusTransactionTag(this.Network.CirrusRewardDummyAddress)))
                {
                    if (packageFees < this.BlockMinFeeRate.GetFee((int)packageSize))
                    {
                        // Everything else we might consider has a lower fee rate
                        return;
                    }
                }

                if (!this.TestPackage(mempoolEntry, 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.MempoolEntry.TransactionHash);
                        failedTx.Add(mempoolEntry);
                    }

                    nConsecutiveFailed++;

                    if ((nConsecutiveFailed > MaxConsecutiveAddTransactionFailures) && (this.BlockWeight > this.Options.BlockMaxWeight - 4000))
                    {
                        // Give up if we're close to full and haven't succeeded in a while
                        break;
                    }
                    continue;
                }

                var    ancestors = new TxMempool.SetEntries();
                long   nNoLimit  = long.MaxValue;
                string dummy;

                this.MempoolLock.ReadAsync(() => this.Mempool.CalculateMemPoolAncestors(mempoolEntry, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, out dummy, false)).ConfigureAwait(false).GetAwaiter().GetResult();

                this.OnlyUnconfirmed(ancestors);
                ancestors.Add(mempoolEntry);

                // Test if all tx's are Final.
                if (!this.TestPackageTransactions(ancestors))
                {
                    if (fUsingModified)
                    {
                        mapModifiedTx.Remove(modit.MempoolEntry.TransactionHash);
                        failedTx.Add(mempoolEntry);
                    }
                    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);
            }
        }
Пример #4
0
        public Transaction BuildRewardTransaction()
        {
            // Get the minimum stake confirmations for the current network.
            int minStakeConfirmations = ((PosConsensusOptions)this.network.Consensus.Options).GetStakeMinConfirmations(this.chainIndexer.Height, this.network);

            // Take a local copy of the tip.
            ChainedHeader chainTip = this.chainIndexer.Tip;

            if (chainTip.Height < minStakeConfirmations)
            {
                // If the chain is not at least minStakeConfirmations long then just do nothing.
                return(null);
            }

            // Get the block that is minStakeConfirmations behind the current tip.
            ChainedHeader chainedHeader = this.chainIndexer.GetHeader(chainTip.Height - minStakeConfirmations);

            Block maturedBlock = chainedHeader.Block;

            if (maturedBlock == null)
            {
                maturedBlock = this.consensusManager.GetBlockData(chainedHeader.HashBlock).Block;
            }

            // If we still don't have the block data, just return.
            if (maturedBlock == null)
            {
                this.logger.LogDebug("Consensus does not have the block data for '{0}'", chainedHeader);
                return(null);
            }

            // As this runs on the mainchain we presume there will be a coinstake transaction in the block (but during the PoW era there obviously may not be).
            // If not, just do nothing with this block.
            if (maturedBlock.Transactions.Count < 2 || !maturedBlock.Transactions[1].IsCoinStake)
            {
                return(null);
            }

            // We are only interested in the coinstake, as it is the only transaction that we expect to contain outputs paying the reward script.
            Transaction coinStake = maturedBlock.Transactions[1];

            // Identify any outputs paying the reward script a nonzero amount.
            TxOut[] rewardOutputs = coinStake.Outputs.Where(o => o.ScriptPubKey == StraxCoinstakeRule.CirrusRewardScript && o.Value != 0).ToArray();

            // This shouldn't be the case but check anyway.
            if (rewardOutputs.Length == 0)
            {
                return(null);
            }

            // Build a transaction using these inputs, paying the federation.
            var builder = new TransactionBuilder(this.network);

            foreach (TxOut txOutput in rewardOutputs)
            {
                // The reward script is P2SH, so we need to inform the builder of the corresponding redeem script to enable it to be spent.
                var coin = ScriptCoin.Create(this.network, coinStake, txOutput, StraxCoinstakeRule.CirrusRewardScriptRedeem);
                builder.AddCoins(coin);
            }

            // An OP_RETURN for a dummy Cirrus address that tells the sidechain federation they can distribute the transaction.
            builder.Send(StraxCoinstakeRule.CirrusTransactionTag(this.network.CirrusRewardDummyAddress), Money.Zero);

            // The mempool will accept a zero-fee transaction as long as it matches this structure, paying to the federation.
            builder.Send(this.network.Federations.GetOnlyFederation().MultisigScript.PaymentScript, rewardOutputs.Sum(o => o.Value));

            Transaction builtTransaction = builder.BuildTransaction(true);

            // Filter out FeeTooLowPolicyError errors as reward transaction's will not contain any fees.
            IEnumerable <TransactionPolicyError> errors = builder.Check(builtTransaction).Where(e => !(e is FeeTooLowPolicyError));

            if (errors.Any())
            {
                foreach (TransactionPolicyError error in errors)
                {
                    this.logger.LogWarning("Unable to validate reward claim transaction '{0}', error: {1}", builtTransaction.ToHex(), error.ToString());
                }

                // Not much further can be done at this point.
                return(null);
            }

            this.logger.LogInformation($"Reward distribution transaction built; payment script to federation '{this.network.Federations.GetOnlyFederation().MultisigScript.PaymentScript}'.");

            return(builtTransaction);
        }