UniValue estimatefee(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( "estimatefee nblocks\n" "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" "confirmation within nblocks blocks. Uses virtual transaction size of transaction\n" "as defined in BIP 141 (witness data is discounted).\n" "\nArguments:\n" "1. nblocks (numeric, required)\n" "\nResult:\n" "n (numeric) estimated fee-per-kilobyte\n" "\n" "A negative value is returned if not enough transactions and blocks\n" "have been observed to make an estimate.\n" "-1 is always returned for nblocks == 1 as it is impossible to calculate\n" "a fee that is high enough to get reliably included in the next block.\n" "\nExample:\n" + HelpExampleCli("estimatefee", "6") ); RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VNUM)); int nBlocks = request.params[0].get_int(); if (nBlocks < 1) nBlocks = 1; CFeeRate feeRate = mempool.estimateFee(nBlocks); if (feeRate == CFeeRate(0)) return -1.0; return ValueFromAmount(feeRate.GetFeePerK()); }
void SendCoinsDialog::updateSmartFeeLabel() { if(!model || !model->getOptionsModel()) return; int nBlocksToConfirm = defaultConfirmTarget - ui->sliderSmartFee->value(); int estimateFoundAtBlocks = nBlocksToConfirm; CFeeRate feeRate = mempool.estimateSmartFee(nBlocksToConfirm, &estimateFoundAtBlocks); if (feeRate <= CFeeRate(0)) // not enough data => minfee { ui->labelSmartFee->setText(VCoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), std::max(CWallet::fallbackFee.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB"); ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...) ui->labelFeeEstimation->setText(""); } else { ui->labelSmartFee->setText(VCoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), std::max(feeRate.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB"); ui->labelSmartFee2->hide(); ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", estimateFoundAtBlocks)); } updateFeeMinimizedLabel(); }
static UniValue estimatesmartfee(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( "estimatesmartfee conf_target (\"estimate_mode\")\n" "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" "confirmation within conf_target blocks if possible and return the number of blocks\n" "for which the estimate is valid. Uses virtual transaction size as defined\n" "in BIP 141 (witness data is discounted).\n" "\nArguments:\n" "1. conf_target (numeric) Confirmation target in blocks (1 - 1008)\n" "2. \"estimate_mode\" (string, optional, default=CONSERVATIVE) The fee estimate mode.\n" " Whether to return a more conservative estimate which also satisfies\n" " a longer history. A conservative estimate potentially returns a\n" " higher feerate and is more likely to be sufficient for the desired\n" " target, but is not as responsive to short term drops in the\n" " prevailing fee market. Must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" " \"CONSERVATIVE\"\n" "\nResult:\n" "{\n" " \"feerate\" : x.x, (numeric, optional) estimate fee rate in " + CURRENCY_UNIT + "/kB\n" " \"errors\": [ str... ] (json array of strings, optional) Errors encountered during processing\n" " \"blocks\" : n (numeric) block number where estimate was found\n" "}\n" "\n" "The request target will be clamped between 2 and the highest target\n" "fee estimation is able to return based on how long it has been running.\n" "An error is returned if not enough transactions and blocks\n" "have been observed to make an estimate for any number of blocks.\n" "\nExample:\n" + HelpExampleCli("estimatesmartfee", "6") ); RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR}); RPCTypeCheckArgument(request.params[0], UniValue::VNUM); unsigned int conf_target = ParseConfirmTarget(request.params[0]); bool conservative = true; if (!request.params[1].isNull()) { FeeEstimateMode fee_mode; if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); } if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false; } UniValue result(UniValue::VOBJ); UniValue errors(UniValue::VARR); FeeCalculation feeCalc; CFeeRate feeRate = ::feeEstimator.estimateSmartFee(conf_target, &feeCalc, conservative); if (feeRate != CFeeRate(0)) { result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK())); } else { errors.push_back("Insufficient data or no feerate found"); result.pushKV("errors", errors); } result.pushKV("blocks", feeCalc.returnedTarget); return result; }
UniValue estimatefee(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 1) throw runtime_error( "estimatefee nblocks\n" "\nEstimates the approximate fee per kilobyte\n" "needed for a transaction to begin confirmation\n" "within nblocks blocks.\n" "\nArguments:\n" "1. nblocks (numeric)\n" "\nResult:\n" "n : (numeric) estimated fee-per-kilobyte\n" "\n" "-1.0 is returned if not enough transactions and\n" "blocks have been observed to make an estimate.\n" "\nExample:\n" + HelpExampleCli("estimatefee", "6") ); RPCTypeCheck(params, boost::assign::list_of(UniValue::VNUM)); int nBlocks = params[0].get_int(); if (nBlocks < 1) nBlocks = 1; CFeeRate feeRate = mempool.estimateFee(nBlocks); if (feeRate == CFeeRate(0)) return -1.0; return ValueFromAmount(feeRate.GetFeePerK()); }
UniValue estimatesmartfee(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 1) throw runtime_error( "estimatesmartfee nblocks\n" "\nWARNING: This interface is unstable and may disappear or change!\n" "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" "confirmation within nblocks blocks if possible and return the number of blocks\n" "for which the estimate is valid.\n" "\nArguments:\n" "1. nblocks (numeric)\n" "\nResult:\n" "{\n" " \"feerate\" : x.x, (numeric) estimate fee-per-kilobyte (in BTC)\n" " \"blocks\" : n (numeric) block number where estimate was found\n" "}\n" "\n" "A negative value is returned if not enough transactions and blocks\n" "have been observed to make an estimate for any number of blocks.\n" "However it will not return a value below the mempool reject fee.\n" "\nExample:\n" + HelpExampleCli("estimatesmartfee", "6") ); RPCTypeCheck(params, boost::assign::list_of(UniValue::VNUM)); int nBlocks = params[0].get_int(); UniValue result(UniValue::VOBJ); int answerFound; CFeeRate feeRate = mempool.estimateSmartFee(nBlocks, &answerFound); result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK()))); result.push_back(Pair("blocks", answerFound)); return result; }
void CTxMemPool::trackPackageRemoved(const CFeeRate& rate) { AssertLockHeld(cs); if (rate.GetFeePerK() > rollingMinimumFeeRate) { rollingMinimumFeeRate = rate.GetFeePerK(); blockSinceLastRollingFeeBump = false; } }
void SendCoinsDialog::updateSmartFeeLabel() { if(!model || !model->getOptionsModel()) return; CCoinControl coin_control; updateCoinControlState(coin_control); coin_control.m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels FeeCalculation feeCalc; CFeeRate feeRate = CFeeRate(GetMinimumFee(1000, coin_control, ::mempool, ::feeEstimator, &feeCalc)); ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB"); if (feeCalc.reason == FeeReason::FALLBACK) { ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...) ui->labelFeeEstimation->setText(""); ui->fallbackFeeWarningLabel->setVisible(true); int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness(); QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14)); ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }"); ui->fallbackFeeWarningLabel->setIndent(QFontMetrics(ui->fallbackFeeWarningLabel->font()).width("x")); } else { ui->labelSmartFee2->hide(); ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", feeCalc.returnedTarget)); ui->fallbackFeeWarningLabel->setVisible(false); } updateFeeMinimizedLabel(); }
CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) { // "Dust" is defined in terms of dustRelayFee, // which has units satoshis-per-kilobyte. // If you'd pay more than 1/3 in fees // to spend something, then we consider it dust. // A typical spendable non-segwit txout is 34 bytes big, and will // need a CTxIn of at least 148 bytes to spend: // so dust is a spendable txout less than // 546*dustRelayFee/1000 (in satoshis). // A typical spendable segwit txout is 31 bytes big, and will // need a CTxIn of at least 67 bytes to spend: // so dust is a spendable txout less than // 294*dustRelayFee/1000 (in satoshis). if (txout.scriptPubKey.IsUnspendable()) return 0; size_t nSize = GetSerializeSize(txout, SER_DISK, 0); int witnessversion = 0; std::vector<unsigned char> witnessprogram; if (txout.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) { // sum the sizes of the parts of a transaction input // with 75% segwit discount applied to the script size. nSize += (32 + 4 + 1 + (107 / WITNESS_SCALE_FACTOR) + 4); } else { nSize += (32 + 4 + 1 + 107 + 4); // the 148 mentioned above } return 3 * dustRelayFeeIn.GetFee(nSize); }
FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee) { CAmount minFeeLimit = std::max(CAmount(1), minIncrementalFee.GetFeePerK() / 2); feeset.insert(0); for (double bucketBoundary = minFeeLimit; bucketBoundary <= MAX_BUCKET_FEERATE; bucketBoundary *= FEE_SPACING) { feeset.insert(bucketBoundary); } }
static UniValue estimaterawfee(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( "estimaterawfee conf_target (threshold)\n" "\nWARNING: This interface is unstable and may disappear or change!\n" "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n" " implementation of fee estimation. The parameters it can be called with\n" " and the results it returns will change if the internal implementation changes.\n" "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n" "defined in BIP 141 (witness data is discounted).\n" "\nArguments:\n" "1. conf_target (numeric) Confirmation target in blocks (1 - 1008)\n" "2. threshold (numeric, optional) The proportion of transactions in a given feerate range that must have been\n" " confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n" " lower buckets. Default: 0.95\n" "\nResult:\n" "{\n" " \"short\" : { (json object, optional) estimate for short time horizon\n" " \"feerate\" : x.x, (numeric, optional) estimate fee rate in " + CURRENCY_UNIT + "/kB\n" " \"decay\" : x.x, (numeric) exponential decay (per block) for historical moving average of confirmation data\n" " \"scale\" : x, (numeric) The resolution of confirmation targets at this time horizon\n" " \"pass\" : { (json object, optional) information about the lowest range of feerates to succeed in meeting the threshold\n" " \"startrange\" : x.x, (numeric) start of feerate range\n" " \"endrange\" : x.x, (numeric) end of feerate range\n" " \"withintarget\" : x.x, (numeric) number of txs over history horizon in the feerate range that were confirmed within target\n" " \"totalconfirmed\" : x.x, (numeric) number of txs over history horizon in the feerate range that were confirmed at any point\n" " \"inmempool\" : x.x, (numeric) current number of txs in mempool in the feerate range unconfirmed for at least target blocks\n" " \"leftmempool\" : x.x, (numeric) number of txs over history horizon in the feerate range that left mempool unconfirmed after target\n" " },\n" " \"fail\" : { ... }, (json object, optional) information about the highest range of feerates to fail to meet the threshold\n" " \"errors\": [ str... ] (json array of strings, optional) Errors encountered during processing\n" " },\n" " \"medium\" : { ... }, (json object, optional) estimate for medium time horizon\n" " \"long\" : { ... } (json object) estimate for long time horizon\n" "}\n" "\n" "Results are returned for any horizon which tracks blocks up to the confirmation target.\n" "\nExample:\n" + HelpExampleCli("estimaterawfee", "6 0.9") ); RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true); RPCTypeCheckArgument(request.params[0], UniValue::VNUM); unsigned int conf_target = ParseConfirmTarget(request.params[0]); double threshold = 0.95; if (!request.params[1].isNull()) { threshold = request.params[1].get_real(); } if (threshold < 0 || threshold > 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold"); } UniValue result(UniValue::VOBJ); for (const FeeEstimateHorizon horizon : {FeeEstimateHorizon::SHORT_HALFLIFE, FeeEstimateHorizon::MED_HALFLIFE, FeeEstimateHorizon::LONG_HALFLIFE}) { CFeeRate feeRate; EstimationResult buckets; // Only output results for horizons which track the target if (conf_target > ::feeEstimator.HighestTargetTracked(horizon)) continue; feeRate = ::feeEstimator.estimateRawFee(conf_target, threshold, horizon, &buckets); UniValue horizon_result(UniValue::VOBJ); UniValue errors(UniValue::VARR); UniValue passbucket(UniValue::VOBJ); passbucket.pushKV("startrange", round(buckets.pass.start)); passbucket.pushKV("endrange", round(buckets.pass.end)); passbucket.pushKV("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0); passbucket.pushKV("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0); passbucket.pushKV("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0); passbucket.pushKV("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0); UniValue failbucket(UniValue::VOBJ); failbucket.pushKV("startrange", round(buckets.fail.start)); failbucket.pushKV("endrange", round(buckets.fail.end)); failbucket.pushKV("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0); failbucket.pushKV("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0); failbucket.pushKV("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0); failbucket.pushKV("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0); // CFeeRate(0) is used to indicate error as a return value from estimateRawFee if (feeRate != CFeeRate(0)) { horizon_result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK())); horizon_result.pushKV("decay", buckets.decay); horizon_result.pushKV("scale", (int)buckets.scale); horizon_result.pushKV("pass", passbucket); // buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output if (buckets.fail.start != -1) horizon_result.pushKV("fail", failbucket); } else { // Output only information that is still meaningful in the event of error horizon_result.pushKV("decay", buckets.decay); horizon_result.pushKV("scale", (int)buckets.scale); horizon_result.pushKV("fail", failbucket); errors.push_back("Insufficient data or no feerate found which meets threshold"); horizon_result.pushKV("errors",errors); } result.pushKV(StringForFeeEstimateHorizon(horizon), horizon_result); } return result; }
// Test suite for ancestor feerate transaction selection. // Implemented as an additional function, rather than a separate test case, // to allow reusing the blockchain created in CreateNewBlock_validity. void TestPackageSelection(const CChainParams& chainparams, CScript scriptPubKey, std::vector<CTransactionRef>& txFirst) { // Test the ancestor feerate transaction selection. TestMemPoolEntryHelper entry; // Test that a medium fee transaction will be selected after a higher fee // rate package with a low fee rate parent. CMutableTransaction tx; tx.vin.resize(1); tx.vin[0].scriptSig = CScript() << OP_1; tx.vin[0].prevout.hash = txFirst[0]->GetHash(); tx.vin[0].prevout.n = 0; tx.vout.resize(1); tx.vout[0].nValue = 5000000000LL - 1000; // This tx has a low fee: 1000 satoshis uint256 hashParentTx = tx.GetHash(); // save this txid for later use mempool.addUnchecked(hashParentTx, entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); // This tx has a medium fee: 10000 satoshis tx.vin[0].prevout.hash = txFirst[1]->GetHash(); tx.vout[0].nValue = 5000000000LL - 10000; uint256 hashMediumFeeTx = tx.GetHash(); mempool.addUnchecked(hashMediumFeeTx, entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); // This tx has a high fee, but depends on the first transaction tx.vin[0].prevout.hash = hashParentTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 50k satoshi fee uint256 hashHighFeeTx = tx.GetHash(); mempool.addUnchecked(hashHighFeeTx, entry.Fee(50000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashParentTx); BOOST_CHECK(pblocktemplate->block.vtx[2]->GetHash() == hashHighFeeTx); BOOST_CHECK(pblocktemplate->block.vtx[3]->GetHash() == hashMediumFeeTx); // Test that a package below the block min tx fee doesn't get included tx.vin[0].prevout.hash = hashHighFeeTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 0 fee uint256 hashFreeTx = tx.GetHash(); mempool.addUnchecked(hashFreeTx, entry.Fee(0).FromTx(tx)); size_t freeTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); // Calculate a fee on child transaction that will put the package just // below the block min tx fee (assuming 1 child tx of the same size). CAmount feeToUse = blockMinFeeRate.GetFee(2*freeTxSize) - 1; tx.vin[0].prevout.hash = hashFreeTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000 - feeToUse; uint256 hashLowFeeTx = tx.GetHash(); mempool.addUnchecked(hashLowFeeTx, entry.Fee(feeToUse).FromTx(tx)); pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); // Verify that the free tx and the low fee tx didn't get selected for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) { BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashFreeTx); BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashLowFeeTx); } // Test that packages above the min relay fee do get included, even if one // of the transactions is below the min relay fee // Remove the low fee transaction and replace with a higher fee transaction mempool.removeRecursive(tx); tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee hashLowFeeTx = tx.GetHash(); mempool.addUnchecked(hashLowFeeTx, entry.Fee(feeToUse+2).FromTx(tx)); pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); BOOST_CHECK(pblocktemplate->block.vtx[4]->GetHash() == hashFreeTx); BOOST_CHECK(pblocktemplate->block.vtx[5]->GetHash() == hashLowFeeTx); // Test that transaction selection properly updates ancestor fee // calculations as ancestor transactions get included in a block. // Add a 0-fee transaction that has 2 outputs. tx.vin[0].prevout.hash = txFirst[2]->GetHash(); tx.vout.resize(2); tx.vout[0].nValue = 5000000000LL - 100000000; tx.vout[1].nValue = 100000000; // 1BTC output uint256 hashFreeTx2 = tx.GetHash(); mempool.addUnchecked(hashFreeTx2, entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); // This tx can't be mined by itself tx.vin[0].prevout.hash = hashFreeTx2; tx.vout.resize(1); feeToUse = blockMinFeeRate.GetFee(freeTxSize); tx.vout[0].nValue = 5000000000LL - 100000000 - feeToUse; uint256 hashLowFeeTx2 = tx.GetHash(); mempool.addUnchecked(hashLowFeeTx2, entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx)); pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); // Verify that this tx isn't selected. for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) { BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashFreeTx2); BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashLowFeeTx2); } // This tx will be mineable, and should cause hashLowFeeTx2 to be selected // as well. tx.vin[0].prevout.n = 1; tx.vout[0].nValue = 100000000 - 10000; // 10k satoshi fee mempool.addUnchecked(tx.GetHash(), entry.Fee(10000).FromTx(tx)); pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); BOOST_CHECK(pblocktemplate->block.vtx[8]->GetHash() == hashLowFeeTx2); }
CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConfirmTarget, bool specifiedConfirmTarget, CAmount totalFee, bool newTxReplaceable) : txid(std::move(txidIn)), nOldFee(0), nNewFee(0) { vErrors.clear(); bumpedTxid.SetNull(); AssertLockHeld(pWallet->cs_wallet); if (!pWallet->mapWallet.count(txid)) { vErrors.push_back("Invalid or non-wallet transaction id"); currentResult = BumpFeeResult::INVALID_ADDRESS_OR_KEY; return; } auto it = pWallet->mapWallet.find(txid); const CWalletTx& wtx = it->second; if (!preconditionChecks(pWallet, wtx)) { return; } if (!SignalsOptInRBF(wtx)) { vErrors.push_back("Transaction is not BIP 125 replaceable"); currentResult = BumpFeeResult::WALLET_ERROR; return; } if (wtx.mapValue.count("replaced_by_txid")) { vErrors.push_back(strprintf("Cannot bump transaction %s which was already bumped by transaction %s", txid.ToString(), wtx.mapValue.at("replaced_by_txid"))); currentResult = BumpFeeResult::WALLET_ERROR; return; } // check that original tx consists entirely of our inputs // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee) if (!pWallet->IsAllFromMe(wtx, ISMINE_SPENDABLE)) { vErrors.push_back("Transaction contains inputs that don't belong to this wallet"); currentResult = BumpFeeResult::WALLET_ERROR; return; } // figure out which output was change // if there was no change output or multiple change outputs, fail int nOutput = -1; for (size_t i = 0; i < wtx.tx->vout.size(); ++i) { if (pWallet->IsChange(wtx.tx->vout[i])) { if (nOutput != -1) { vErrors.push_back("Transaction has multiple change outputs"); currentResult = BumpFeeResult::WALLET_ERROR; return; } nOutput = i; } } if (nOutput == -1) { vErrors.push_back("Transaction does not have a change output"); currentResult = BumpFeeResult::WALLET_ERROR; return; } // Calculate the expected size of the new transaction. int64_t txSize = GetVirtualTransactionSize(*(wtx.tx)); const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx, pWallet); if (maxNewTxSize < 0) { vErrors.push_back("Transaction contains inputs that cannot be signed"); currentResult = BumpFeeResult::INVALID_ADDRESS_OR_KEY; return; } // calculate the old fee and fee-rate nOldFee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut(); CFeeRate nOldFeeRate(nOldFee, txSize); CFeeRate nNewFeeRate; // The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to // future proof against changes to network wide policy for incremental relay // fee that our node may not be aware of. CFeeRate walletIncrementalRelayFee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE); if (::incrementalRelayFee > walletIncrementalRelayFee) { walletIncrementalRelayFee = ::incrementalRelayFee; } if (totalFee > 0) { CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + ::incrementalRelayFee.GetFee(maxNewTxSize); if (totalFee < minTotalFee) { vErrors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)", FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(::incrementalRelayFee.GetFee(maxNewTxSize)))); currentResult = BumpFeeResult::INVALID_PARAMETER; return; } CAmount requiredFee = CWallet::GetRequiredFee(maxNewTxSize); if (totalFee < requiredFee) { vErrors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)", FormatMoney(requiredFee))); currentResult = BumpFeeResult::INVALID_PARAMETER; return; } nNewFee = totalFee; nNewFeeRate = CFeeRate(totalFee, maxNewTxSize); } else { // if user specified a confirm target then don't consider any global payTxFee if (specifiedConfirmTarget) { nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator, true); } // otherwise use the regular wallet logic to select payTxFee or default confirm target else { nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator); } nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize); // New fee rate must be at least old rate + minimum incremental relay rate // walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized // in that unit (fee per kb). // However, nOldFeeRate is a calculated value from the tx fee/size, so // add 1 satoshi to the result, because it may have been rounded down. if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) { nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()); nNewFee = nNewFeeRate.GetFee(maxNewTxSize); } } // Check that in all cases the new fee doesn't violate maxTxFee if (nNewFee > maxTxFee) { vErrors.push_back(strprintf("Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s)", FormatMoney(nNewFee), FormatMoney(maxTxFee))); currentResult = BumpFeeResult::WALLET_ERROR; return; } // check that fee rate is higher than mempool's minimum fee // (no point in bumping fee if we know that the new tx won't be accepted to the mempool) // This may occur if the user set TotalFee or paytxfee too low, if fallbackfee is too low, or, perhaps, // in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a // moment earlier. In this case, we report an error to the user, who may use totalFee to make an adjustment. CFeeRate minMempoolFeeRate = mempool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000); if (nNewFeeRate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) { vErrors.push_back(strprintf("New fee rate (%s) is less than the minimum fee rate (%s) to get into the mempool. totalFee value should to be at least %s or settxfee value should be at least %s to add transaction.", FormatMoney(nNewFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)), FormatMoney(minMempoolFeeRate.GetFeePerK()))); currentResult = BumpFeeResult::WALLET_ERROR; return; } // Now modify the output to increase the fee. // If the output is not large enough to pay the fee, fail. CAmount nDelta = nNewFee - nOldFee; assert(nDelta > 0); mtx = *wtx.tx; CTxOut* poutput = &(mtx.vout[nOutput]); if (poutput->nValue < nDelta) { vErrors.push_back("Change output is too small to bump the fee"); currentResult = BumpFeeResult::WALLET_ERROR; return; } // If the output would become dust, discard it (converting the dust to fee) poutput->nValue -= nDelta; if (poutput->nValue <= GetDustThreshold(*poutput, ::dustRelayFee)) { LogPrint(BCLog::RPC, "Bumping fee and discarding dust output\n"); nNewFee += poutput->nValue; mtx.vout.erase(mtx.vout.begin() + nOutput); } // Mark new tx not replaceable, if requested. if (!newTxReplaceable) { for (auto& input : mtx.vin) { if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe; } } currentResult = BumpFeeResult::OK; }
#include <boost/test/unit_test.hpp> BOOST_FIXTURE_TEST_SUITE(miner_tests, TestingSetup) // BOOST_CHECK_EXCEPTION predicates to check the specific validation error class HasReason { public: explicit HasReason(const std::string& reason) : m_reason(reason) {} bool operator() (const std::runtime_error& e) const { return std::string(e.what()).find(m_reason) != std::string::npos; }; private: const std::string m_reason; }; static CFeeRate blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE); static BlockAssembler AssemblerForTest(const CChainParams& params) { BlockAssembler::Options options; options.nBlockMaxWeight = MAX_BLOCK_WEIGHT; options.blockMinFeeRate = blockMinFeeRate; return BlockAssembler(params, options); } static struct { unsigned char extranonce; unsigned int nonce; } blockinfo[] = { {4, 0xa4a3e223}, {2, 0x15c32f9e}, {1, 0x0375b547}, {1, 0x7004a8a5},