diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/core_io.h | 6 | ||||
-rw-r--r-- | src/core_read.cpp | 28 | ||||
-rw-r--r-- | src/interfaces/wallet.cpp | 15 | ||||
-rw-r--r-- | src/net.cpp | 1 | ||||
-rw-r--r-- | src/net.h | 14 | ||||
-rw-r--r-- | src/net_processing.cpp | 3 | ||||
-rw-r--r-- | src/psbt.cpp | 166 | ||||
-rw-r--r-- | src/psbt.h | 51 | ||||
-rw-r--r-- | src/qt/forms/receivecoinsdialog.ui | 8 | ||||
-rw-r--r-- | src/qt/receivecoinsdialog.cpp | 6 | ||||
-rw-r--r-- | src/rpc/rawtransaction.cpp | 169 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 19 | ||||
-rw-r--r-- | src/wallet/test/wallet_tests.cpp | 8 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 84 | ||||
-rw-r--r-- | src/wallet/wallet.h | 14 |
16 files changed, 318 insertions, 276 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 8f0110b43e..0385c825ea 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -434,8 +434,8 @@ libbitcoin_common_a_SOURCES = \ netaddress.cpp \ netbase.cpp \ policy/feerate.cpp \ - psbt.cpp \ protocol.cpp \ + psbt.cpp \ scheduler.cpp \ script/descriptor.cpp \ script/ismine.cpp \ diff --git a/src/core_io.h b/src/core_io.h index ae377eb6e8..19fb7b29f6 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -16,7 +16,6 @@ class CBlockHeader; class CScript; class CTransaction; struct CMutableTransaction; -struct PartiallySignedTransaction; class uint256; class UniValue; @@ -37,11 +36,6 @@ bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header); */ bool ParseHashStr(const std::string& strHex, uint256& result); std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName); - -//! Decode a base64ed PSBT into a PartiallySignedTransaction -NODISCARD bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error); -//! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction -NODISCARD bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, const std::string& raw_psbt, std::string& error); int ParseSighashString(const UniValue& sighash); // core_write.cpp diff --git a/src/core_read.cpp b/src/core_read.cpp index 536a7f4f17..a879a375ce 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -4,7 +4,6 @@ #include <core_io.h> -#include <psbt.h> #include <primitives/block.h> #include <primitives/transaction.h> #include <script/script.h> @@ -177,33 +176,6 @@ bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk) return true; } -bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error) -{ - bool invalid; - std::string tx_data = DecodeBase64(base64_tx, &invalid); - if (invalid) { - error = "invalid base64"; - return false; - } - return DecodeRawPSBT(psbt, tx_data, error); -} - -bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data, std::string& error) -{ - CDataStream ss_data(tx_data.data(), tx_data.data() + tx_data.size(), SER_NETWORK, PROTOCOL_VERSION); - try { - ss_data >> psbt; - if (!ss_data.empty()) { - error = "extra data after PSBT"; - return false; - } - } catch (const std::exception& e) { - error = e.what(); - return false; - } - return true; -} - bool ParseHashStr(const std::string& strHex, uint256& result) { if ((strHex.size() != 64) || !IsHex(strHex)) diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 60173b29ac..0bb9739c9c 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -358,15 +358,16 @@ public: } WalletBalances getBalances() override { + const auto bal = m_wallet->GetBalance(); WalletBalances result; - result.balance = m_wallet->GetBalance(); - result.unconfirmed_balance = m_wallet->GetUnconfirmedBalance(); - result.immature_balance = m_wallet->GetImmatureBalance(); + result.balance = bal.m_mine_trusted; + result.unconfirmed_balance = bal.m_mine_untrusted_pending; + result.immature_balance = bal.m_mine_immature; result.have_watch_only = m_wallet->HaveWatchOnly(); if (result.have_watch_only) { - result.watch_only_balance = m_wallet->GetBalance(ISMINE_WATCH_ONLY); - result.unconfirmed_watch_only_balance = m_wallet->GetUnconfirmedWatchOnlyBalance(); - result.immature_watch_only_balance = m_wallet->GetImmatureWatchOnlyBalance(); + result.watch_only_balance = bal.m_watchonly_trusted; + result.unconfirmed_watch_only_balance = bal.m_watchonly_untrusted_pending; + result.immature_watch_only_balance = bal.m_watchonly_immature; } return result; } @@ -382,7 +383,7 @@ public: num_blocks = locked_chain->getHeight().get_value_or(-1); return true; } - CAmount getBalance() override { return m_wallet->GetBalance(); } + CAmount getBalance() override { return m_wallet->GetBalance().m_mine_trusted; } CAmount getAvailableBalance(const CCoinControl& coin_control) override { return m_wallet->GetAvailableBalance(&coin_control); diff --git a/src/net.cpp b/src/net.cpp index ccab4a1718..1335804b06 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2627,7 +2627,6 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn { hSocket = hSocketIn; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; - strSubVer = ""; hashContinue = uint256(); filterInventoryKnown.reset(); pfilter = MakeUnique<CBloomFilter>(); @@ -53,7 +53,7 @@ static const unsigned int MAX_LOCATOR_SZ = 101; static const unsigned int MAX_ADDR_TO_SEND = 1000; /** Maximum length of incoming protocol messages (no message over 4 MB is currently acceptable). */ static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = 4 * 1000 * 1000; -/** Maximum length of strSubVer in `version` message */ +/** Maximum length of the user agent string in `version` message */ static const unsigned int MAX_SUBVERSION_LENGTH = 256; /** Maximum number of automatic outgoing nodes */ static const int MAX_OUTBOUND_CONNECTIONS = 8; @@ -650,12 +650,12 @@ public: // Bind address of our side of the connection const CAddress addrBind; std::atomic<int> nVersion{0}; - // strSubVer is whatever byte array we read from the wire. However, this field is intended - // to be printed out, displayed to humans in various forms and so on. So we sanitize it and - // store the sanitized version in cleanSubVer. The original should be used when dealing with - // the network or wire types and the cleaned string used when displayed or logged. - std::string strSubVer GUARDED_BY(cs_SubVer), cleanSubVer GUARDED_BY(cs_SubVer); - CCriticalSection cs_SubVer; // used for both cleanSubVer and strSubVer + RecursiveMutex cs_SubVer; + /** + * cleanSubVer is a sanitized string of the user agent byte array we read + * from the wire. This cleaned string can safely be logged or displayed. + */ + std::string cleanSubVer GUARDED_BY(cs_SubVer){}; bool m_prefer_evict{false}; // This peer is preferred for eviction. bool fWhitelisted{false}; // This peer can bypass DoS banning. bool fFeeler{false}; // If true this node is being used as a short lived feeler. diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 0654a96e26..0f13d6e269 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1843,7 +1843,6 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr ServiceFlags nServices; int nVersion; int nSendVersion; - std::string strSubVer; std::string cleanSubVer; int nStartingHeight = -1; bool fRelay = true; @@ -1880,6 +1879,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (!vRecv.empty()) vRecv >> addrFrom >> nNonce; if (!vRecv.empty()) { + std::string strSubVer; vRecv >> LIMITED_STRING(strSubVer, MAX_SUBVERSION_LENGTH); cleanSubVer = SanitizeString(strSubVer); } @@ -1911,7 +1911,6 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr pfrom->SetAddrLocal(addrMe); { LOCK(pfrom->cs_SubVer); - pfrom->strSubVer = strSubVer; pfrom->cleanSubVer = cleanSubVer; } pfrom->nStartingHeight = nStartingHeight; diff --git a/src/psbt.cpp b/src/psbt.cpp index 0fb7d49d7d..184129e330 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -2,9 +2,14 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <coins.h> +#include <consensus/tx_verify.h> +#include <policy/policy.h> #include <psbt.h> #include <util/strencodings.h> +#include <numeric> + PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx) { inputs.resize(tx.vin.size()); @@ -205,7 +210,7 @@ void PSBTOutput::Merge(const PSBTOutput& output) if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script; if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script; } -bool PSBTInputSigned(PSBTInput& input) +bool PSBTInputSigned(const PSBTInput& input) { return !input.final_script_sig.empty() || !input.final_script_witness.IsNull(); } @@ -325,3 +330,162 @@ TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector return TransactionError::OK; } + +std::string PSBTRoleName(PSBTRole role) { + switch (role) { + case PSBTRole::UPDATER: return "updater"; + case PSBTRole::SIGNER: return "signer"; + case PSBTRole::FINALIZER: return "finalizer"; + case PSBTRole::EXTRACTOR: return "extractor"; + } +} + +PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) +{ + // Go through each input and build status + PSBTAnalysis result; + + bool calc_fee = true; + bool all_final = true; + bool only_missing_sigs = true; + bool only_missing_final = false; + CAmount in_amt = 0; + + result.inputs.resize(psbtx.tx->vin.size()); + + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInput& input = psbtx.inputs[i]; + PSBTInputAnalysis& input_analysis = result.inputs[i]; + + // Check for a UTXO + CTxOut utxo; + if (psbtx.GetInputUTXO(utxo, i)) { + in_amt += utxo.nValue; + input_analysis.has_utxo = true; + } else { + input_analysis.has_utxo = false; + input_analysis.is_final = false; + input_analysis.next = PSBTRole::UPDATER; + calc_fee = false; + } + + // Check if it is final + if (!utxo.IsNull() && !PSBTInputSigned(input)) { + input_analysis.is_final = false; + all_final = false; + + // Figure out what is missing + SignatureData outdata; + bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &outdata); + + // Things are missing + if (!complete) { + input_analysis.missing_pubkeys = outdata.missing_pubkeys; + input_analysis.missing_redeem_script = outdata.missing_redeem_script; + input_analysis.missing_witness_script = outdata.missing_witness_script; + input_analysis.missing_sigs = outdata.missing_sigs; + + // If we are only missing signatures and nothing else, then next is signer + if (outdata.missing_pubkeys.empty() && outdata.missing_redeem_script.IsNull() && outdata.missing_witness_script.IsNull() && !outdata.missing_sigs.empty()) { + input_analysis.next = PSBTRole::SIGNER; + } else { + only_missing_sigs = false; + input_analysis.next = PSBTRole::UPDATER; + } + } else { + only_missing_final = true; + input_analysis.next = PSBTRole::FINALIZER; + } + } else if (!utxo.IsNull()){ + input_analysis.is_final = true; + } + } + + if (all_final) { + only_missing_sigs = false; + result.next = PSBTRole::EXTRACTOR; + } + if (calc_fee) { + // Get the output amount + CAmount out_amt = std::accumulate(psbtx.tx->vout.begin(), psbtx.tx->vout.end(), CAmount(0), + [](CAmount a, const CTxOut& b) { + return a += b.nValue; + } + ); + + // Get the fee + CAmount fee = in_amt - out_amt; + result.fee = fee; + + // Estimate the size + CMutableTransaction mtx(*psbtx.tx); + CCoinsView view_dummy; + CCoinsViewCache view(&view_dummy); + bool success = true; + + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInput& input = psbtx.inputs[i]; + Coin newcoin; + + if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true) || !psbtx.GetInputUTXO(newcoin.out, i)) { + success = false; + break; + } else { + mtx.vin[i].scriptSig = input.final_script_sig; + mtx.vin[i].scriptWitness = input.final_script_witness; + newcoin.nHeight = 1; + view.AddCoin(psbtx.tx->vin[i].prevout, std::move(newcoin), true); + } + } + + if (success) { + CTransaction ctx = CTransaction(mtx); + size_t size = GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS)); + result.estimated_vsize = size; + // Estimate fee rate + CFeeRate feerate(fee, size); + result.estimated_feerate = feerate; + } + + if (only_missing_sigs) { + result.next = PSBTRole::SIGNER; + } else if (only_missing_final) { + result.next = PSBTRole::FINALIZER; + } else if (all_final) { + result.next = PSBTRole::EXTRACTOR; + } else { + result.next = PSBTRole::UPDATER; + } + } else { + result.next = PSBTRole::UPDATER; + } + + return result; +} + +bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error) +{ + bool invalid; + std::string tx_data = DecodeBase64(base64_tx, &invalid); + if (invalid) { + error = "invalid base64"; + return false; + } + return DecodeRawPSBT(psbt, tx_data, error); +} + +bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data, std::string& error) +{ + CDataStream ss_data(tx_data.data(), tx_data.data() + tx_data.size(), SER_NETWORK, PROTOCOL_VERSION); + try { + ss_data >> psbt; + if (!ss_data.empty()) { + error = "extra data after PSBT"; + return false; + } + } catch (const std::exception& e) { + error = e.what(); + return false; + } + return true; +} diff --git a/src/psbt.h b/src/psbt.h index c889dad361..fcb3337a53 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -7,6 +7,8 @@ #include <attributes.h> #include <node/transaction.h> +#include <optional.h> +#include <policy/feerate.h> #include <primitives/transaction.h> #include <pubkey.h> #include <script/sign.h> @@ -548,8 +550,42 @@ struct PartiallySignedTransaction } }; +enum class PSBTRole { + UPDATER, + SIGNER, + FINALIZER, + EXTRACTOR +}; + +/** + * Holds an analysis of one input from a PSBT + */ +struct PSBTInputAnalysis { + bool has_utxo; //!< Whether we have UTXO information for this input + bool is_final; //!< Whether the input has all required information including signatures + PSBTRole next; //!< Which of the BIP 174 roles needs to handle this input next + + std::vector<CKeyID> missing_pubkeys; //!< Pubkeys whose BIP32 derivation path is missing + std::vector<CKeyID> missing_sigs; //!< Pubkeys whose signatures are missing + uint160 missing_redeem_script; //!< Hash160 of redeem script, if missing + uint256 missing_witness_script; //!< SHA256 of witness script, if missing +}; + +/** + * Holds the results of AnalyzePSBT (miscellaneous information about a PSBT) + */ +struct PSBTAnalysis { + Optional<size_t> estimated_vsize; //!< Estimated weight of the transaction + Optional<CFeeRate> estimated_feerate; //!< Estimated feerate (fee / weight) of the transaction + Optional<CAmount> fee; //!< Amount of fee being paid by the transaction + std::vector<PSBTInputAnalysis> inputs; //!< More information about the individual inputs of the transaction + PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next +}; + +std::string PSBTRoleName(PSBTRole role); + /** Checks whether a PSBTInput is already signed. */ -bool PSBTInputSigned(PSBTInput& input); +bool PSBTInputSigned(const PSBTInput& input); /** Signs a PSBTInput, verifying that all provided data matches what is being signed. */ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool use_dummy = false); @@ -580,4 +616,17 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti */ NODISCARD TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs); +/** + * Provides helpful miscellaneous information about where a PSBT is in the signing workflow. + * + * @param[in] psbtx the PSBT to analyze + * @return A PSBTAnalysis with information about the provided PSBT. + */ +PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx); + +//! Decode a base64ed PSBT into a PartiallySignedTransaction +NODISCARD bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error); +//! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction +NODISCARD bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, const std::string& raw_psbt, std::string& error); + #endif // BITCOIN_PSBT_H diff --git a/src/qt/forms/receivecoinsdialog.ui b/src/qt/forms/receivecoinsdialog.ui index 2f916d0b44..8876ea1337 100644 --- a/src/qt/forms/receivecoinsdialog.ui +++ b/src/qt/forms/receivecoinsdialog.ui @@ -189,7 +189,7 @@ </widget> </item> <item> - <widget class="QCheckBox" name="useBech32"> + <widget class="QCheckBox" name="useLegacyAddress"> <property name="sizePolicy"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <horstretch>0</horstretch> @@ -206,10 +206,10 @@ <enum>Qt::StrongFocus</enum> </property> <property name="toolTip"> - <string>Native segwit addresses (aka Bech32 or BIP-173) reduce your transaction fees later on and offer better protection against typos, but old wallets don't support them. When unchecked, an address compatible with older wallets will be created instead.</string> + <string>Native segwit addresses (aka Bech32 or BIP-173) reduce your transaction fees later on and offer better protection against typos, but old wallets don't support them. When checked, an address compatible with older wallets will be created instead.</string> </property> <property name="text"> - <string>Generate native segwit (Bech32) address</string> + <string>Generate legacy address</string> </property> </widget> </item> @@ -360,7 +360,7 @@ <tabstops> <tabstop>reqLabel</tabstop> <tabstop>reqAmount</tabstop> - <tabstop>useBech32</tabstop> + <tabstop>useLegacyAddress</tabstop> <tabstop>reqMessage</tabstop> <tabstop>receiveButton</tabstop> <tabstop>clearButton</tabstop> diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 22a79a12bb..fc58090dcd 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -95,9 +95,9 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model) columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(tableView, AMOUNT_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this); if (model->wallet().getDefaultAddressType() == OutputType::BECH32) { - ui->useBech32->setCheckState(Qt::Checked); + ui->useLegacyAddress->setCheckState(Qt::Unchecked); } else { - ui->useBech32->setCheckState(Qt::Unchecked); + ui->useLegacyAddress->setCheckState(Qt::Checked); } // Set the button to be enabled or disabled based on whether the wallet can give out new addresses. @@ -150,7 +150,7 @@ void ReceiveCoinsDialog::on_receiveButton_clicked() QString label = ui->reqLabel->text(); /* Generate new receiving address */ OutputType address_type; - if (ui->useBech32->isChecked()) { + if (!ui->useLegacyAddress->isChecked()) { address_type = OutputType::BECH32; } else { address_type = model->wallet().getDefaultAddressType(); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 5dcd04323e..6ecde84cdb 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1054,7 +1054,9 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) "\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"}, - {"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE), "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"}, + {"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE), + "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + + "/kB.\nSet to 0 to accept any fee rate.\n"}, }, RPCResult{ "\"hex\" (string) The transaction hash in hex\n" @@ -1549,7 +1551,6 @@ UniValue combinepsbt(const JSONRPCRequest& request) throw JSONRPCTransactionError(error); } - UniValue result(UniValue::VOBJ); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << merged_psbt; return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()); @@ -1944,148 +1945,56 @@ UniValue analyzepsbt(const JSONRPCRequest& request) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); } - // Go through each input and build status + PSBTAnalysis psbta = AnalyzePSBT(psbtx); + UniValue result(UniValue::VOBJ); UniValue inputs_result(UniValue::VARR); - bool calc_fee = true; - bool all_final = true; - bool only_missing_sigs = true; - bool only_missing_final = false; - CAmount in_amt = 0; - for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - PSBTInput& input = psbtx.inputs[i]; + for (const auto& input : psbta.inputs) { UniValue input_univ(UniValue::VOBJ); UniValue missing(UniValue::VOBJ); - // Check for a UTXO - CTxOut utxo; - if (psbtx.GetInputUTXO(utxo, i)) { - in_amt += utxo.nValue; - input_univ.pushKV("has_utxo", true); - } else { - input_univ.pushKV("has_utxo", false); - input_univ.pushKV("is_final", false); - input_univ.pushKV("next", "updater"); - calc_fee = false; - } + input_univ.pushKV("has_utxo", input.has_utxo); + input_univ.pushKV("is_final", input.is_final); + input_univ.pushKV("next", PSBTRoleName(input.next)); - // Check if it is final - if (!utxo.IsNull() && !PSBTInputSigned(input)) { - input_univ.pushKV("is_final", false); - all_final = false; - - // Figure out what is missing - SignatureData outdata; - bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &outdata); - - // Things are missing - if (!complete) { - if (!outdata.missing_pubkeys.empty()) { - // Missing pubkeys - UniValue missing_pubkeys_univ(UniValue::VARR); - for (const CKeyID& pubkey : outdata.missing_pubkeys) { - missing_pubkeys_univ.push_back(HexStr(pubkey)); - } - missing.pushKV("pubkeys", missing_pubkeys_univ); - } - if (!outdata.missing_redeem_script.IsNull()) { - // Missing redeemScript - missing.pushKV("redeemscript", HexStr(outdata.missing_redeem_script)); - } - if (!outdata.missing_witness_script.IsNull()) { - // Missing witnessScript - missing.pushKV("witnessscript", HexStr(outdata.missing_witness_script)); - } - if (!outdata.missing_sigs.empty()) { - // Missing sigs - UniValue missing_sigs_univ(UniValue::VARR); - for (const CKeyID& pubkey : outdata.missing_sigs) { - missing_sigs_univ.push_back(HexStr(pubkey)); - } - missing.pushKV("signatures", missing_sigs_univ); - } - input_univ.pushKV("missing", missing); - - // If we are only missing signatures and nothing else, then next is signer - if (outdata.missing_pubkeys.empty() && outdata.missing_redeem_script.IsNull() && outdata.missing_witness_script.IsNull() && !outdata.missing_sigs.empty()) { - input_univ.pushKV("next", "signer"); - } else { - only_missing_sigs = false; - input_univ.pushKV("next", "updater"); - } - } else { - only_missing_final = true; - input_univ.pushKV("next", "finalizer"); + if (!input.missing_pubkeys.empty()) { + UniValue missing_pubkeys_univ(UniValue::VARR); + for (const CKeyID& pubkey : input.missing_pubkeys) { + missing_pubkeys_univ.push_back(HexStr(pubkey)); + } + missing.pushKV("pubkeys", missing_pubkeys_univ); + } + if (!input.missing_redeem_script.IsNull()) { + missing.pushKV("redeemscript", HexStr(input.missing_redeem_script)); + } + if (!input.missing_witness_script.IsNull()) { + missing.pushKV("witnessscript", HexStr(input.missing_witness_script)); + } + if (!input.missing_sigs.empty()) { + UniValue missing_sigs_univ(UniValue::VARR); + for (const CKeyID& pubkey : input.missing_sigs) { + missing_sigs_univ.push_back(HexStr(pubkey)); } - } else if (!utxo.IsNull()){ - input_univ.pushKV("is_final", true); + missing.pushKV("signatures", missing_sigs_univ); + } + if (!missing.getKeys().empty()) { + input_univ.pushKV("missing", missing); } inputs_result.push_back(input_univ); } result.pushKV("inputs", inputs_result); - if (all_final) { - only_missing_sigs = false; - result.pushKV("next", "extractor"); + if (psbta.estimated_vsize != nullopt) { + result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize); } - if (calc_fee) { - // Get the output amount - CAmount out_amt = std::accumulate(psbtx.tx->vout.begin(), psbtx.tx->vout.end(), CAmount(0), - [](CAmount a, const CTxOut& b) { - return a += b.nValue; - } - ); - - // Get the fee - CAmount fee = in_amt - out_amt; - - // Estimate the size - CMutableTransaction mtx(*psbtx.tx); - CCoinsView view_dummy; - CCoinsViewCache view(&view_dummy); - bool success = true; - - for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - PSBTInput& input = psbtx.inputs[i]; - if (SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true)) { - mtx.vin[i].scriptSig = input.final_script_sig; - mtx.vin[i].scriptWitness = input.final_script_witness; - - Coin newcoin; - if (!psbtx.GetInputUTXO(newcoin.out, i)) { - success = false; - break; - } - newcoin.nHeight = 1; - view.AddCoin(psbtx.tx->vin[i].prevout, std::move(newcoin), true); - } else { - success = false; - break; - } - } - - if (success) { - CTransaction ctx = CTransaction(mtx); - size_t size = GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS)); - result.pushKV("estimated_vsize", (int)size); - // Estimate fee rate - CFeeRate feerate(fee, size); - result.pushKV("estimated_feerate", ValueFromAmount(feerate.GetFeePerK())); - } - result.pushKV("fee", ValueFromAmount(fee)); - - if (only_missing_sigs) { - result.pushKV("next", "signer"); - } else if (only_missing_final) { - result.pushKV("next", "finalizer"); - } else if (all_final) { - result.pushKV("next", "extractor"); - } else { - result.pushKV("next", "updater"); - } - } else { - result.pushKV("next", "updater"); + if (psbta.estimated_feerate != nullopt) { + result.pushKV("estimated_feerate", ValueFromAmount(psbta.estimated_feerate->GetFeePerK())); } + if (psbta.fee != nullopt) { + result.pushKV("fee", ValueFromAmount(*psbta.fee)); + } + result.pushKV("next", PSBTRoleName(psbta.next)); + return result; } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index f1da1fb589..d9ae0b9bd6 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -308,7 +308,7 @@ static UniValue setlabel(const JSONRPCRequest& request) static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue) { - CAmount curBalance = pwallet->GetBalance(); + CAmount curBalance = pwallet->GetBalance().m_mine_trusted; // Check amount if (nValue <= 0) @@ -761,12 +761,14 @@ static UniValue getbalance(const JSONRPCRequest& request) min_depth = request.params[1].get_int(); } - isminefilter filter = ISMINE_SPENDABLE; + bool include_watchonly = false; if (!request.params[2].isNull() && request.params[2].get_bool()) { - filter = filter | ISMINE_WATCH_ONLY; + include_watchonly = true; } - return ValueFromAmount(pwallet->GetBalance(filter, min_depth)); + const auto bal = pwallet->GetBalance(min_depth); + + return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0)); } static UniValue getunconfirmedbalance(const JSONRPCRequest &request) @@ -794,7 +796,7 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request) auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - return ValueFromAmount(pwallet->GetUnconfirmedBalance()); + return ValueFromAmount(pwallet->GetBalance().m_mine_untrusted_pending); } @@ -2416,11 +2418,12 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) UniValue obj(UniValue::VOBJ); size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); + const auto bal = pwallet->GetBalance(); obj.pushKV("walletname", pwallet->GetName()); obj.pushKV("walletversion", pwallet->GetVersion()); - obj.pushKV("balance", ValueFromAmount(pwallet->GetBalance())); - obj.pushKV("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance())); - obj.pushKV("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance())); + obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted)); + obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending)); + obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature)); obj.pushKV("txcount", (int)pwallet->mapWallet.size()); obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime()); obj.pushKV("keypoolsize", (int64_t)kpExternalSize); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index af57dbf5f6..7c8085dc20 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -58,7 +58,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); - BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0); + BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); } // Verify ScanForWalletTransactions picks up transactions in both the old @@ -73,7 +73,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); - BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); + BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 100 * COIN); } // Prune the older block file. @@ -92,7 +92,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); - BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); + BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 50 * COIN); } // Prune the remaining block file. @@ -110,7 +110,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); - BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0); + BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); } } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f0499d5b32..b62ab0c514 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2154,84 +2154,32 @@ void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, in */ -CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) const +CWallet::Balance CWallet::GetBalance(const int min_depth) const { - CAmount nTotal = 0; + Balance ret; { auto locked_chain = chain().lock(); LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx& wtx = entry.second; - if (wtx.IsTrusted(*locked_chain) && wtx.GetDepthInMainChain(*locked_chain) >= min_depth) { - nTotal += wtx.GetAvailableCredit(*locked_chain, true, filter); + const bool is_trusted{wtx.IsTrusted(*locked_chain)}; + const int tx_depth{wtx.GetDepthInMainChain(*locked_chain)}; + const CAmount tx_credit_mine{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_SPENDABLE)}; + const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_WATCH_ONLY)}; + if (is_trusted && tx_depth >= min_depth) { + ret.m_mine_trusted += tx_credit_mine; + ret.m_watchonly_trusted += tx_credit_watchonly; } + if (!is_trusted && tx_depth == 0 && wtx.InMempool()) { + ret.m_mine_untrusted_pending += tx_credit_mine; + ret.m_watchonly_untrusted_pending += tx_credit_watchonly; + } + ret.m_mine_immature += wtx.GetImmatureCredit(*locked_chain); + ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(*locked_chain); } } - - return nTotal; -} - -CAmount CWallet::GetUnconfirmedBalance() const -{ - CAmount nTotal = 0; - { - auto locked_chain = chain().lock(); - LOCK(cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx& wtx = entry.second; - if (!wtx.IsTrusted(*locked_chain) && wtx.GetDepthInMainChain(*locked_chain) == 0 && wtx.InMempool()) - nTotal += wtx.GetAvailableCredit(*locked_chain); - } - } - return nTotal; -} - -CAmount CWallet::GetImmatureBalance() const -{ - CAmount nTotal = 0; - { - auto locked_chain = chain().lock(); - LOCK(cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx& wtx = entry.second; - nTotal += wtx.GetImmatureCredit(*locked_chain); - } - } - return nTotal; -} - -CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const -{ - CAmount nTotal = 0; - { - auto locked_chain = chain().lock(); - LOCK(cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx& wtx = entry.second; - if (!wtx.IsTrusted(*locked_chain) && wtx.GetDepthInMainChain(*locked_chain) == 0 && wtx.InMempool()) - nTotal += wtx.GetAvailableCredit(*locked_chain, true, ISMINE_WATCH_ONLY); - } - } - return nTotal; -} - -CAmount CWallet::GetImmatureWatchOnlyBalance() const -{ - CAmount nTotal = 0; - { - auto locked_chain = chain().lock(); - LOCK(cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx& wtx = entry.second; - nTotal += wtx.GetImmatureWatchOnlyCredit(*locked_chain); - } - } - return nTotal; + return ret; } CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 3aeeaafd1f..fd274fa375 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -947,11 +947,15 @@ public: void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; void ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, int64_t nBestBlockTime) override; - CAmount GetBalance(const isminefilter& filter=ISMINE_SPENDABLE, const int min_depth=0) const; - CAmount GetUnconfirmedBalance() const; - CAmount GetImmatureBalance() const; - CAmount GetUnconfirmedWatchOnlyBalance() const; - CAmount GetImmatureWatchOnlyBalance() const; + struct Balance { + CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more + CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending) + CAmount m_mine_immature{0}; //!< Immature coinbases in the main chain + CAmount m_watchonly_trusted{0}; + CAmount m_watchonly_untrusted_pending{0}; + CAmount m_watchonly_immature{0}; + }; + Balance GetBalance(int min_depth = 0) const; CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const; OutputType TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend); |