diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index 0d18fbeb1f..a871243087 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -1,301 +1,313 @@ // Copyright (c) 2018 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 <interfaces/node.h> #include <addrdb.h> #include <amount.h> #include <chain.h> #include <chainparams.h> #include <config.h> #include <init.h> #include <interfaces/handler.h> #include <interfaces/wallet.h> #include <net.h> #include <net_processing.h> #include <netaddress.h> #include <netbase.h> +#include <policy/fees.h> +#include <policy/policy.h> #include <primitives/block.h> #include <rpc/server.h> #include <scheduler.h> #include <sync.h> #include <txmempool.h> #include <ui_interface.h> #include <util.h> #include <validation.h> #include <warnings.h> #if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> #endif #ifdef ENABLE_WALLET +#include <wallet/fees.h> #include <wallet/wallet.h> #define CHECK_WALLET(x) x #else #define CHECK_WALLET(x) \ throw std::logic_error("Wallet function called in non-wallet build.") #endif #include <boost/thread/thread.hpp> #include <univalue.h> #include <atomic> class HTTPRPCRequestProcessor; namespace interfaces { namespace { class NodeImpl : public Node { void parseParameters(int argc, const char *const argv[]) override { gArgs.ParseParameters(argc, argv); } void readConfigFile(const std::string &conf_path) override { gArgs.ReadConfigFile(conf_path); } bool softSetArg(const std::string &arg, const std::string &value) override { return gArgs.SoftSetArg(arg, value); } bool softSetBoolArg(const std::string &arg, bool value) override { return gArgs.SoftSetBoolArg(arg, value); } void selectParams(const std::string &network) override { SelectParams(network); } void initLogging() override { InitLogging(); } void initParameterInteraction() override { InitParameterInteraction(); } std::string getWarnings(const std::string &type) override { return GetWarnings(type); } bool baseInitialize(Config &config, RPCServer &rpcServer) override { return AppInitBasicSetup() && AppInitParameterInteraction(config, rpcServer) && AppInitSanityChecks() && AppInitLockDataDirectory(); } bool appInitMain(Config &config, HTTPRPCRequestProcessor &httpRPCRequestProcessor) override { return AppInitMain(config, httpRPCRequestProcessor); } void appShutdown() override { Interrupt(); Shutdown(); } void startShutdown() override { StartShutdown(); } bool shutdownRequested() override { return ShutdownRequested(); } void mapPort(bool use_upnp) override { if (use_upnp) { StartMapPort(); } else { InterruptMapPort(); StopMapPort(); } } std::string helpMessage(HelpMessageMode mode) override { return HelpMessage(mode); } bool getProxy(Network net, proxyType &proxy_info) override { return GetProxy(net, proxy_info); } size_t getNodeCount(CConnman::NumConnections flags) override { return g_connman ? g_connman->GetNodeCount(flags) : 0; } bool getNodesStats(NodesStats &stats) override { stats.clear(); if (g_connman) { std::vector<CNodeStats> stats_temp; g_connman->GetNodeStats(stats_temp); stats.reserve(stats_temp.size()); for (auto &node_stats_temp : stats_temp) { stats.emplace_back(std::move(node_stats_temp), false, CNodeStateStats()); } // Try to retrieve the CNodeStateStats for each node. TRY_LOCK(::cs_main, lockMain); if (lockMain) { for (auto &node_stats : stats) { std::get<1>(node_stats) = GetNodeStateStats(std::get<0>(node_stats).nodeid, std::get<2>(node_stats)); } } return true; } return false; } bool getBanned(banmap_t &banmap) override { if (g_connman) { g_connman->GetBanned(banmap); return true; } return false; } bool ban(const CNetAddr &net_addr, BanReason reason, int64_t ban_time_offset) override { if (g_connman) { g_connman->Ban(net_addr, reason, ban_time_offset); return true; } return false; } bool unban(const CSubNet &ip) override { if (g_connman) { g_connman->Unban(ip); return true; } return false; } bool disconnect(NodeId id) override { if (g_connman) { return g_connman->DisconnectNode(id); } return false; } int64_t getTotalBytesRecv() override { return g_connman ? g_connman->GetTotalBytesRecv() : 0; } int64_t getTotalBytesSent() override { return g_connman ? g_connman->GetTotalBytesSent() : 0; } size_t getMempoolSize() override { return g_mempool.size(); } size_t getMempoolDynamicUsage() override { return g_mempool.DynamicMemoryUsage(); } bool getHeaderTip(int &height, int64_t &block_time) override { LOCK(::cs_main); if (::pindexBestHeader) { height = ::pindexBestHeader->nHeight; block_time = ::pindexBestHeader->GetBlockTime(); return true; } return false; } int getNumBlocks() override { LOCK(::cs_main); return ::chainActive.Height(); } int64_t getLastBlockTime() override { LOCK(::cs_main); if (::chainActive.Tip()) { return ::chainActive.Tip()->GetBlockTime(); } // Genesis block's time of current network return Params().GenesisBlock().GetBlockTime(); } double getVerificationProgress() override { const CBlockIndex *tip; { LOCK(::cs_main); tip = ::chainActive.Tip(); } return GuessVerificationProgress(Params().TxData(), tip); } bool isInitialBlockDownload() override { return IsInitialBlockDownload(); } bool getReindex() override { return ::fReindex; } bool getImporting() override { return ::fImporting; } void setNetworkActive(bool active) override { if (g_connman) { g_connman->SetNetworkActive(active); } } bool getNetworkActive() override { return g_connman && g_connman->GetNetworkActive(); } + Amount getMinimumFee(unsigned int tx_bytes, + const CCoinControl &coin_control) override { + Amount result; + CHECK_WALLET(result = + GetMinimumFee(tx_bytes, g_mempool, coin_control)); + return result; + } Amount getMaxTxFee() override { return ::maxTxFee; } + CFeeRate getDustRelayFee() override { return ::dustRelayFee; } + CFeeRate getPayTxFee() override { CHECK_WALLET(return ::payTxFee); } UniValue executeRpc(Config &config, const std::string &command, const UniValue ¶ms, const std::string &uri) override { JSONRPCRequest req; req.params = params; req.strMethod = command; req.URI = uri; return ::tableRPC.execute(config, req); } std::vector<std::string> listRpcCommands() override { return ::tableRPC.listCommands(); } void rpcSetTimerInterfaceIfUnset(RPCTimerInterface *iface) override { RPCSetTimerInterfaceIfUnset(iface); } void rpcUnsetTimerInterface(RPCTimerInterface *iface) override { RPCUnsetTimerInterface(iface); } std::vector<std::unique_ptr<Wallet>> getWallets() override { #ifdef ENABLE_WALLET std::vector<std::unique_ptr<Wallet>> wallets; for (CWalletRef wallet : ::vpwallets) { wallets.emplace_back(MakeWallet(*wallet)); } return wallets; #else throw std::logic_error( "Node::getWallets() called in non-wallet build."); #endif } std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) override { return MakeHandler(::uiInterface.InitMessage.connect(fn)); } std::unique_ptr<Handler> handleMessageBox(MessageBoxFn fn) override { return MakeHandler(::uiInterface.ThreadSafeMessageBox.connect(fn)); } std::unique_ptr<Handler> handleQuestion(QuestionFn fn) override { return MakeHandler(::uiInterface.ThreadSafeQuestion.connect(fn)); } std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) override { return MakeHandler(::uiInterface.ShowProgress.connect(fn)); } std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) override { CHECK_WALLET(return MakeHandler(::uiInterface.LoadWallet.connect( [fn](CWallet *wallet) { fn(MakeWallet(*wallet)); }))); } std::unique_ptr<Handler> handleNotifyNumConnectionsChanged( NotifyNumConnectionsChangedFn fn) override { return MakeHandler( ::uiInterface.NotifyNumConnectionsChanged.connect(fn)); } std::unique_ptr<Handler> handleNotifyNetworkActiveChanged( NotifyNetworkActiveChangedFn fn) override { return MakeHandler( ::uiInterface.NotifyNetworkActiveChanged.connect(fn)); } std::unique_ptr<Handler> handleNotifyAlertChanged(NotifyAlertChangedFn fn) override { return MakeHandler(::uiInterface.NotifyAlertChanged.connect(fn)); } std::unique_ptr<Handler> handleBannedListChanged(BannedListChangedFn fn) override { return MakeHandler(::uiInterface.BannedListChanged.connect(fn)); } std::unique_ptr<Handler> handleNotifyBlockTip(NotifyBlockTipFn fn) override { return MakeHandler(::uiInterface.NotifyBlockTip.connect( [fn](bool initial_download, const CBlockIndex *block) { fn(initial_download, block->nHeight, block->GetBlockTime(), GuessVerificationProgress(Params().TxData(), block)); })); } std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) override { return MakeHandler(::uiInterface.NotifyHeaderTip.connect( [fn](bool initial_download, const CBlockIndex *block) { fn(initial_download, block->nHeight, block->GetBlockTime(), GuessVerificationProgress(Params().TxData(), block)); })); } }; } // namespace std::unique_ptr<Node> MakeNode() { return std::make_unique<NodeImpl>(); } } // namespace interfaces diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 3fab620409..bb4b3f3c1f 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -1,239 +1,251 @@ // Copyright (c) 2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_INTERFACES_NODE_H #define BITCOIN_INTERFACES_NODE_H #include <addrdb.h> // For banmap_t #include <amount.h> // For Amount #include <init.h> // For HelpMessageMode #include <net.h> // For CConnman::NumConnections #include <netaddress.h> // For Network #include <cstddef> #include <cstdint> #include <functional> #include <memory> #include <string> #include <tuple> #include <vector> +class CCoinControl; +class CFeeRate; struct CNodeStateStats; struct CNodeStats; class Config; class HTTPRPCRequestProcessor; class proxyType; class RPCServer; class RPCTimerInterface; class UniValue; namespace interfaces { class Handler; class Wallet; //! Top-level interface for a bitcoin node (bitcoind process). class Node { public: virtual ~Node() {} //! Set command line arguments. virtual void parseParameters(int argc, const char *const argv[]) = 0; //! Set a command line argument if it doesn't already have a value virtual bool softSetArg(const std::string &arg, const std::string &value) = 0; //! Set a command line boolean argument if it doesn't already have a value virtual bool softSetBoolArg(const std::string &arg, bool value) = 0; //! Load settings from configuration file. virtual void readConfigFile(const std::string &conf_path) = 0; //! Choose network parameters. virtual void selectParams(const std::string &network) = 0; //! Init logging. virtual void initLogging() = 0; //! Init parameter interaction. virtual void initParameterInteraction() = 0; //! Get warnings. virtual std::string getWarnings(const std::string &type) = 0; //! Initialize app dependencies. virtual bool baseInitialize(Config &config, RPCServer &rpcServer) = 0; //! Start node. virtual bool appInitMain(Config &config, HTTPRPCRequestProcessor &httpRPCRequestProcessor) = 0; //! Stop node. virtual void appShutdown() = 0; //! Start shutdown. virtual void startShutdown() = 0; //! Return whether shutdown was requested. virtual bool shutdownRequested() = 0; //! Get help message string. virtual std::string helpMessage(HelpMessageMode mode) = 0; //! Map port. virtual void mapPort(bool use_upnp) = 0; //! Get proxy. virtual bool getProxy(Network net, proxyType &proxy_info) = 0; //! Get number of connections. virtual size_t getNodeCount(CConnman::NumConnections flags) = 0; //! Get stats for connected nodes. using NodesStats = std::vector<std::tuple<CNodeStats, bool, CNodeStateStats>>; virtual bool getNodesStats(NodesStats &stats) = 0; //! Get ban map entries. virtual bool getBanned(banmap_t &banmap) = 0; //! Ban node. virtual bool ban(const CNetAddr &net_addr, BanReason reason, int64_t ban_time_offset) = 0; //! Unban node. virtual bool unban(const CSubNet &ip) = 0; //! Disconnect node. virtual bool disconnect(NodeId id) = 0; //! Get total bytes recv. virtual int64_t getTotalBytesRecv() = 0; //! Get total bytes sent. virtual int64_t getTotalBytesSent() = 0; //! Get mempool size. virtual size_t getMempoolSize() = 0; //! Get mempool dynamic usage. virtual size_t getMempoolDynamicUsage() = 0; //! Get header tip height and time. virtual bool getHeaderTip(int &height, int64_t &block_time) = 0; //! Get num blocks. virtual int getNumBlocks() = 0; //! Get last block time. virtual int64_t getLastBlockTime() = 0; //! Get verification progress. virtual double getVerificationProgress() = 0; //! Is initial block download. virtual bool isInitialBlockDownload() = 0; //! Get reindex. virtual bool getReindex() = 0; //! Get importing. virtual bool getImporting() = 0; //! Set network active. virtual void setNetworkActive(bool active) = 0; //! Get network active. virtual bool getNetworkActive() = 0; + //! Get minimum fee. + virtual Amount getMinimumFee(unsigned int tx_bytes, + const CCoinControl &coin_control) = 0; + //! Get max tx fee. virtual Amount getMaxTxFee() = 0; + //! Get dust relay fee. + virtual CFeeRate getDustRelayFee() = 0; + + //! Get pay tx fee. + virtual CFeeRate getPayTxFee() = 0; + //! Execute rpc command. virtual UniValue executeRpc(Config &config, const std::string &command, const UniValue ¶ms, const std::string &uri) = 0; //! List rpc commands. virtual std::vector<std::string> listRpcCommands() = 0; //! Set RPC timer interface if unset. virtual void rpcSetTimerInterfaceIfUnset(RPCTimerInterface *iface) = 0; //! Unset RPC timer interface. virtual void rpcUnsetTimerInterface(RPCTimerInterface *iface) = 0; //! Return interfaces for accessing wallets (if any). virtual std::vector<std::unique_ptr<Wallet>> getWallets() = 0; //! Register handler for init messages. using InitMessageFn = std::function<void(const std::string &message)>; virtual std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) = 0; //! Register handler for message box messages. using MessageBoxFn = std::function<bool(const std::string &message, const std::string &caption, unsigned int style)>; virtual std::unique_ptr<Handler> handleMessageBox(MessageBoxFn fn) = 0; //! Register handler for question messages. using QuestionFn = std::function<bool( const std::string &message, const std::string &non_interactive_message, const std::string &caption, unsigned int style)>; virtual std::unique_ptr<Handler> handleQuestion(QuestionFn fn) = 0; //! Register handler for progress messages. using ShowProgressFn = std::function<void( const std::string &title, int progress, bool resume_possible)>; virtual std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) = 0; //! Register handler for load wallet messages. using LoadWalletFn = std::function<void(std::unique_ptr<Wallet> wallet)>; virtual std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) = 0; //! Register handler for number of connections changed messages. using NotifyNumConnectionsChangedFn = std::function<void(int new_num_connections)>; virtual std::unique_ptr<Handler> handleNotifyNumConnectionsChanged(NotifyNumConnectionsChangedFn fn) = 0; //! Register handler for network active messages. using NotifyNetworkActiveChangedFn = std::function<void(bool network_active)>; virtual std::unique_ptr<Handler> handleNotifyNetworkActiveChanged(NotifyNetworkActiveChangedFn fn) = 0; //! Register handler for notify alert messages. using NotifyAlertChangedFn = std::function<void()>; virtual std::unique_ptr<Handler> handleNotifyAlertChanged(NotifyAlertChangedFn fn) = 0; //! Register handler for ban list messages. using BannedListChangedFn = std::function<void()>; virtual std::unique_ptr<Handler> handleBannedListChanged(BannedListChangedFn fn) = 0; //! Register handler for block tip messages. using NotifyBlockTipFn = std::function<void(bool initial_download, int height, int64_t block_time, double verification_progress)>; virtual std::unique_ptr<Handler> handleNotifyBlockTip(NotifyBlockTipFn fn) = 0; //! Register handler for header tip messages. using NotifyHeaderTipFn = std::function<void(bool initial_download, int height, int64_t block_time, double verification_progress)>; virtual std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) = 0; }; //! Return implementation of Node interface. std::unique_ptr<Node> MakeNode(); } // namespace interfaces #endif // BITCOIN_INTERFACES_NODE_H diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index b6525415b1..6c64792173 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -1,232 +1,274 @@ // Copyright (c) 2018 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 <interfaces/wallet.h> #include <amount.h> #include <chain.h> #include <consensus/validation.h> #include <interfaces/handler.h> #include <net.h> #include <policy/policy.h> #include <primitives/transaction.h> #include <script/ismine.h> #include <script/standard.h> #include <support/allocators/secure.h> #include <sync.h> #include <ui_interface.h> #include <validation.h> #include <wallet/wallet.h> #include <memory> namespace interfaces { namespace { class PendingWalletTxImpl : public PendingWalletTx { public: PendingWalletTxImpl(CWallet &wallet) : m_wallet(wallet), m_key(&wallet) {} const CTransaction &get() override { return *m_tx; } bool commit(WalletValueMap value_map, WalletOrderForm order_form, std::string from_account, std::string &reject_reason) override { LOCK2(cs_main, m_wallet.cs_wallet); CValidationState state; if (!m_wallet.CommitTransaction( m_tx, std::move(value_map), std::move(order_form), std::move(from_account), m_key, g_connman.get(), state)) { reject_reason = state.GetRejectReason(); return false; } return true; } CTransactionRef m_tx; CWallet &m_wallet; CReserveKey m_key; }; + //! Construct wallet TxOut struct. + WalletTxOut MakeWalletTxOut(CWallet &wallet, const CWalletTx &wtx, int n, + int depth) { + WalletTxOut result; + result.txout = wtx.tx->vout[n]; + result.time = wtx.GetTxTime(); + result.depth_in_main_chain = depth; + result.is_spent = wallet.IsSpent(wtx.GetId(), n); + return result; + } + class WalletImpl : public Wallet { public: WalletImpl(CWallet &wallet) : m_wallet(wallet) {} bool encryptWallet(const SecureString &wallet_passphrase) override { return m_wallet.EncryptWallet(wallet_passphrase); } bool isCrypted() override { return m_wallet.IsCrypted(); } bool lock() override { return m_wallet.Lock(); } bool unlock(const SecureString &wallet_passphrase) override { return m_wallet.Unlock(wallet_passphrase); } bool isLocked() override { return m_wallet.IsLocked(); } bool changeWalletPassphrase( const SecureString &old_wallet_passphrase, const SecureString &new_wallet_passphrase) override { return m_wallet.ChangeWalletPassphrase(old_wallet_passphrase, new_wallet_passphrase); } bool backupWallet(const std::string &filename) override { return m_wallet.BackupWallet(filename); } std::string getWalletName() override { return m_wallet.GetName(); } const CChainParams &getChainParams() override { return m_wallet.chainParams; } bool getPubKey(const CKeyID &address, CPubKey &pub_key) override { return m_wallet.GetPubKey(address, pub_key); } bool getPrivKey(const CKeyID &address, CKey &key) override { return m_wallet.GetKey(address, key); } bool isSpendable(const CTxDestination &dest) override { return IsMine(m_wallet, dest) & ISMINE_SPENDABLE; } bool haveWatchOnly() override { return m_wallet.HaveWatchOnly(); }; bool setAddressBook(const CTxDestination &dest, const std::string &name, const std::string &purpose) override { return m_wallet.SetAddressBook(dest, name, purpose); } bool getAddress(const CTxDestination &dest, std::string *name, isminetype *is_mine) override { LOCK(m_wallet.cs_wallet); auto it = m_wallet.mapAddressBook.find(dest); if (it == m_wallet.mapAddressBook.end()) { return false; } if (name) { *name = it->second.name; } if (is_mine) { *is_mine = IsMine(m_wallet, dest); } return true; } bool addDestData(const CTxDestination &dest, const std::string &key, const std::string &value) override { LOCK(m_wallet.cs_wallet); return m_wallet.AddDestData(dest, key, value); } bool eraseDestData(const CTxDestination &dest, const std::string &key) override { LOCK(m_wallet.cs_wallet); return m_wallet.EraseDestData(dest, key); } std::vector<std::string> getDestValues(const std::string &prefix) override { return m_wallet.GetDestValues(prefix); } void lockCoin(const COutPoint &output) override { LOCK2(cs_main, m_wallet.cs_wallet); return m_wallet.LockCoin(output); } void unlockCoin(const COutPoint &output) override { LOCK2(cs_main, m_wallet.cs_wallet); return m_wallet.UnlockCoin(output); } bool isLockedCoin(const COutPoint &output) override { LOCK2(cs_main, m_wallet.cs_wallet); return m_wallet.IsLockedCoin(output.GetTxId(), output.GetN()); } void listLockedCoins(std::vector<COutPoint> &outputs) override { LOCK2(cs_main, m_wallet.cs_wallet); return m_wallet.ListLockedCoins(outputs); } std::unique_ptr<PendingWalletTx> createTransaction(const std::vector<CRecipient> &recipients, const CCoinControl &coin_control, bool sign, int &change_pos, Amount &fee, std::string &fail_reason) override { LOCK2(cs_main, m_wallet.cs_wallet); auto pending = std::make_unique<PendingWalletTxImpl>(m_wallet); if (!m_wallet.CreateTransaction(recipients, pending->m_tx, pending->m_key, fee, change_pos, fail_reason, coin_control, sign)) { return {}; } return std::move(pending); } bool transactionCanBeAbandoned(const TxId &txid) override { return m_wallet.TransactionCanBeAbandoned(txid); } bool abandonTransaction(const TxId &txid) override { LOCK2(cs_main, m_wallet.cs_wallet); return m_wallet.AbandonTransaction(txid); } WalletBalances getBalances() override { WalletBalances result; result.balance = m_wallet.GetBalance(); result.unconfirmed_balance = m_wallet.GetUnconfirmedBalance(); result.immature_balance = m_wallet.GetImmatureBalance(); result.have_watch_only = m_wallet.HaveWatchOnly(); if (result.have_watch_only) { result.watch_only_balance = m_wallet.GetWatchOnlyBalance(); result.unconfirmed_watch_only_balance = m_wallet.GetUnconfirmedWatchOnlyBalance(); result.immature_watch_only_balance = m_wallet.GetImmatureWatchOnlyBalance(); } return result; } bool tryGetBalances(WalletBalances &balances, int &num_blocks) override { TRY_LOCK(cs_main, locked_chain); if (!locked_chain) { return false; } TRY_LOCK(m_wallet.cs_wallet, locked_wallet); if (!locked_wallet) { return false; } balances = getBalances(); num_blocks = ::chainActive.Height(); return true; } Amount getBalance() override { return m_wallet.GetBalance(); } Amount getAvailableBalance(const CCoinControl &coin_control) override { return m_wallet.GetAvailableBalance(&coin_control); } + CoinsList listCoins() override { + LOCK2(::cs_main, m_wallet.cs_wallet); + CoinsList result; + for (const auto &entry : m_wallet.ListCoins()) { + auto &group = result[entry.first]; + for (const auto &coin : entry.second) { + group.emplace_back(COutPoint(coin.tx->GetId(), coin.i), + MakeWalletTxOut(m_wallet, *coin.tx, + coin.i, coin.nDepth)); + } + } + return result; + } + std::vector<WalletTxOut> + getCoins(const std::vector<COutPoint> &outputs) override { + LOCK2(::cs_main, m_wallet.cs_wallet); + std::vector<WalletTxOut> result; + result.reserve(outputs.size()); + for (const auto &output : outputs) { + result.emplace_back(); + auto it = m_wallet.mapWallet.find(output.GetTxId()); + if (it != m_wallet.mapWallet.end()) { + int depth = it->second.GetDepthInMainChain(); + if (depth >= 0) { + result.back() = MakeWalletTxOut(m_wallet, it->second, + output.GetN(), depth); + } + } + } + return result; + } bool hdEnabled() override { return m_wallet.IsHDEnabled(); } std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) override { return MakeHandler(m_wallet.ShowProgress.connect(fn)); } std::unique_ptr<Handler> handleStatusChanged(StatusChangedFn fn) override { return MakeHandler(m_wallet.NotifyStatusChanged.connect( [fn](CCryptoKeyStore *) { fn(); })); } std::unique_ptr<Handler> handleAddressBookChanged(AddressBookChangedFn fn) override { return MakeHandler(m_wallet.NotifyAddressBookChanged.connect( [fn](CWallet *, const CTxDestination &address, const std::string &label, bool is_mine, const std::string &purpose, ChangeType status) { fn(address, label, is_mine, purpose, status); })); } std::unique_ptr<Handler> handleTransactionChanged(TransactionChangedFn fn) override { return MakeHandler(m_wallet.NotifyTransactionChanged.connect( [fn, this](CWallet *, const TxId &txid, ChangeType status) { fn(txid, status); })); } std::unique_ptr<Handler> handleWatchOnlyChanged(WatchOnlyChangedFn fn) override { return MakeHandler(m_wallet.NotifyWatchonlyChanged.connect(fn)); } CWallet &m_wallet; }; } // namespace std::unique_ptr<Wallet> MakeWallet(CWallet &wallet) { return std::make_unique<WalletImpl>(wallet); } } // namespace interfaces diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index e7cd542d59..2d3660fedd 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -1,221 +1,242 @@ // Copyright (c) 2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_INTERFACES_WALLET_H #define BITCOIN_INTERFACES_WALLET_H #include <amount.h> // For Amount +#include <primitives/transaction.h> // For CTxOut #include <script/ismine.h> // For isminefilter, isminetype #include <script/standard.h> // For CTxDestination #include <support/allocators/secure.h> // For SecureString #include <ui_interface.h> // For ChangeType #include <cstdint> #include <functional> #include <map> #include <memory> #include <string> +#include <tuple> #include <utility> #include <vector> class CChainParams; class CCoinControl; class CKey; class CMutableTransaction; class COutPoint; class CTransaction; class CWallet; enum class OutputType; struct CRecipient; struct TxId; namespace interfaces { class Handler; class PendingWalletTx; struct WalletBalances; +struct WalletTxOut; using WalletOrderForm = std::vector<std::pair<std::string, std::string>>; using WalletValueMap = std::map<std::string, std::string>; //! Interface for accessing a wallet. class Wallet { public: virtual ~Wallet() {} //! Encrypt wallet. virtual bool encryptWallet(const SecureString &wallet_passphrase) = 0; //! Return whether wallet is encrypted. virtual bool isCrypted() = 0; //! Lock wallet. virtual bool lock() = 0; //! Unlock wallet. virtual bool unlock(const SecureString &wallet_passphrase) = 0; //! Return whether wallet is locked. virtual bool isLocked() = 0; //! Change wallet passphrase. virtual bool changeWalletPassphrase(const SecureString &old_wallet_passphrase, const SecureString &new_wallet_passphrase) = 0; //! Back up wallet. virtual bool backupWallet(const std::string &filename) = 0; //! Get wallet name. virtual std::string getWalletName() = 0; //! Get chainparams. virtual const CChainParams &getChainParams() = 0; //! Get public key. virtual bool getPubKey(const CKeyID &address, CPubKey &pub_key) = 0; //! Get private key. virtual bool getPrivKey(const CKeyID &address, CKey &key) = 0; //! Return whether wallet has private key. virtual bool isSpendable(const CTxDestination &dest) = 0; //! Return whether wallet has watch only keys. virtual bool haveWatchOnly() = 0; //! Add or update address. virtual bool setAddressBook(const CTxDestination &dest, const std::string &name, const std::string &purpose) = 0; //! Look up address in wallet, return whether exists. virtual bool getAddress(const CTxDestination &dest, std::string *name = nullptr, isminetype *is_mine = nullptr) = 0; //! Add dest data. virtual bool addDestData(const CTxDestination &dest, const std::string &key, const std::string &value) = 0; //! Erase dest data. virtual bool eraseDestData(const CTxDestination &dest, const std::string &key) = 0; //! Get dest values with prefix. virtual std::vector<std::string> getDestValues(const std::string &prefix) = 0; //! Lock coin. virtual void lockCoin(const COutPoint &output) = 0; //! Unlock coin. virtual void unlockCoin(const COutPoint &output) = 0; //! Return whether coin is locked. virtual bool isLockedCoin(const COutPoint &output) = 0; //! List locked coins. virtual void listLockedCoins(std::vector<COutPoint> &outputs) = 0; //! Create transaction. virtual std::unique_ptr<PendingWalletTx> createTransaction(const std::vector<CRecipient> &recipients, const CCoinControl &coin_control, bool sign, int &change_pos, Amount &fee, std::string &fail_reason) = 0; //! Return whether transaction can be abandoned. virtual bool transactionCanBeAbandoned(const TxId &txid) = 0; //! Abandon transaction. virtual bool abandonTransaction(const TxId &txid) = 0; //! Get balances. virtual WalletBalances getBalances() = 0; //! Get balances if possible without blocking. virtual bool tryGetBalances(WalletBalances &balances, int &num_blocks) = 0; //! Get balance. virtual Amount getBalance() = 0; //! Get available balance. virtual Amount getAvailableBalance(const CCoinControl &coin_control) = 0; + //! Return AvailableCoins + LockedCoins grouped by wallet address. + //! (put change in one group with wallet address) + using CoinsList = std::map<CTxDestination, + std::vector<std::tuple<COutPoint, WalletTxOut>>>; + virtual CoinsList listCoins() = 0; + + //! Return wallet transaction output information. + virtual std::vector<WalletTxOut> + getCoins(const std::vector<COutPoint> &outputs) = 0; + // Return whether HD enabled. virtual bool hdEnabled() = 0; //! Register handler for show progress messages. using ShowProgressFn = std::function<void(const std::string &title, int progress)>; virtual std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) = 0; //! Register handler for status changed messages. using StatusChangedFn = std::function<void()>; virtual std::unique_ptr<Handler> handleStatusChanged(StatusChangedFn fn) = 0; //! Register handler for address book changed messages. using AddressBookChangedFn = std::function<void( const CTxDestination &address, const std::string &label, bool is_mine, const std::string &purpose, ChangeType status)>; virtual std::unique_ptr<Handler> handleAddressBookChanged(AddressBookChangedFn fn) = 0; //! Register handler for transaction changed messages. using TransactionChangedFn = std::function<void(const TxId &txid, ChangeType status)>; virtual std::unique_ptr<Handler> handleTransactionChanged(TransactionChangedFn fn) = 0; //! Register handler for watchonly changed messages. using WatchOnlyChangedFn = std::function<void(bool have_watch_only)>; virtual std::unique_ptr<Handler> handleWatchOnlyChanged(WatchOnlyChangedFn fn) = 0; }; //! Tracking object returned by CreateTransaction and passed to //! CommitTransaction. class PendingWalletTx { public: virtual ~PendingWalletTx() {} //! Get transaction data. virtual const CTransaction &get() = 0; //! Send pending transaction and commit to wallet. virtual bool commit(WalletValueMap value_map, WalletOrderForm order_form, std::string from_account, std::string &reject_reason) = 0; }; //! Collection of wallet balances. struct WalletBalances { Amount balance = Amount::zero(); Amount unconfirmed_balance = Amount::zero(); Amount immature_balance = Amount::zero(); bool have_watch_only = false; Amount watch_only_balance = Amount::zero(); Amount unconfirmed_watch_only_balance = Amount::zero(); Amount immature_watch_only_balance = Amount::zero(); bool balanceChanged(const WalletBalances &prev) const { return balance != prev.balance || unconfirmed_balance != prev.unconfirmed_balance || immature_balance != prev.immature_balance || watch_only_balance != prev.watch_only_balance || unconfirmed_watch_only_balance != prev.unconfirmed_watch_only_balance || immature_watch_only_balance != prev.immature_watch_only_balance; } }; +//! Wallet transaction output. +struct WalletTxOut { + CTxOut txout; + int64_t time; + int depth_in_main_chain = -1; + bool is_spent = false; +}; + //! Return implementation of Wallet interface. This function will be undefined //! in builds where ENABLE_WALLET is false. std::unique_ptr<Wallet> MakeWallet(CWallet &wallet); } // namespace interfaces #endif // BITCOIN_INTERFACES_WALLET_H diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 4deeb972d7..b173178cdb 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -1,836 +1,834 @@ // Copyright (c) 2011-2016 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 "coincontroldialog.h" -#include "ui_coincontroldialog.h" - -#include "addresstablemodel.h" -#include "bitcoinunits.h" -#include "guiutil.h" -#include "optionsmodel.h" -#include "platformstyle.h" -#include "txmempool.h" -#include "walletmodel.h" - -#include "dstencode.h" -#include "init.h" -#include "policy/policy.h" -#include "validation.h" // For mempool -#include "wallet/coincontrol.h" -#include "wallet/fees.h" -#include "wallet/wallet.h" +#include <qt/coincontroldialog.h> +#include <qt/forms/ui_coincontroldialog.h> + +#include <dstencode.h> +#include <interfaces/node.h> +#include <policy/policy.h> +#include <qt/addresstablemodel.h> +#include <qt/bitcoinunits.h> +#include <qt/guiutil.h> +#include <qt/optionsmodel.h> +#include <qt/platformstyle.h> +#include <qt/walletmodel.h> +#include <txmempool.h> +#include <validation.h> // For mempool +#include <wallet/coincontrol.h> +#include <wallet/fees.h> +#include <wallet/wallet.h> #include <QApplication> #include <QCheckBox> #include <QCursor> #include <QDialogButtonBox> #include <QFlags> #include <QIcon> #include <QSettings> #include <QString> #include <QTreeWidget> #include <QTreeWidgetItem> QList<Amount> CoinControlDialog::payAmounts; bool CoinControlDialog::fSubtractFeeFromAmount = false; bool CCoinControlWidgetItem::operator<(const QTreeWidgetItem &other) const { int column = treeWidget()->sortColumn(); if (column == CoinControlDialog::COLUMN_AMOUNT || column == CoinControlDialog::COLUMN_DATE || column == CoinControlDialog::COLUMN_CONFIRMATIONS) return data(column, Qt::UserRole).toLongLong() < other.data(column, Qt::UserRole).toLongLong(); return QTreeWidgetItem::operator<(other); } CoinControlDialog::CoinControlDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::CoinControlDialog), model(0), platformStyle(_platformStyle) { ui->setupUi(this); // context menu actions QAction *copyAddressAction = new QAction(tr("Copy address"), this); QAction *copyLabelAction = new QAction(tr("Copy label"), this); QAction *copyAmountAction = new QAction(tr("Copy amount"), this); // we need to enable/disable this copyTransactionHashAction = new QAction(tr("Copy transaction ID"), this); // we need to enable/disable this lockAction = new QAction(tr("Lock unspent"), this); // we need to enable/disable this unlockAction = new QAction(tr("Unlock unspent"), this); // context menu contextMenu = new QMenu(this); contextMenu->addAction(copyAddressAction); contextMenu->addAction(copyLabelAction); contextMenu->addAction(copyAmountAction); contextMenu->addAction(copyTransactionHashAction); contextMenu->addSeparator(); contextMenu->addAction(lockAction); contextMenu->addAction(unlockAction); // context menu signals connect(ui->treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); connect(copyTransactionHashAction, SIGNAL(triggered()), this, SLOT(copyTransactionHash())); connect(lockAction, SIGNAL(triggered()), this, SLOT(lockCoin())); connect(unlockAction, SIGNAL(triggered()), this, SLOT(unlockCoin())); // clipboard actions QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(clipboardQuantity())); connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(clipboardAmount())); connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(clipboardFee())); connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(clipboardAfterFee())); connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(clipboardBytes())); connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(clipboardLowOutput())); connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(clipboardChange())); ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); ui->labelCoinControlAmount->addAction(clipboardAmountAction); ui->labelCoinControlFee->addAction(clipboardFeeAction); ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); ui->labelCoinControlBytes->addAction(clipboardBytesAction); ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); ui->labelCoinControlChange->addAction(clipboardChangeAction); // toggle tree/list mode connect(ui->radioTreeMode, SIGNAL(toggled(bool)), this, SLOT(radioTreeMode(bool))); connect(ui->radioListMode, SIGNAL(toggled(bool)), this, SLOT(radioListMode(bool))); // click on checkbox connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem *, int)), this, SLOT(viewItemChanged(QTreeWidgetItem *, int))); // click on header ui->treeWidget->header()->setSectionsClickable(true); connect(ui->treeWidget->header(), SIGNAL(sectionClicked(int)), this, SLOT(headerSectionClicked(int))); // ok button connect(ui->buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(buttonBoxClicked(QAbstractButton *))); // (un)select all connect(ui->pushButtonSelectAll, SIGNAL(clicked()), this, SLOT(buttonSelectAllClicked())); // change coin control first column label due Qt4 bug. // see https://github.com/bitcoin/bitcoin/issues/5716 ui->treeWidget->headerItem()->setText(COLUMN_CHECKBOX, QString()); ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84); ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 110); ui->treeWidget->setColumnWidth(COLUMN_LABEL, 190); ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 320); ui->treeWidget->setColumnWidth(COLUMN_DATE, 130); ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 110); // store transaction hash in this column, but don't show it ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true); // store vout index in this column, but don't show it ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true); // default view is sorted by amount desc sortView(COLUMN_AMOUNT, Qt::DescendingOrder); // restore list mode and sortorder as a convenience feature QSettings settings; if (settings.contains("nCoinControlMode") && !settings.value("nCoinControlMode").toBool()) ui->radioTreeMode->click(); if (settings.contains("nCoinControlSortColumn") && settings.contains("nCoinControlSortOrder")) sortView(settings.value("nCoinControlSortColumn").toInt(), (static_cast<Qt::SortOrder>( settings.value("nCoinControlSortOrder").toInt()))); } CoinControlDialog::~CoinControlDialog() { QSettings settings; settings.setValue("nCoinControlMode", ui->radioListMode->isChecked()); settings.setValue("nCoinControlSortColumn", sortColumn); settings.setValue("nCoinControlSortOrder", (int)sortOrder); delete ui; } void CoinControlDialog::setModel(WalletModel *_model) { this->model = _model; if (_model && _model->getOptionsModel() && _model->getAddressTableModel()) { updateView(); updateLabelLocked(); CoinControlDialog::updateLabels(_model, this); } } // ok button void CoinControlDialog::buttonBoxClicked(QAbstractButton *button) { if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { // closes the dialog done(QDialog::Accepted); } } // (un)select all void CoinControlDialog::buttonSelectAllClicked() { Qt::CheckState state = Qt::Checked; for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) { if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != Qt::Unchecked) { state = Qt::Unchecked; break; } } ui->treeWidget->setEnabled(false); for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != state) { ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, state); } ui->treeWidget->setEnabled(true); if (state == Qt::Unchecked) { // just to be sure coinControl()->UnSelectAll(); } CoinControlDialog::updateLabels(model, this); } // context menu void CoinControlDialog::showMenu(const QPoint &point) { QTreeWidgetItem *item = ui->treeWidget->itemAt(point); if (item) { contextMenuItem = item; // disable some items (like Copy Transaction ID, lock, unlock) for tree // roots in context menu if (item->text(COLUMN_TXHASH).length() == 64) { TxId txid; txid.SetHex(item->text(COLUMN_TXHASH).toStdString()); // transaction hash is 64 characters (this means its a child node, // so its not a parent node in tree mode) copyTransactionHashAction->setEnabled(true); if (model->wallet().isLockedCoin( COutPoint(txid, item->text(COLUMN_VOUT_INDEX).toUInt()))) { lockAction->setEnabled(false); unlockAction->setEnabled(true); } else { lockAction->setEnabled(true); unlockAction->setEnabled(false); } } else { // this means click on parent node in tree mode -> disable all copyTransactionHashAction->setEnabled(false); lockAction->setEnabled(false); unlockAction->setEnabled(false); } // show context menu contextMenu->exec(QCursor::pos()); } } // context menu action: copy amount void CoinControlDialog::copyAmount() { GUIUtil::setClipboard( BitcoinUnits::removeSpaces(contextMenuItem->text(COLUMN_AMOUNT))); } // context menu action: copy label void CoinControlDialog::copyLabel() { if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_LABEL).length() == 0 && contextMenuItem->parent()) { GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_LABEL)); } else { GUIUtil::setClipboard(contextMenuItem->text(COLUMN_LABEL)); } } // context menu action: copy address void CoinControlDialog::copyAddress() { if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_ADDRESS).length() == 0 && contextMenuItem->parent()) { GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_ADDRESS)); } else { GUIUtil::setClipboard(contextMenuItem->text(COLUMN_ADDRESS)); } } // context menu action: copy transaction id void CoinControlDialog::copyTransactionHash() { GUIUtil::setClipboard(contextMenuItem->text(COLUMN_TXHASH)); } // context menu action: lock coin void CoinControlDialog::lockCoin() { if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked) { contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); } COutPoint outpt( uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); model->wallet().lockCoin(outpt); contextMenuItem->setDisabled(true); contextMenuItem->setIcon( COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); updateLabelLocked(); } // context menu action: unlock coin void CoinControlDialog::unlockCoin() { COutPoint outpt( uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); model->wallet().unlockCoin(outpt); contextMenuItem->setDisabled(false); contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon()); updateLabelLocked(); } // copy label "Quantity" to clipboard void CoinControlDialog::clipboardQuantity() { GUIUtil::setClipboard(ui->labelCoinControlQuantity->text()); } // copy label "Amount" to clipboard void CoinControlDialog::clipboardAmount() { GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left( ui->labelCoinControlAmount->text().indexOf(" "))); } // copy label "Fee" to clipboard void CoinControlDialog::clipboardFee() { GUIUtil::setClipboard( ui->labelCoinControlFee->text() .left(ui->labelCoinControlFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // copy label "After fee" to clipboard void CoinControlDialog::clipboardAfterFee() { GUIUtil::setClipboard( ui->labelCoinControlAfterFee->text() .left(ui->labelCoinControlAfterFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // copy label "Bytes" to clipboard void CoinControlDialog::clipboardBytes() { GUIUtil::setClipboard( ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, "")); } // copy label "Dust" to clipboard void CoinControlDialog::clipboardLowOutput() { GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text()); } // copy label "Change" to clipboard void CoinControlDialog::clipboardChange() { GUIUtil::setClipboard( ui->labelCoinControlChange->text() .left(ui->labelCoinControlChange->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // treeview: sort void CoinControlDialog::sortView(int column, Qt::SortOrder order) { sortColumn = column; sortOrder = order; ui->treeWidget->sortItems(column, order); ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); } // treeview: clicked on header void CoinControlDialog::headerSectionClicked(int logicalIndex) { // click on most left column -> do nothing if (logicalIndex == COLUMN_CHECKBOX) { ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); } else { if (sortColumn == logicalIndex) { sortOrder = ((sortOrder == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder); } else { sortColumn = logicalIndex; // if label or address then default => asc, else default => desc sortOrder = ((sortColumn == COLUMN_LABEL || sortColumn == COLUMN_ADDRESS) ? Qt::AscendingOrder : Qt::DescendingOrder); } sortView(sortColumn, sortOrder); } } // toggle tree mode void CoinControlDialog::radioTreeMode(bool checked) { if (checked && model) { updateView(); } } // toggle list mode void CoinControlDialog::radioListMode(bool checked) { if (checked && model) { updateView(); } } // checkbox clicked by user void CoinControlDialog::viewItemChanged(QTreeWidgetItem *item, int column) { // transaction hash is 64 characters (this means its a child node, so its // not a parent node in tree mode) if (column == COLUMN_CHECKBOX && item->text(COLUMN_TXHASH).length() == 64) { COutPoint outpt(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()); if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) { coinControl()->UnSelect(outpt); } else if (item->isDisabled()) { // locked (this happens if "check all" through parent node) item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); } else { coinControl()->Select(outpt); } // selection changed -> update labels if (ui->treeWidget->isEnabled()) { // do not update on every click for (un)select all CoinControlDialog::updateLabels(model, this); } } // TODO: Remove this temporary qt5 fix after Qt5.3 and Qt5.4 are no longer // used. // Fixed in Qt5.5 and above: https://bugreports.qt.io/browse/QTBUG-43473 else if (column == COLUMN_CHECKBOX && item->childCount() > 0) { if (item->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked && item->child(0)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) { item->setCheckState(COLUMN_CHECKBOX, Qt::Checked); } } } // shows count of locked unspent outputs void CoinControlDialog::updateLabelLocked() { std::vector<COutPoint> vOutpts; model->wallet().listLockedCoins(vOutpts); if (vOutpts.size() > 0) { ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size())); ui->labelLocked->setVisible(true); } else { ui->labelLocked->setVisible(false); } } void CoinControlDialog::updateLabels(WalletModel *model, QDialog *dialog) { if (!model) { return; } // nPayAmount Amount nPayAmount = Amount::zero(); bool fDust = false; CMutableTransaction txDummy; for (const Amount amount : CoinControlDialog::payAmounts) { nPayAmount += amount; if (amount > Amount::zero()) { CTxOut txout(amount, static_cast<CScript>(std::vector<uint8_t>(24, 0))); txDummy.vout.push_back(txout); - if (txout.IsDust(dustRelayFee)) { + if (txout.IsDust(model->node().getDustRelayFee())) { fDust = true; } } } Amount nAmount = Amount::zero(); Amount nPayFee = Amount::zero(); Amount nAfterFee = Amount::zero(); Amount nChange = Amount::zero(); unsigned int nBytes = 0; unsigned int nBytesInputs = 0; unsigned int nQuantity = 0; int nQuantityUncompressed = 0; std::vector<COutPoint> vCoinControl; - std::vector<COutput> vOutputs; coinControl()->ListSelected(vCoinControl); - model->getOutputs(vCoinControl, vOutputs); - for (const COutput &out : vOutputs) { + size_t i = 0; + for (const auto &out : model->wallet().getCoins(vCoinControl)) { + if (out.depth_in_main_chain < 0) { + continue; + } + // unselect already spent, very unlikely scenario, this could happen // when selected are spent elsewhere, like rpc or another computer - uint256 txhash = out.tx->GetId(); - COutPoint outpt(txhash, out.i); - if (model->isSpent(outpt)) { + const COutPoint &outpt = vCoinControl[i++]; + if (out.is_spent) { coinControl()->UnSelect(outpt); continue; } // Quantity nQuantity++; // Amount - nAmount += out.tx->tx->vout[out.i].nValue; + nAmount += out.txout.nValue; // Bytes CTxDestination address; - if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, address)) { + if (ExtractDestination(out.txout.scriptPubKey, address)) { CPubKey pubkey; CKeyID *keyid = boost::get<CKeyID>(&address); if (keyid && model->wallet().getPubKey(*keyid, pubkey)) { nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); if (!pubkey.IsCompressed()) { nQuantityUncompressed++; } } else { // in all error cases, simply assume 148 here nBytesInputs += 148; } } else { nBytesInputs += 148; } } // calculation if (nQuantity > 0) { // Bytes // always assume +1 output for change here nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // in the subtract fee from amount case, we can tell if zero change // already and subtract the bytes, so that fee calculation afterwards is // accurate if (CoinControlDialog::fSubtractFeeFromAmount) { if (nAmount - nPayAmount == Amount::zero()) { nBytes -= 34; } } // Fee - nPayFee = GetMinimumFee(nBytes, g_mempool, *coinControl()); + nPayFee = model->node().getMinimumFee(nBytes, *coinControl()); if (nPayAmount > Amount::zero()) { nChange = nAmount - nPayAmount; if (!CoinControlDialog::fSubtractFeeFromAmount) { nChange -= nPayFee; } // Never create dust outputs; if we would, just add the dust to the // fee. if (nChange > Amount::zero() && nChange < MIN_CHANGE) { CTxOut txout(nChange, static_cast<CScript>(std::vector<uint8_t>(24, 0))); - if (txout.IsDust(dustRelayFee)) { + if (txout.IsDust(model->node().getDustRelayFee())) { // dust-change will be raised until no dust if (CoinControlDialog::fSubtractFeeFromAmount) { - nChange = txout.GetDustThreshold(dustRelayFee); + nChange = txout.GetDustThreshold( + model->node().getDustRelayFee()); } else { nPayFee += nChange; nChange = Amount::zero(); } } } if (nChange == Amount::zero() && !CoinControlDialog::fSubtractFeeFromAmount) { nBytes -= 34; } } // after fee nAfterFee = std::max(nAmount - nPayFee, Amount::zero()); } // actually update labels int nDisplayUnit = BitcoinUnits::BCH; if (model && model->getOptionsModel()) { nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); } QLabel *l1 = dialog->findChild<QLabel *>("labelCoinControlQuantity"); QLabel *l2 = dialog->findChild<QLabel *>("labelCoinControlAmount"); QLabel *l3 = dialog->findChild<QLabel *>("labelCoinControlFee"); QLabel *l4 = dialog->findChild<QLabel *>("labelCoinControlAfterFee"); QLabel *l5 = dialog->findChild<QLabel *>("labelCoinControlBytes"); QLabel *l7 = dialog->findChild<QLabel *>("labelCoinControlLowOutput"); QLabel *l8 = dialog->findChild<QLabel *>("labelCoinControlChange"); // enable/disable "dust" and "change" dialog->findChild<QLabel *>("labelCoinControlLowOutputText") ->setEnabled(nPayAmount > Amount::zero()); dialog->findChild<QLabel *>("labelCoinControlLowOutput") ->setEnabled(nPayAmount > Amount::zero()); dialog->findChild<QLabel *>("labelCoinControlChangeText") ->setEnabled(nPayAmount > Amount::zero()); dialog->findChild<QLabel *>("labelCoinControlChange") ->setEnabled(nPayAmount > Amount::zero()); // stats // Quantity l1->setText(QString::number(nQuantity)); // Amount l2->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAmount)); // Fee l3->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nPayFee)); // After Fee l4->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAfterFee)); // Bytes l5->setText(((nBytes > 0) ? ASYMP_UTF8 : "") + QString::number(nBytes)); // Dust l7->setText(fDust ? tr("yes") : tr("no")); // Change l8->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nChange)); if (nPayFee > Amount::zero()) { l3->setText(ASYMP_UTF8 + l3->text()); l4->setText(ASYMP_UTF8 + l4->text()); if (nChange > Amount::zero() && !CoinControlDialog::fSubtractFeeFromAmount) { l8->setText(ASYMP_UTF8 + l8->text()); } } // turn label red when dust l7->setStyleSheet((fDust) ? "color:red;" : ""); // tool tips QString toolTipDust = tr("This label turns red if any recipient receives an amount smaller " "than the current dust threshold."); // how many satoshis the estimated fee can vary per byte we guess wrong double dFeeVary = GetMinimumFee(1000, g_mempool) / (1000 * SATOSHI); QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary); l3->setToolTip(toolTip4); l4->setToolTip(toolTip4); l7->setToolTip(toolTipDust); l8->setToolTip(toolTip4); dialog->findChild<QLabel *>("labelCoinControlFeeText") ->setToolTip(l3->toolTip()); dialog->findChild<QLabel *>("labelCoinControlAfterFeeText") ->setToolTip(l4->toolTip()); dialog->findChild<QLabel *>("labelCoinControlBytesText") ->setToolTip(l5->toolTip()); dialog->findChild<QLabel *>("labelCoinControlLowOutputText") ->setToolTip(l7->toolTip()); dialog->findChild<QLabel *>("labelCoinControlChangeText") ->setToolTip(l8->toolTip()); // Insufficient funds QLabel *label = dialog->findChild<QLabel *>("labelCoinControlInsuffFunds"); if (label) { label->setVisible(nChange < Amount::zero()); } } CCoinControl *CoinControlDialog::coinControl() { static CCoinControl coin_control; return &coin_control; } void CoinControlDialog::updateView() { if (!model || !model->getOptionsModel() || !model->getAddressTableModel()) { return; } bool treeMode = ui->radioTreeMode->isChecked(); ui->treeWidget->clear(); // performance, otherwise updateLabels would be called for every checked // checkbox ui->treeWidget->setEnabled(false); ui->treeWidget->setAlternatingRowColors(!treeMode); QFlags<Qt::ItemFlag> flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; QFlags<Qt::ItemFlag> flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; int nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); - std::map<QString, std::vector<COutput>> mapCoins; - model->listCoins(mapCoins); - - for (const std::pair<QString, std::vector<COutput>> &coins : mapCoins) { + for (const auto &coins : model->wallet().listCoins()) { CCoinControlWidgetItem *itemWalletAddress = new CCoinControlWidgetItem(); itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); - QString sWalletAddress = coins.first; + QString sWalletAddress = + QString::fromStdString(EncodeDestination(coins.first)); QString sWalletLabel = model->getAddressTableModel()->labelForAddress(sWalletAddress); if (sWalletLabel.isEmpty()) { sWalletLabel = tr("(no label)"); } if (treeMode) { // wallet address ui->treeWidget->addTopLevelItem(itemWalletAddress); itemWalletAddress->setFlags(flgTristate); itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); // label itemWalletAddress->setText(COLUMN_LABEL, sWalletLabel); // address itemWalletAddress->setText(COLUMN_ADDRESS, sWalletAddress); } Amount nSum = Amount::zero(); int nChildren = 0; - for (const COutput &out : coins.second) { - nSum += out.tx->tx->vout[out.i].nValue; + for (const auto &outpair : coins.second) { + const COutPoint &output = std::get<0>(outpair); + const interfaces::WalletTxOut &out = std::get<1>(outpair); + nSum += out.txout.nValue; nChildren++; CCoinControlWidgetItem *itemOutput; if (treeMode) { itemOutput = new CCoinControlWidgetItem(itemWalletAddress); } else { itemOutput = new CCoinControlWidgetItem(ui->treeWidget); } itemOutput->setFlags(flgCheckbox); itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); // address CTxDestination outputAddress; QString sAddress = ""; - if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, - outputAddress)) { + if (ExtractDestination(out.txout.scriptPubKey, outputAddress)) { sAddress = QString::fromStdString(EncodeDestination(outputAddress)); // if listMode or change => show bitcoin address. In tree mode, // address is not shown again for direct wallet address outputs if (!treeMode || (!(sAddress == sWalletAddress))) { itemOutput->setText(COLUMN_ADDRESS, sAddress); } } // label if (!(sAddress == sWalletAddress)) { // change tooltip from where the change comes from itemOutput->setToolTip(COLUMN_LABEL, tr("change from %1 (%2)") .arg(sWalletLabel) .arg(sWalletAddress)); itemOutput->setText(COLUMN_LABEL, tr("(change)")); } else if (!treeMode) { QString sLabel = model->getAddressTableModel()->labelForAddress(sAddress); if (sLabel.isEmpty()) { sLabel = tr("(no label)"); } itemOutput->setText(COLUMN_LABEL, sLabel); } // amount itemOutput->setText( COLUMN_AMOUNT, - BitcoinUnits::format(nDisplayUnit, - out.tx->tx->vout[out.i].nValue)); + BitcoinUnits::format(nDisplayUnit, out.txout.nValue)); // padding so that sorting works correctly itemOutput->setData( COLUMN_AMOUNT, Qt::UserRole, - QVariant(qlonglong(out.tx->tx->vout[out.i].nValue / SATOSHI))); + QVariant(qlonglong(out.txout.nValue / SATOSHI))); // date - itemOutput->setText(COLUMN_DATE, - GUIUtil::dateTimeStr(out.tx->GetTxTime())); + itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.time)); itemOutput->setData(COLUMN_DATE, Qt::UserRole, - QVariant((qlonglong)out.tx->GetTxTime())); + QVariant((qlonglong)out.time)); // confirmations itemOutput->setText(COLUMN_CONFIRMATIONS, - QString::number(out.nDepth)); + QString::number(out.depth_in_main_chain)); itemOutput->setData(COLUMN_CONFIRMATIONS, Qt::UserRole, - QVariant((qlonglong)out.nDepth)); + QVariant((qlonglong)out.depth_in_main_chain)); // transaction id - const TxId txid = out.tx->GetId(); - itemOutput->setText(COLUMN_TXHASH, - QString::fromStdString(txid.GetHex())); + itemOutput->setText(COLUMN_TXHASH, QString::fromStdString( + output.GetTxId().GetHex())); // vout index - itemOutput->setText(COLUMN_VOUT_INDEX, QString::number(out.i)); + itemOutput->setText(COLUMN_VOUT_INDEX, + QString::number(output.GetN())); // disable locked coins - if (model->wallet().isLockedCoin(COutPoint(txid, out.i))) { - COutPoint outpt(txid, out.i); + if (model->wallet().isLockedCoin(output)) { // just to be sure - coinControl()->UnSelect(outpt); + coinControl()->UnSelect(output); itemOutput->setDisabled(true); itemOutput->setIcon( COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); } // set checkbox - if (coinControl()->IsSelected(COutPoint(txid, out.i))) { + if (coinControl()->IsSelected(output)) { itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked); } } // amount if (treeMode) { itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); itemWalletAddress->setText( COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum)); itemWalletAddress->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant(qlonglong(nSum / SATOSHI))); } } // expand all partially selected if (treeMode) { for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) { if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) ui->treeWidget->topLevelItem(i)->setExpanded(true); } } // sort view sortView(sortColumn, sortOrder); ui->treeWidget->setEnabled(true); } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 57515745da..147c1c0ed2 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -1,527 +1,490 @@ // Copyright (c) 2011-2016 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 <qt/walletmodel.h> #include <chain.h> #include <config.h> #include <consensus/validation.h> #include <dstencode.h> #include <interfaces/handler.h> #include <interfaces/node.h> #include <keystore.h> #include <net.h> // for g_connman #include <qt/addresstablemodel.h> #include <qt/guiconstants.h> #include <qt/guiutil.h> #include <qt/paymentserver.h> #include <qt/recentrequeststablemodel.h> #include <qt/transactiontablemodel.h> #include <sync.h> #include <ui_interface.h> #include <util.h> // for GetBoolArg #include <validation.h> #include <wallet/coincontrol.h> #include <wallet/wallet.h> #include <wallet/walletdb.h> // for BackupWallet #include <QDebug> #include <QSet> #include <QTimer> #include <cstdint> WalletModel::WalletModel(std::unique_ptr<interfaces::Wallet> wallet, interfaces::Node &node, const PlatformStyle *platformStyle, CWallet *_wallet, OptionsModel *_optionsModel, QObject *parent) : QObject(parent), m_wallet(std::move(wallet)), m_node(node), cwallet(_wallet), optionsModel(_optionsModel), addressTableModel(0), transactionTableModel(0), recentRequestsTableModel(0), cachedEncryptionStatus(Unencrypted), cachedNumBlocks(0) { fHaveWatchOnly = m_wallet->haveWatchOnly(); fForceCheckBalanceChanged = false; addressTableModel = new AddressTableModel(cwallet, this); transactionTableModel = new TransactionTableModel(platformStyle, cwallet, this); recentRequestsTableModel = new RecentRequestsTableModel(cwallet, this); // This timer will be fired repeatedly to update the balance pollTimer = new QTimer(this); connect(pollTimer, SIGNAL(timeout()), this, SLOT(pollBalanceChanged())); pollTimer->start(MODEL_UPDATE_DELAY); subscribeToCoreSignals(); } WalletModel::~WalletModel() { unsubscribeFromCoreSignals(); } void WalletModel::updateStatus() { EncryptionStatus newEncryptionStatus = getEncryptionStatus(); if (cachedEncryptionStatus != newEncryptionStatus) { Q_EMIT encryptionStatusChanged(); } } void WalletModel::pollBalanceChanged() { // Try to get balances and return early if locks can't be acquired. This // avoids the GUI from getting stuck on periodical polls if the core is // holding the locks for a longer time - for example, during a wallet // rescan. interfaces::WalletBalances new_balances; int numBlocks = -1; if (!m_wallet->tryGetBalances(new_balances, numBlocks)) { return; } if (fForceCheckBalanceChanged || m_node.getNumBlocks() != cachedNumBlocks) { fForceCheckBalanceChanged = false; // Balance and number of transactions might have changed cachedNumBlocks = m_node.getNumBlocks(); checkBalanceChanged(new_balances); if (transactionTableModel) { transactionTableModel->updateConfirmations(); } } } void WalletModel::checkBalanceChanged( const interfaces::WalletBalances &new_balances) { if (new_balances.balanceChanged(m_cached_balances)) { m_cached_balances = new_balances; Q_EMIT balanceChanged( new_balances.balance, new_balances.unconfirmed_balance, new_balances.immature_balance, new_balances.watch_only_balance, new_balances.unconfirmed_watch_only_balance, new_balances.immature_watch_only_balance); } } void WalletModel::updateTransaction() { // Balance and number of transactions might have changed fForceCheckBalanceChanged = true; } void WalletModel::updateAddressBook(const QString &address, const QString &label, bool isMine, const QString &purpose, int status) { if (addressTableModel) { addressTableModel->updateEntry(address, label, isMine, purpose, status); } } void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) { fHaveWatchOnly = fHaveWatchonly; Q_EMIT notifyWatchonlyChanged(fHaveWatchonly); } bool WalletModel::validateAddress(const QString &address) { return IsValidDestinationString(address.toStdString(), getChainParams()); } WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl &coinControl) { Amount total = Amount::zero(); bool fSubtractFeeFromAmount = false; QList<SendCoinsRecipient> recipients = transaction.getRecipients(); std::vector<CRecipient> vecSend; if (recipients.empty()) { return OK; } // Used to detect duplicates QSet<QString> setAddress; int nAddresses = 0; // Pre-check input data for validity for (const SendCoinsRecipient &rcp : recipients) { if (rcp.fSubtractFeeFromAmount) fSubtractFeeFromAmount = true; // PaymentRequest... if (rcp.paymentRequest.IsInitialized()) { Amount subtotal = Amount::zero(); const payments::PaymentDetails &details = rcp.paymentRequest.getDetails(); for (int i = 0; i < details.outputs_size(); i++) { const payments::Output &out = details.outputs(i); if (out.amount() <= 0) { continue; } subtotal += int64_t(out.amount()) * SATOSHI; const uint8_t *scriptStr = (const uint8_t *)out.script().data(); CScript scriptPubKey(scriptStr, scriptStr + out.script().size()); Amount nAmount = int64_t(out.amount()) * SATOSHI; CRecipient recipient = {scriptPubKey, nAmount, rcp.fSubtractFeeFromAmount}; vecSend.push_back(recipient); } if (subtotal <= Amount::zero()) { return InvalidAmount; } total += subtotal; } else { // User-entered bitcoin address / amount: if (!validateAddress(rcp.address)) { return InvalidAddress; } if (rcp.amount <= Amount::zero()) { return InvalidAmount; } setAddress.insert(rcp.address); ++nAddresses; CScript scriptPubKey = GetScriptForDestination( DecodeDestination(rcp.address.toStdString(), getChainParams())); CRecipient recipient = {scriptPubKey, Amount(rcp.amount), rcp.fSubtractFeeFromAmount}; vecSend.push_back(recipient); total += rcp.amount; } } if (setAddress.size() != nAddresses) { return DuplicateAddress; } Amount nBalance = m_wallet->getAvailableBalance(coinControl); if (total > nBalance) { return AmountExceedsBalance; } Amount nFeeRequired = Amount::zero(); int nChangePosRet = -1; std::string strFailReason; auto &newTx = transaction.getWtx(); newTx = m_wallet->createTransaction(vecSend, coinControl, true /* sign */, nChangePosRet, nFeeRequired, strFailReason); transaction.setTransactionFee(nFeeRequired); if (fSubtractFeeFromAmount && newTx) { transaction.reassignAmounts(nChangePosRet); } if (!newTx) { if (!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance) { return SendCoinsReturn(AmountWithFeeExceedsBalance); } Q_EMIT message(tr("Send Coins"), QString::fromStdString(strFailReason), CClientUIInterface::MSG_ERROR); return TransactionCreationFailed; } // reject absurdly high fee. (This can never happen because the // wallet caps the fee at maxTxFee. This merely serves as a // belt-and-suspenders check) if (nFeeRequired > m_node.getMaxTxFee()) { return AbsurdFee; } return SendCoinsReturn(OK); } WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &transaction) { /* store serialized transaction */ QByteArray transaction_array; std::vector<std::pair<std::string, std::string>> vOrderForm; for (const SendCoinsRecipient &rcp : transaction.getRecipients()) { if (rcp.paymentRequest.IsInitialized()) { // Make sure any payment requests involved are still valid. if (PaymentServer::verifyExpired(rcp.paymentRequest.getDetails())) { return PaymentRequestExpired; } // Store PaymentRequests in wtx.vOrderForm in wallet. std::string value; rcp.paymentRequest.SerializeToString(&value); vOrderForm.emplace_back("PaymentRequest", std::move(value)); } else if (!rcp.message.isEmpty()) { // Message from normal bitcoincash:URI // (bitcoincash:123...?message=example) vOrderForm.emplace_back("Message", rcp.message.toStdString()); } } auto &newTx = transaction.getWtx(); std::string rejectReason; if (!newTx->commit({} /* mapValue */, std::move(vOrderForm), {} /* fromAccount */, rejectReason)) { return SendCoinsReturn(TransactionCommitFailed, QString::fromStdString(rejectReason)); } CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << newTx->get(); transaction_array.append(&(ssTx[0]), ssTx.size()); // Add addresses / update labels that we've sent to to the address book, and // emit coinsSent signal for each recipient for (const SendCoinsRecipient &rcp : transaction.getRecipients()) { // Don't touch the address book when we have a payment request if (!rcp.paymentRequest.IsInitialized()) { std::string strAddress = rcp.address.toStdString(); CTxDestination dest = DecodeDestination(strAddress, getChainParams()); std::string strLabel = rcp.label.toStdString(); // Check if we have a new address or an updated label std::string name; if (!m_wallet->getAddress(dest, &name)) { m_wallet->setAddressBook(dest, strLabel, "send"); } else if (name != strLabel) { // "" means don't change purpose m_wallet->setAddressBook(dest, strLabel, ""); } } Q_EMIT coinsSent(cwallet, rcp, transaction_array); } // update balance immediately, otherwise there could be a short noticeable // delay until pollBalanceChanged hits checkBalanceChanged(m_wallet->getBalances()); return SendCoinsReturn(OK); } OptionsModel *WalletModel::getOptionsModel() { return optionsModel; } AddressTableModel *WalletModel::getAddressTableModel() { return addressTableModel; } TransactionTableModel *WalletModel::getTransactionTableModel() { return transactionTableModel; } RecentRequestsTableModel *WalletModel::getRecentRequestsTableModel() { return recentRequestsTableModel; } WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const { if (!m_wallet->isCrypted()) { return Unencrypted; } else if (m_wallet->isLocked()) { return Locked; } else { return Unlocked; } } bool WalletModel::setWalletEncrypted(bool encrypted, const SecureString &passphrase) { if (encrypted) { // Encrypt return m_wallet->encryptWallet(passphrase); } else { // Decrypt -- TODO; not supported yet return false; } } bool WalletModel::setWalletLocked(bool locked, const SecureString &passPhrase) { if (locked) { // Lock return m_wallet->lock(); } else { // Unlock return m_wallet->unlock(passPhrase); } } bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureString &newPass) { // Make sure wallet is locked before attempting pass change m_wallet->lock(); return m_wallet->changeWalletPassphrase(oldPass, newPass); } // Handlers for core signals static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel) { qDebug() << "NotifyKeyStoreStatusChanged"; QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); } static void NotifyAddressBookChanged(WalletModel *walletmodel, const CTxDestination &address, const std::string &label, bool isMine, const std::string &purpose, ChangeType status) { QString strAddress = QString::fromStdString(EncodeDestination(address)); QString strLabel = QString::fromStdString(label); QString strPurpose = QString::fromStdString(purpose); qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status); QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, Q_ARG(QString, strAddress), Q_ARG(QString, strLabel), Q_ARG(bool, isMine), Q_ARG(QString, strPurpose), Q_ARG(int, status)); } static void NotifyTransactionChanged(WalletModel *walletmodel, const TxId &hash, ChangeType status) { Q_UNUSED(hash); Q_UNUSED(status); QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection); } static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress) { // emits signal "showProgress" QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(title)), Q_ARG(int, nProgress)); } static void NotifyWatchonlyChanged(WalletModel *walletmodel, bool fHaveWatchonly) { QMetaObject::invokeMethod(walletmodel, "updateWatchOnlyFlag", Qt::QueuedConnection, Q_ARG(bool, fHaveWatchonly)); } void WalletModel::subscribeToCoreSignals() { // Connect signals to wallet m_handler_status_changed = m_wallet->handleStatusChanged( boost::bind(&NotifyKeyStoreStatusChanged, this)); m_handler_address_book_changed = m_wallet->handleAddressBookChanged( boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); m_handler_transaction_changed = m_wallet->handleTransactionChanged( boost::bind(NotifyTransactionChanged, this, _1, _2)); m_handler_show_progress = m_wallet->handleShowProgress(boost::bind(ShowProgress, this, _1, _2)); m_handler_watch_only_changed = m_wallet->handleWatchOnlyChanged( boost::bind(NotifyWatchonlyChanged, this, _1)); } void WalletModel::unsubscribeFromCoreSignals() { // Disconnect signals from wallet m_handler_status_changed->disconnect(); m_handler_address_book_changed->disconnect(); m_handler_transaction_changed->disconnect(); m_handler_show_progress->disconnect(); m_handler_watch_only_changed->disconnect(); } // WalletModel::UnlockContext implementation WalletModel::UnlockContext WalletModel::requestUnlock() { bool was_locked = getEncryptionStatus() == Locked; if (was_locked) { // Request UI to unlock wallet Q_EMIT requireUnlock(); } // If wallet is still locked, unlock was failed or cancelled, mark context // as invalid bool valid = getEncryptionStatus() != Locked; return UnlockContext(this, valid, was_locked); } WalletModel::UnlockContext::UnlockContext(WalletModel *_wallet, bool _valid, bool _relock) : wallet(_wallet), valid(_valid), relock(_relock) {} WalletModel::UnlockContext::~UnlockContext() { if (valid && relock) { wallet->setWalletLocked(true); } } void WalletModel::UnlockContext::CopyFrom(const UnlockContext &rhs) { // Transfer context; old object no longer relocks wallet *this = rhs; rhs.relock = false; } -// returns a list of COutputs from COutPoints -void WalletModel::getOutputs(const std::vector<COutPoint> &vOutpoints, - std::vector<COutput> &vOutputs) { - LOCK2(cs_main, cwallet->cs_wallet); - for (const COutPoint &outpoint : vOutpoints) { - auto it = cwallet->mapWallet.find(outpoint.GetTxId()); - if (it == cwallet->mapWallet.end()) { - continue; - } - int nDepth = it->second.GetDepthInMainChain(); - if (nDepth < 0) { - continue; - } - COutput out(&it->second, outpoint.GetN(), nDepth, true /* spendable */, - true /* solvable */, true /* safe */); - vOutputs.push_back(out); - } -} - -bool WalletModel::isSpent(const COutPoint &outpoint) const { - LOCK2(cs_main, cwallet->cs_wallet); - return cwallet->IsSpent(outpoint.GetTxId(), outpoint.GetN()); -} - -// AvailableCoins + LockedCoins grouped by wallet address (put change in one -// group with wallet address) -void WalletModel::listCoins( - std::map<QString, std::vector<COutput>> &mapCoins) const { - for (auto &group : cwallet->ListCoins()) { - auto &resultGroup = - mapCoins[QString::fromStdString(EncodeDestination(group.first))]; - for (auto &coin : group.second) { - resultGroup.emplace_back(std::move(coin)); - } - } -} - void WalletModel::loadReceiveRequests( std::vector<std::string> &vReceiveRequests) { // receive request vReceiveRequests = m_wallet->getDestValues("rr"); } bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest) { CTxDestination dest = DecodeDestination(sAddress, getChainParams()); std::stringstream ss; ss << nId; // "rr" prefix = "receive request" in destdata std::string key = "rr" + ss.str(); return sRequest.empty() ? m_wallet->eraseDestData(dest, key) : m_wallet->addDestData(dest, key, sRequest); } bool WalletModel::isWalletEnabled() { return !gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET); } QString WalletModel::getWalletName() const { return QString::fromStdString(m_wallet->getWalletName()); } bool WalletModel::isMultiwallet() { return m_node.getWallets().size() > 1; } const CChainParams &WalletModel::getChainParams() const { return GetConfig().GetChainParams(); } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 77ed02a3e9..0717267f1e 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -1,305 +1,300 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_WALLETMODEL_H #define BITCOIN_QT_WALLETMODEL_H #include <chainparams.h> #include <interfaces/wallet.h> #include <qt/paymentrequestplus.h> #include <qt/walletmodeltransaction.h> #include <support/allocators/secure.h> #include <QObject> #include <map> #include <vector> class AddressTableModel; class OptionsModel; class PlatformStyle; class RecentRequestsTableModel; class TransactionTableModel; class WalletModelTransaction; class CCoinControl; class CKeyID; class COutPoint; class COutput; class CPubKey; namespace interfaces { class Node; } // namespace interface QT_BEGIN_NAMESPACE class QTimer; QT_END_NAMESPACE class SendCoinsRecipient { public: explicit SendCoinsRecipient() : amount(), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) {} explicit SendCoinsRecipient(const QString &addr, const QString &_label, const Amount _amount, const QString &_message) : address(addr), label(_label), amount(_amount), message(_message), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) {} // If from an unauthenticated payment request, this is used for storing the // addresses, e.g. address-A<br />address-B<br />address-C. // Info: As we don't need to process addresses in here when using payment // requests, we can abuse it for displaying an address list. // TOFO: This is a hack, should be replaced with a cleaner solution! QString address; QString label; Amount amount; // If from a payment request, this is used for storing the memo QString message; // If from a payment request, paymentRequest.IsInitialized() will be true PaymentRequestPlus paymentRequest; // Empty if no authentication or invalid signature/cert/etc. QString authenticatedMerchant; // memory only bool fSubtractFeeFromAmount; static const int CURRENT_VERSION = 1; int nVersion; ADD_SERIALIZE_METHODS; template <typename Stream, typename Operation> inline void SerializationOp(Stream &s, Operation ser_action) { std::string sAddress = address.toStdString(); std::string sLabel = label.toStdString(); std::string sMessage = message.toStdString(); std::string sPaymentRequest; if (!ser_action.ForRead() && paymentRequest.IsInitialized()) { paymentRequest.SerializeToString(&sPaymentRequest); } std::string sAuthenticatedMerchant = authenticatedMerchant.toStdString(); READWRITE(this->nVersion); READWRITE(sAddress); READWRITE(sLabel); READWRITE(amount); READWRITE(sMessage); READWRITE(sPaymentRequest); READWRITE(sAuthenticatedMerchant); if (ser_action.ForRead()) { address = QString::fromStdString(sAddress); label = QString::fromStdString(sLabel); message = QString::fromStdString(sMessage); if (!sPaymentRequest.empty()) { paymentRequest.parse(QByteArray::fromRawData( sPaymentRequest.data(), sPaymentRequest.size())); } authenticatedMerchant = QString::fromStdString(sAuthenticatedMerchant); } } }; /** Interface to Bitcoin wallet from Qt view code. */ class WalletModel : public QObject { Q_OBJECT public: explicit WalletModel(std::unique_ptr<interfaces::Wallet> wallet, interfaces::Node &node, const PlatformStyle *platformStyle, CWallet *cwallet, OptionsModel *optionsModel, QObject *parent = nullptr); ~WalletModel(); // Returned by sendCoins enum StatusCode { OK, InvalidAmount, InvalidAddress, AmountExceedsBalance, AmountWithFeeExceedsBalance, DuplicateAddress, // Error returned when wallet is still locked TransactionCreationFailed, TransactionCommitFailed, AbsurdFee, PaymentRequestExpired }; enum EncryptionStatus { // !wallet->IsCrypted() Unencrypted, // wallet->IsCrypted() && wallet->IsLocked() Locked, // wallet->IsCrypted() && !wallet->IsLocked() Unlocked }; OptionsModel *getOptionsModel(); AddressTableModel *getAddressTableModel(); TransactionTableModel *getTransactionTableModel(); RecentRequestsTableModel *getRecentRequestsTableModel(); EncryptionStatus getEncryptionStatus() const; // Check address for validity bool validateAddress(const QString &address); // Return status record for SendCoins, contains error id + information struct SendCoinsReturn { SendCoinsReturn(StatusCode _status = OK, QString _reasonCommitFailed = "") : status(_status), reasonCommitFailed(_reasonCommitFailed) {} StatusCode status; QString reasonCommitFailed; }; // prepare transaction for getting txfee before sending coins SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl &coinControl); // Send coins to a list of recipients SendCoinsReturn sendCoins(WalletModelTransaction &transaction); // Wallet encryption bool setWalletEncrypted(bool encrypted, const SecureString &passphrase); // Passphrase only needed when unlocking bool setWalletLocked(bool locked, const SecureString &passPhrase = SecureString()); bool changePassphrase(const SecureString &oldPass, const SecureString &newPass); // RAI object for unlocking wallet, returned by requestUnlock() class UnlockContext { public: UnlockContext(WalletModel *wallet, bool valid, bool relock); ~UnlockContext(); bool isValid() const { return valid; } // Copy operator and constructor transfer the context UnlockContext(const UnlockContext &obj) { CopyFrom(obj); } UnlockContext &operator=(const UnlockContext &rhs) { CopyFrom(rhs); return *this; } private: WalletModel *wallet; bool valid; // mutable, as it can be set to false by copying mutable bool relock; void CopyFrom(const UnlockContext &rhs); }; UnlockContext requestUnlock(); - void getOutputs(const std::vector<COutPoint> &vOutpoints, - std::vector<COutput> &vOutputs); - bool isSpent(const COutPoint &outpoint) const; - void listCoins(std::map<QString, std::vector<COutput>> &mapCoins) const; - void loadReceiveRequests(std::vector<std::string> &vReceiveRequests); bool saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest); static bool isWalletEnabled(); interfaces::Node &node() const { return m_node; } interfaces::Wallet &wallet() const { return *m_wallet; } const CChainParams &getChainParams() const; QString getWalletName() const; bool isMultiwallet(); private: std::unique_ptr<interfaces::Wallet> m_wallet; std::unique_ptr<interfaces::Handler> m_handler_status_changed; std::unique_ptr<interfaces::Handler> m_handler_address_book_changed; std::unique_ptr<interfaces::Handler> m_handler_transaction_changed; std::unique_ptr<interfaces::Handler> m_handler_show_progress; std::unique_ptr<interfaces::Handler> m_handler_watch_only_changed; interfaces::Node &m_node; CWallet *cwallet; bool fHaveWatchOnly; bool fForceCheckBalanceChanged; // Wallet has an options model for wallet-specific options (transaction fee, // for example) OptionsModel *optionsModel; AddressTableModel *addressTableModel; TransactionTableModel *transactionTableModel; RecentRequestsTableModel *recentRequestsTableModel; // Cache some values to be able to detect changes interfaces::WalletBalances m_cached_balances; EncryptionStatus cachedEncryptionStatus; int cachedNumBlocks; QTimer *pollTimer; void subscribeToCoreSignals(); void unsubscribeFromCoreSignals(); void checkBalanceChanged(const interfaces::WalletBalances &new_balances); Q_SIGNALS: // Signal that balance in wallet changed void balanceChanged(const Amount balance, const Amount unconfirmedBalance, const Amount immatureBalance, const Amount watchOnlyBalance, const Amount watchUnconfBalance, const Amount watchImmatureBalance); // Encryption status of wallet changed void encryptionStatusChanged(); // Signal emitted when wallet needs to be unlocked // It is valid behaviour for listeners to keep the wallet locked after this // signal; this means that the unlocking failed or was cancelled. void requireUnlock(); // Fired when a message should be reported to the user void message(const QString &title, const QString &message, unsigned int style); // Coins sent: from wallet, to recipient, in (serialized) transaction: void coinsSent(CWallet *wallet, SendCoinsRecipient recipient, QByteArray transaction); // Show progress dialog e.g. for rescan void showProgress(const QString &title, int nProgress); // Watch-only address added void notifyWatchonlyChanged(bool fHaveWatchonly); public Q_SLOTS: /** Wallet status might have changed. */ void updateStatus(); /** New transaction, or transaction changed status. */ void updateTransaction(); /** New, updated or removed address book entry. */ void updateAddressBook(const QString &address, const QString &label, bool isMine, const QString &purpose, int status); /** Watch-only added. */ void updateWatchOnlyFlag(bool fHaveWatchonly); /** * Current, immature or unconfirmed balance might have changed - emit * 'balanceChanged' if so. */ void pollBalanceChanged(); }; #endif // BITCOIN_QT_WALLETMODEL_H