/// <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); }
/// <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); } }
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); }