14c948987f
68af651
MOVEONLY: move TxConfirmStats to cpp (Alex Morcos)2332f19
Initialize TxConfirmStats in constructor (Alex Morcos)5ba81e5
Read and Write fee estimate file directly from CBlockPolicyEstimator (Alex Morcos)14e10aa
Call estimate(Smart)Fee directly from CBlockPolicyEstimator (Alex Morcos)dbb9e36
Give CBlockPolicyEstimator it's own lock (Alex Morcos)f6187d6
Make processBlockTx private. (Alex Morcos)ae7327b
Make feeEstimator its own global instance of CBlockPolicyEstimator (Alex Morcos) Tree-SHA512: dbf3bd2b30822e609a35f3da519b62d23f8a50e564750695ddebd08553b4c01874ae3e07d792c6cc78cc377d2db33b951ffedc46ac7edaf5793f9ebb931713af
284 lines
12 KiB
C++
284 lines
12 KiB
C++
// Copyright (c) 2017 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include "consensus/validation.h"
|
|
#include "wallet/feebumper.h"
|
|
#include "wallet/wallet.h"
|
|
#include "policy/fees.h"
|
|
#include "policy/policy.h"
|
|
#include "policy/rbf.h"
|
|
#include "validation.h" //for mempool access
|
|
#include "txmempool.h"
|
|
#include "utilmoneystr.h"
|
|
#include "util.h"
|
|
#include "net.h"
|
|
|
|
// Calculate the size of the transaction assuming all signatures are max size
|
|
// Use DummySignatureCreator, which inserts 72 byte signatures everywhere.
|
|
// TODO: re-use this in CWallet::CreateTransaction (right now
|
|
// CreateTransaction uses the constructed dummy-signed tx to do a priority
|
|
// calculation, but we should be able to refactor after priority is removed).
|
|
// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
|
|
// be IsAllFromMe).
|
|
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *pWallet)
|
|
{
|
|
CMutableTransaction txNew(tx);
|
|
std::vector<CInputCoin> vCoins;
|
|
// Look up the inputs. We should have already checked that this transaction
|
|
// IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
|
|
// wallet, with a valid index into the vout array.
|
|
for (auto& input : tx.vin) {
|
|
const auto mi = pWallet->mapWallet.find(input.prevout.hash);
|
|
assert(mi != pWallet->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
|
|
vCoins.emplace_back(CInputCoin(&(mi->second), input.prevout.n));
|
|
}
|
|
if (!pWallet->DummySignTx(txNew, vCoins)) {
|
|
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
|
|
// implies that we can sign for every input.
|
|
return -1;
|
|
}
|
|
return GetVirtualTransactionSize(txNew);
|
|
}
|
|
|
|
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 (pWallet->HasWalletSpend(txid)) {
|
|
vErrors.push_back("Transaction has descendants in the wallet");
|
|
currentResult = BumpFeeResult::INVALID_PARAMETER;
|
|
return;
|
|
}
|
|
|
|
{
|
|
LOCK(mempool.cs);
|
|
auto it_mp = mempool.mapTx.find(txid);
|
|
if (it_mp != mempool.mapTx.end() && it_mp->GetCountWithDescendants() > 1) {
|
|
vErrors.push_back("Transaction has descendants in the mempool");
|
|
currentResult = BumpFeeResult::INVALID_PARAMETER;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (wtx.GetDepthInMainChain() != 0) {
|
|
vErrors.push_back("Transaction has been mined, or is conflicted with a mined transaction");
|
|
currentResult = BumpFeeResult::WALLET_ERROR;
|
|
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, CAmount(0));
|
|
}
|
|
// 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 <= poutput->GetDustThreshold(::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;
|
|
}
|
|
|
|
bool CFeeBumper::signTransaction(CWallet *pWallet)
|
|
{
|
|
return pWallet->SignTransaction(mtx);
|
|
}
|
|
|
|
bool CFeeBumper::commit(CWallet *pWallet)
|
|
{
|
|
AssertLockHeld(pWallet->cs_wallet);
|
|
if (!vErrors.empty() || currentResult != BumpFeeResult::OK) {
|
|
return false;
|
|
}
|
|
if (txid.IsNull() || !pWallet->mapWallet.count(txid)) {
|
|
vErrors.push_back("Invalid or non-wallet transaction id");
|
|
currentResult = BumpFeeResult::MISC_ERROR;
|
|
return false;
|
|
}
|
|
CWalletTx& oldWtx = pWallet->mapWallet[txid];
|
|
|
|
CWalletTx wtxBumped(pWallet, MakeTransactionRef(std::move(mtx)));
|
|
// commit/broadcast the tx
|
|
CReserveKey reservekey(pWallet);
|
|
wtxBumped.mapValue = oldWtx.mapValue;
|
|
wtxBumped.mapValue["replaces_txid"] = oldWtx.GetHash().ToString();
|
|
wtxBumped.vOrderForm = oldWtx.vOrderForm;
|
|
wtxBumped.strFromAccount = oldWtx.strFromAccount;
|
|
wtxBumped.fTimeReceivedIsTxTime = true;
|
|
wtxBumped.fFromMe = true;
|
|
CValidationState state;
|
|
if (!pWallet->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state)) {
|
|
// NOTE: CommitTransaction never returns false, so this should never happen.
|
|
vErrors.push_back(strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason()));
|
|
return false;
|
|
}
|
|
|
|
bumpedTxid = wtxBumped.GetHash();
|
|
if (state.IsInvalid()) {
|
|
// This can happen if the mempool rejected the transaction. Report
|
|
// what happened in the "errors" response.
|
|
vErrors.push_back(strprintf("Error: The transaction was rejected: %s", FormatStateMessage(state)));
|
|
}
|
|
|
|
// mark the original tx as bumped
|
|
if (!pWallet->MarkReplaced(oldWtx.GetHash(), wtxBumped.GetHash())) {
|
|
// TODO: see if JSON-RPC has a standard way of returning a response
|
|
// along with an exception. It would be good to return information about
|
|
// wtxBumped to the caller even if marking the original transaction
|
|
// replaced does not succeed for some reason.
|
|
vErrors.push_back("Error: Created new bumpfee transaction but could not mark the original transaction as replaced.");
|
|
}
|
|
return true;
|
|
}
|
|
|