diff options
author | Suhas Daftuar <sdaftuar@chaincode.com> | 2017-01-26 12:59:32 -0500 |
---|---|---|
committer | Suhas Daftuar <sdaftuar@gmail.com> | 2017-01-31 20:03:45 -0500 |
commit | f62659448cdfd752447117fb73ab5328fbe3e41d (patch) | |
tree | 0f255ce9d302ff9ae07368cb0c278ce60583a063 | |
parent | d625b907a1800a5a30c4ad285641c7418d2c28c1 (diff) |
rpc: bumpfee: use correct maximum signed tx size for fee calculation
More accurate than simply adding one byte per input, and properly handles the
case where the original transaction happened to have very small signatures
-rw-r--r-- | src/wallet/rpcwallet.cpp | 39 |
1 files changed, 35 insertions, 4 deletions
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index bfc738afad..4f763fd6e2 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2664,6 +2664,33 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) return result; } +// 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) +{ + CMutableTransaction txNew(tx); + std::vector<pair<CWalletTx *, unsigned int>> 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 = pwalletMain->mapWallet.find(input.prevout.hash); + assert(mi != pwalletMain->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size()); + vCoins.emplace_back(make_pair(&(mi->second), input.prevout.n)); + } + if (!pwalletMain->DummySignTx(txNew, vCoins)) { + // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) + // implies that we can sign for every input. + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction contains inputs that cannot be signed"); + } + return GetVirtualTransactionSize(txNew); +} + UniValue bumpfee(const JSONRPCRequest& request) { if (!EnsureWalletIsAvailable(request.fHelp)) { @@ -2769,9 +2796,9 @@ UniValue bumpfee(const JSONRPCRequest& request) throw JSONRPCError(RPC_MISC_ERROR, "Transaction does not have a change output"); } - // signature sizes can vary by a byte, so add 1 for each input when calculating the new fee + // Calculate the expected size of the new transaction. int64_t txSize = GetVirtualTransactionSize(*(wtx.tx)); - const int64_t maxNewTxSize = txSize + wtx.tx->vin.size(); + const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx); // optional parameters bool specifiedConfirmTarget = false; @@ -2845,8 +2872,12 @@ UniValue bumpfee(const JSONRPCRequest& request) nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize); // New fee rate must be at least old rate + minimum incremental relay rate - if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + walletIncrementalRelayFee.GetFeePerK()) { - nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + walletIncrementalRelayFee.GetFeePerK()); + // 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); } } |