@@ -2664,6 +2664,33 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
2664
2664
return result;
2665
2665
}
2666
2666
2667
+ // Calculate the size of the transaction assuming all signatures are max size
2668
+ // Use DummySignatureCreator, which inserts 72 byte signatures everywhere.
2669
+ // TODO: re-use this in CWallet::CreateTransaction (right now
2670
+ // CreateTransaction uses the constructed dummy-signed tx to do a priority
2671
+ // calculation, but we should be able to refactor after priority is removed).
2672
+ // NOTE: this requires that all inputs must be in mapWallet (eg the tx should
2673
+ // be IsAllFromMe).
2674
+ int64_t CalculateMaximumSignedTxSize (const CTransaction &tx)
2675
+ {
2676
+ CMutableTransaction txNew (tx);
2677
+ std::vector<pair<CWalletTx *, unsigned int >> vCoins;
2678
+ // Look up the inputs. We should have already checked that this transaction
2679
+ // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
2680
+ // wallet, with a valid index into the vout array.
2681
+ for (auto & input : tx.vin ) {
2682
+ const auto mi = pwalletMain->mapWallet .find (input.prevout .hash );
2683
+ assert (mi != pwalletMain->mapWallet .end () && input.prevout .n < mi->second .tx ->vout .size ());
2684
+ vCoins.emplace_back (make_pair (&(mi->second ), input.prevout .n ));
2685
+ }
2686
+ if (!pwalletMain->DummySignTx (txNew, vCoins)) {
2687
+ // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
2688
+ // implies that we can sign for every input.
2689
+ throw JSONRPCError (RPC_INVALID_ADDRESS_OR_KEY, " Transaction contains inputs that cannot be signed" );
2690
+ }
2691
+ return GetVirtualTransactionSize (txNew);
2692
+ }
2693
+
2667
2694
UniValue bumpfee (const JSONRPCRequest& request)
2668
2695
{
2669
2696
if (!EnsureWalletIsAvailable (request.fHelp )) {
@@ -2706,6 +2733,7 @@ UniValue bumpfee(const JSONRPCRequest& request)
2706
2733
" \" txid\" : \" value\" , (string) The id of the new transaction\n "
2707
2734
" \" origfee\" : n, (numeric) Fee of the replaced transaction\n "
2708
2735
" \" fee\" : n, (numeric) Fee of the new transaction\n "
2736
+ " \" errors\" : [ str... ] (json array of strings) Errors encountered during processing (may be empty)\n "
2709
2737
" }\n "
2710
2738
" \n Examples:\n "
2711
2739
" \n Bump the fee, get the new transaction\' s txid\n " +
@@ -2769,9 +2797,9 @@ UniValue bumpfee(const JSONRPCRequest& request)
2769
2797
throw JSONRPCError (RPC_MISC_ERROR, " Transaction does not have a change output" );
2770
2798
}
2771
2799
2772
- // signature sizes can vary by a byte, so add 1 for each input when calculating the new fee
2800
+ // Calculate the expected size of the new transaction.
2773
2801
int64_t txSize = GetVirtualTransactionSize (*(wtx.tx ));
2774
- const int64_t maxNewTxSize = txSize + wtx.tx -> vin . size ( );
2802
+ const int64_t maxNewTxSize = CalculateMaximumSignedTxSize (* wtx.tx );
2775
2803
2776
2804
// optional parameters
2777
2805
bool specifiedConfirmTarget = false ;
@@ -2845,8 +2873,12 @@ UniValue bumpfee(const JSONRPCRequest& request)
2845
2873
nNewFeeRate = CFeeRate (nNewFee, maxNewTxSize);
2846
2874
2847
2875
// New fee rate must be at least old rate + minimum incremental relay rate
2848
- if (nNewFeeRate.GetFeePerK () < nOldFeeRate.GetFeePerK () + walletIncrementalRelayFee.GetFeePerK ()) {
2849
- nNewFeeRate = CFeeRate (nOldFeeRate.GetFeePerK () + walletIncrementalRelayFee.GetFeePerK ());
2876
+ // walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized
2877
+ // in that unit (fee per kb).
2878
+ // However, nOldFeeRate is a calculated value from the tx fee/size, so
2879
+ // add 1 satoshi to the result, because it may have been rounded down.
2880
+ if (nNewFeeRate.GetFeePerK () < nOldFeeRate.GetFeePerK () + 1 + walletIncrementalRelayFee.GetFeePerK ()) {
2881
+ nNewFeeRate = CFeeRate (nOldFeeRate.GetFeePerK () + 1 + walletIncrementalRelayFee.GetFeePerK ());
2850
2882
nNewFee = nNewFeeRate.GetFee (maxNewTxSize);
2851
2883
}
2852
2884
}
@@ -2914,23 +2946,32 @@ UniValue bumpfee(const JSONRPCRequest& request)
2914
2946
CWalletTx wtxBumped (pwalletMain, MakeTransactionRef (std::move (tx)));
2915
2947
wtxBumped.mapValue [" replaces_txid" ] = hash.ToString ();
2916
2948
CValidationState state;
2917
- if (!pwalletMain->CommitTransaction (wtxBumped, reservekey, g_connman.get (), state) || !state.IsValid ()) {
2949
+ if (!pwalletMain->CommitTransaction (wtxBumped, reservekey, g_connman.get (), state)) {
2950
+ // NOTE: CommitTransaction never returns false, so this should never happen.
2918
2951
throw JSONRPCError (RPC_WALLET_ERROR, strprintf (" Error: The transaction was rejected! Reason given: %s" , state.GetRejectReason ()));
2919
2952
}
2920
2953
2954
+ UniValue vErrors (UniValue::VARR);
2955
+ if (state.IsInvalid ()) {
2956
+ // This can happen if the mempool rejected the transaction. Report
2957
+ // what happened in the "errors" response.
2958
+ vErrors.push_back (strprintf (" Error: The transaction was rejected: %s" , FormatStateMessage (state)));
2959
+ }
2960
+
2921
2961
// mark the original tx as bumped
2922
2962
if (!pwalletMain->MarkReplaced (wtx.GetHash (), wtxBumped.GetHash ())) {
2923
2963
// TODO: see if JSON-RPC has a standard way of returning a response
2924
2964
// along with an exception. It would be good to return information about
2925
2965
// wtxBumped to the caller even if marking the original transaction
2926
2966
// replaced does not succeed for some reason.
2927
- throw JSONRPCError (RPC_WALLET_ERROR, " Error: Created new bumpfee transaction but could not mark the original transaction as replaced." );
2967
+ vErrors. push_back ( " Error: Created new bumpfee transaction but could not mark the original transaction as replaced." );
2928
2968
}
2929
2969
2930
2970
UniValue result (UniValue::VOBJ);
2931
2971
result.push_back (Pair (" txid" , wtxBumped.GetHash ().GetHex ()));
2932
2972
result.push_back (Pair (" origfee" , ValueFromAmount (nOldFee)));
2933
2973
result.push_back (Pair (" fee" , ValueFromAmount (nNewFee)));
2974
+ result.push_back (Pair (" errors" , vErrors));
2934
2975
2935
2976
return result;
2936
2977
}
0 commit comments