@ -43,13 +43,13 @@ static int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWalle
return GetVirtualTransactionSize ( txNew ) ;
return GetVirtualTransactionSize ( txNew ) ;
}
}
namespace feebumper {
//! Check whether transaction has descendant in wallet or mempool, or has been
//! mined, or conflicts with a mined transaction. Return a feebumper::Result.
bool FeeBumper : : preconditionChecks ( const CWallet * wallet , const CWalletTx & wtx ) {
static feebumper : : Result PreconditionChecks ( const CWallet * wallet , const CWalletTx & wtx , std : : vector < std : : string > & errors )
{
if ( wallet - > HasWalletSpend ( wtx . GetHash ( ) ) ) {
if ( wallet - > HasWalletSpend ( wtx . GetHash ( ) ) ) {
errors . push_back ( " Transaction has descendants in the wallet " ) ;
errors . push_back ( " Transaction has descendants in the wallet " ) ;
current_result = Result : : INVALID_PARAMETER ;
return feebumper : : Result : : INVALID_PARAMETER ;
return false ;
}
}
{
{
@ -57,58 +57,58 @@ bool FeeBumper::preconditionChecks(const CWallet *wallet, const CWalletTx& wtx)
auto it_mp = mempool . mapTx . find ( wtx . GetHash ( ) ) ;
auto it_mp = mempool . mapTx . find ( wtx . GetHash ( ) ) ;
if ( it_mp ! = mempool . mapTx . end ( ) & & it_mp - > GetCountWithDescendants ( ) > 1 ) {
if ( it_mp ! = mempool . mapTx . end ( ) & & it_mp - > GetCountWithDescendants ( ) > 1 ) {
errors . push_back ( " Transaction has descendants in the mempool " ) ;
errors . push_back ( " Transaction has descendants in the mempool " ) ;
current_result = Result : : INVALID_PARAMETER ;
return feebumper : : Result : : INVALID_PARAMETER ;
return false ;
}
}
}
}
if ( wtx . GetDepthInMainChain ( ) ! = 0 ) {
if ( wtx . GetDepthInMainChain ( ) ! = 0 ) {
errors . push_back ( " Transaction has been mined, or is conflicted with a mined transaction " ) ;
errors . push_back ( " Transaction has been mined, or is conflicted with a mined transaction " ) ;
current_result = Result : : WALLET_ERROR ;
return feebumper : : Result : : WALLET_ERROR ;
return false ;
}
}
return true ;
return feebumper : : Result : : OK ;
}
}
FeeBumper : : FeeBumper ( const CWallet * wallet , const uint256 txid_in , const CCoinControl & coin_control , CAmount total_fee )
namespace feebumper {
:
txid ( std : : move ( txid_in ) ) ,
bool TransactionCanBeBumped ( CWallet * wallet , const uint256 & txid )
old_fee ( 0 ) ,
new_fee ( 0 )
{
{
LOCK2 ( cs_main , wallet - > cs_wallet ) ;
const CWalletTx * wtx = wallet - > GetWalletTx ( txid ) ;
return wtx & & SignalsOptInRBF ( * wtx - > tx ) & & ! wtx - > mapValue . count ( " replaced_by_txid " ) ;
}
Result CreateTransaction ( const CWallet * wallet , const uint256 & txid , const CCoinControl & coin_control , CAmount total_fee , std : : vector < std : : string > & errors ,
CAmount & old_fee , CAmount & new_fee , CMutableTransaction & mtx )
{
LOCK2 ( cs_main , wallet - > cs_wallet ) ;
errors . clear ( ) ;
errors . clear ( ) ;
bumped_txid . SetNull ( ) ;
AssertLockHeld ( wallet - > cs_wallet ) ;
auto it = wallet - > mapWallet . find ( txid ) ;
auto it = wallet - > mapWallet . find ( txid ) ;
if ( it = = wallet - > mapWallet . end ( ) ) {
if ( it = = wallet - > mapWallet . end ( ) ) {
errors . push_back ( " Invalid or non-wallet transaction id " ) ;
errors . push_back ( " Invalid or non-wallet transaction id " ) ;
current_result = Result : : INVALID_ADDRESS_OR_KEY ;
return Result : : INVALID_ADDRESS_OR_KEY ;
return ;
}
}
const CWalletTx & wtx = it - > second ;
const CWalletTx & wtx = it - > second ;
if ( ! preconditionChecks ( wallet , wtx ) ) {
Result result = PreconditionChecks ( wallet , wtx , errors ) ;
return ;
if ( result ! = Result : : OK ) {
return result ;
}
}
if ( ! SignalsOptInRBF ( * wtx . tx ) ) {
if ( ! SignalsOptInRBF ( * wtx . tx ) ) {
errors . push_back ( " Transaction is not BIP 125 replaceable " ) ;
errors . push_back ( " Transaction is not BIP 125 replaceable " ) ;
current_result = Result : : WALLET_ERROR ;
return Result : : WALLET_ERROR ;
return ;
}
}
if ( wtx . mapValue . count ( " replaced_by_txid " ) ) {
if ( wtx . mapValue . count ( " replaced_by_txid " ) ) {
errors . push_back ( strprintf ( " Cannot bump transaction %s which was already bumped by transaction %s " , txid . ToString ( ) , wtx . mapValue . at ( " replaced_by_txid " ) ) ) ;
errors . push_back ( strprintf ( " Cannot bump transaction %s which was already bumped by transaction %s " , txid . ToString ( ) , wtx . mapValue . at ( " replaced_by_txid " ) ) ) ;
current_result = Result : : WALLET_ERROR ;
return Result : : WALLET_ERROR ;
return ;
}
}
// check that original tx consists entirely of our inputs
// 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 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 ( ! wallet - > IsAllFromMe ( * wtx . tx , ISMINE_SPENDABLE ) ) {
if ( ! wallet - > IsAllFromMe ( * wtx . tx , ISMINE_SPENDABLE ) ) {
errors . push_back ( " Transaction contains inputs that don't belong to this wallet " ) ;
errors . push_back ( " Transaction contains inputs that don't belong to this wallet " ) ;
current_result = Result : : WALLET_ERROR ;
return Result : : WALLET_ERROR ;
return ;
}
}
// figure out which output was change
// figure out which output was change
@ -118,16 +118,14 @@ FeeBumper::FeeBumper(const CWallet *wallet, const uint256 txid_in, const CCoinCo
if ( wallet - > IsChange ( wtx . tx - > vout [ i ] ) ) {
if ( wallet - > IsChange ( wtx . tx - > vout [ i ] ) ) {
if ( nOutput ! = - 1 ) {
if ( nOutput ! = - 1 ) {
errors . push_back ( " Transaction has multiple change outputs " ) ;
errors . push_back ( " Transaction has multiple change outputs " ) ;
current_result = Result : : WALLET_ERROR ;
return Result : : WALLET_ERROR ;
return ;
}
}
nOutput = i ;
nOutput = i ;
}
}
}
}
if ( nOutput = = - 1 ) {
if ( nOutput = = - 1 ) {
errors . push_back ( " Transaction does not have a change output " ) ;
errors . push_back ( " Transaction does not have a change output " ) ;
current_result = Result : : WALLET_ERROR ;
return Result : : WALLET_ERROR ;
return ;
}
}
// Calculate the expected size of the new transaction.
// Calculate the expected size of the new transaction.
@ -135,8 +133,7 @@ FeeBumper::FeeBumper(const CWallet *wallet, const uint256 txid_in, const CCoinCo
const int64_t maxNewTxSize = CalculateMaximumSignedTxSize ( * wtx . tx , wallet ) ;
const int64_t maxNewTxSize = CalculateMaximumSignedTxSize ( * wtx . tx , wallet ) ;
if ( maxNewTxSize < 0 ) {
if ( maxNewTxSize < 0 ) {
errors . push_back ( " Transaction contains inputs that cannot be signed " ) ;
errors . push_back ( " Transaction contains inputs that cannot be signed " ) ;
current_result = Result : : INVALID_ADDRESS_OR_KEY ;
return Result : : INVALID_ADDRESS_OR_KEY ;
return ;
}
}
// calculate the old fee and fee-rate
// calculate the old fee and fee-rate
@ -156,15 +153,13 @@ FeeBumper::FeeBumper(const CWallet *wallet, const uint256 txid_in, const CCoinCo
if ( total_fee < minTotalFee ) {
if ( total_fee < minTotalFee ) {
errors . push_back ( strprintf ( " Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s) " ,
errors . push_back ( strprintf ( " Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s) " ,
FormatMoney ( minTotalFee ) , FormatMoney ( nOldFeeRate . GetFee ( maxNewTxSize ) ) , FormatMoney ( : : incrementalRelayFee . GetFee ( maxNewTxSize ) ) ) ) ;
FormatMoney ( minTotalFee ) , FormatMoney ( nOldFeeRate . GetFee ( maxNewTxSize ) ) , FormatMoney ( : : incrementalRelayFee . GetFee ( maxNewTxSize ) ) ) ) ;
current_result = Result : : INVALID_PARAMETER ;
return Result : : INVALID_PARAMETER ;
return ;
}
}
CAmount requiredFee = GetRequiredFee ( maxNewTxSize ) ;
CAmount requiredFee = GetRequiredFee ( maxNewTxSize ) ;
if ( total_fee < requiredFee ) {
if ( total_fee < requiredFee ) {
errors . push_back ( strprintf ( " Insufficient totalFee (cannot be less than required fee %s) " ,
errors . push_back ( strprintf ( " Insufficient totalFee (cannot be less than required fee %s) " ,
FormatMoney ( requiredFee ) ) ) ;
FormatMoney ( requiredFee ) ) ) ;
current_result = Result : : INVALID_PARAMETER ;
return Result : : INVALID_PARAMETER ;
return ;
}
}
new_fee = total_fee ;
new_fee = total_fee ;
nNewFeeRate = CFeeRate ( total_fee , maxNewTxSize ) ;
nNewFeeRate = CFeeRate ( total_fee , maxNewTxSize ) ;
@ -187,8 +182,7 @@ FeeBumper::FeeBumper(const CWallet *wallet, const uint256 txid_in, const CCoinCo
if ( new_fee > maxTxFee ) {
if ( new_fee > maxTxFee ) {
errors . push_back ( strprintf ( " Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s) " ,
errors . push_back ( strprintf ( " Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s) " ,
FormatMoney ( new_fee ) , FormatMoney ( maxTxFee ) ) ) ;
FormatMoney ( new_fee ) , FormatMoney ( maxTxFee ) ) ) ;
current_result = Result : : WALLET_ERROR ;
return Result : : WALLET_ERROR ;
return ;
}
}
// check that fee rate is higher than mempool's minimum fee
// check that fee rate is higher than mempool's minimum fee
@ -205,8 +199,7 @@ FeeBumper::FeeBumper(const CWallet *wallet, const uint256 txid_in, const CCoinCo
FormatMoney ( minMempoolFeeRate . GetFeePerK ( ) ) ,
FormatMoney ( minMempoolFeeRate . GetFeePerK ( ) ) ,
FormatMoney ( minMempoolFeeRate . GetFee ( maxNewTxSize ) ) ,
FormatMoney ( minMempoolFeeRate . GetFee ( maxNewTxSize ) ) ,
FormatMoney ( minMempoolFeeRate . GetFeePerK ( ) ) ) ) ;
FormatMoney ( minMempoolFeeRate . GetFeePerK ( ) ) ) ) ;
current_result = Result : : WALLET_ERROR ;
return Result : : WALLET_ERROR ;
return ;
}
}
// Now modify the output to increase the fee.
// Now modify the output to increase the fee.
@ -217,8 +210,7 @@ FeeBumper::FeeBumper(const CWallet *wallet, const uint256 txid_in, const CCoinCo
CTxOut * poutput = & ( mtx . vout [ nOutput ] ) ;
CTxOut * poutput = & ( mtx . vout [ nOutput ] ) ;
if ( poutput - > nValue < nDelta ) {
if ( poutput - > nValue < nDelta ) {
errors . push_back ( " Change output is too small to bump the fee " ) ;
errors . push_back ( " Change output is too small to bump the fee " ) ;
current_result = Result : : WALLET_ERROR ;
return Result : : WALLET_ERROR ;
return ;
}
}
// If the output would become dust, discard it (converting the dust to fee)
// If the output would become dust, discard it (converting the dust to fee)
@ -236,31 +228,31 @@ FeeBumper::FeeBumper(const CWallet *wallet, const uint256 txid_in, const CCoinCo
}
}
}
}
current_result = Result : : OK ;
return Result : : OK ;
}
}
bool FeeBumper : : signTransaction ( CWallet * wallet )
bool SignTransaction ( CWallet * wallet , CMutableTransaction & mtx ) {
{
LOCK2 ( cs_main , wallet - > cs_wallet ) ;
return wallet - > SignTransaction ( mtx ) ;
return wallet - > SignTransaction ( mtx ) ;
}
}
bool FeeBumper : : commit ( CWallet * wallet )
Result CommitTransaction ( CWallet * wallet , const uint256 & txid , CMutableTransaction & & mtx , std : : vector < std : : string > & errors , uint256 & bumped_txid )
{
{
AssertLockHeld ( wallet - > cs_wallet ) ;
LOCK2 ( cs_main , wallet - > cs_wallet ) ;
if ( ! errors . empty ( ) | | current_result ! = Result : : OK ) {
if ( ! errors . empty ( ) ) {
return false ;
return Result : : MISC_ERROR ;
}
}
auto it = txid . IsNull ( ) ? wallet - > mapWallet . end ( ) : wallet - > mapWallet . find ( txid ) ;
auto it = txid . IsNull ( ) ? wallet - > mapWallet . end ( ) : wallet - > mapWallet . find ( txid ) ;
if ( it = = wallet - > mapWallet . end ( ) ) {
if ( it = = wallet - > mapWallet . end ( ) ) {
errors . push_back ( " Invalid or non-wallet transaction id " ) ;
errors . push_back ( " Invalid or non-wallet transaction id " ) ;
current_result = Result : : MISC_ERROR ;
return Result : : MISC_ERROR ;
return false ;
}
}
CWalletTx & oldWtx = it - > second ;
CWalletTx & oldWtx = it - > second ;
// make sure the transaction still has no descendants and hasn't been mined in the meantime
// make sure the transaction still has no descendants and hasn't been mined in the meantime
if ( ! preconditionChecks ( wallet , oldWtx ) ) {
Result result = PreconditionChecks ( wallet , oldWtx , errors ) ;
return false ;
if ( result ! = Result : : OK ) {
return result ;
}
}
CWalletTx wtxBumped ( wallet , MakeTransactionRef ( std : : move ( mtx ) ) ) ;
CWalletTx wtxBumped ( wallet , MakeTransactionRef ( std : : move ( mtx ) ) ) ;
@ -276,14 +268,14 @@ bool FeeBumper::commit(CWallet *wallet)
if ( ! wallet - > CommitTransaction ( wtxBumped , reservekey , g_connman . get ( ) , state ) ) {
if ( ! wallet - > CommitTransaction ( wtxBumped , reservekey , g_connman . get ( ) , state ) ) {
// NOTE: CommitTransaction never returns false, so this should never happen.
// NOTE: CommitTransaction never returns false, so this should never happen.
errors . push_back ( strprintf ( " The transaction was rejected: %s " , state . GetRejectReason ( ) ) ) ;
errors . push_back ( strprintf ( " The transaction was rejected: %s " , state . GetRejectReason ( ) ) ) ;
return false ;
return Result : : WALLET_ERROR ;
}
}
bumped_txid = wtxBumped . GetHash ( ) ;
bumped_txid = wtxBumped . GetHash ( ) ;
if ( state . IsInvalid ( ) ) {
if ( state . IsInvalid ( ) ) {
// This can happen if the mempool rejected the transaction. Report
// This can happen if the mempool rejected the transaction. Report
// what happened in the "errors" response.
// what happened in the "errors" response.
errors . push_back ( strprintf ( " The transaction was rejected: %s " , FormatStateMessage ( state ) ) ) ;
errors . push_back ( strprintf ( " Error: The transaction was rejected: %s" , FormatStateMessage ( state ) ) ) ;
}
}
// mark the original tx as bumped
// mark the original tx as bumped
@ -294,7 +286,7 @@ bool FeeBumper::commit(CWallet *wallet)
// replaced does not succeed for some reason.
// replaced does not succeed for some reason.
errors . push_back ( " Created new bumpfee transaction but could not mark the original transaction as replaced " ) ;
errors . push_back ( " Created new bumpfee transaction but could not mark the original transaction as replaced " ) ;
}
}
return true ;
return Result : : OK ;
}
}
} // namespace feebumper
} // namespace feebumper