diff --git a/src/chain.h b/src/chain.h index 5e80d899f..aea80d729 100644 --- a/src/chain.h +++ b/src/chain.h @@ -1,419 +1,427 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-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_CHAIN_H #define BITCOIN_CHAIN_H #include <arith_uint256.h> #include <blockstatus.h> #include <blockvalidity.h> #include <consensus/params.h> #include <diskblockpos.h> #include <pow.h> #include <primitives/block.h> #include <sync.h> #include <tinyformat.h> #include <uint256.h> #include <unordered_map> #include <vector> /** * Maximum amount of time that a block timestamp is allowed to exceed the * current network-adjusted time before the block will be accepted. */ -static const int64_t MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60; +static constexpr int64_t MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60; /** * Timestamp window used as a grace period by code that compares external * timestamps (such as timestamps passed to RPCs, or wallet key creation times) * to block timestamps. This should be set at least as high as * MAX_FUTURE_BLOCK_TIME. */ -static const int64_t TIMESTAMP_WINDOW = MAX_FUTURE_BLOCK_TIME; +static constexpr int64_t TIMESTAMP_WINDOW = MAX_FUTURE_BLOCK_TIME; + +/** + * Maximum gap between node time and block time used + * for the "Catching up..." mode in GUI. + * + * Ref: https://github.com/bitcoin/bitcoin/pull/1026 + */ +static constexpr int64_t MAX_BLOCK_TIME_GAP = 90 * 60; /** * The block chain is a tree shaped structure starting with the genesis block at * the root, with each block potentially having multiple candidates to be the * next block. A blockindex may have multiple pprev pointing to it, but at most * one of them can be part of the currently active branch. */ class CBlockIndex { public: //! pointer to the hash of the block, if any. Memory is owned by this //! CBlockIndex const uint256 *phashBlock; //! pointer to the index of the predecessor of this block CBlockIndex *pprev; //! pointer to the index of some further predecessor of this block CBlockIndex *pskip; //! height of the entry in the chain. The genesis block has height 0 int nHeight; //! Which # file this block is stored in (blk?????.dat) int nFile; //! Byte offset within blk?????.dat where this block's data is stored unsigned int nDataPos; //! Byte offset within rev?????.dat where this block's undo data is stored unsigned int nUndoPos; //! (memory only) Total amount of work (expected number of hashes) in the //! chain up to and including this block arith_uint256 nChainWork; //! Number of transactions in this block. //! Note: in a potential headers-first mode, this number cannot be relied //! upon unsigned int nTx; //! (memory only) Number of transactions in the chain up to and including //! this block. //! This value will be non-zero only if and only if transactions for this //! block and all its parents are available. Change to 64-bit type when //! necessary; won't happen before 2030 unsigned int nChainTx; //! Verification status of this block. See enum BlockStatus BlockStatus nStatus; //! block header int32_t nVersion; uint256 hashMerkleRoot; uint32_t nTime; uint32_t nBits; uint32_t nNonce; //! (memory only) Sequential id assigned to distinguish order in which //! blocks are received. int32_t nSequenceId; //! (memory only) block header metadata uint64_t nTimeReceived; //! (memory only) Maximum nTime in the chain up to and including this block. unsigned int nTimeMax; void SetNull() { phashBlock = nullptr; pprev = nullptr; pskip = nullptr; nHeight = 0; nFile = 0; nDataPos = 0; nUndoPos = 0; nChainWork = arith_uint256(); nTx = 0; nChainTx = 0; nStatus = BlockStatus(); nSequenceId = 0; nTimeMax = 0; nVersion = 0; hashMerkleRoot = uint256(); nTime = 0; nTimeReceived = 0; nBits = 0; nNonce = 0; } CBlockIndex() { SetNull(); } explicit CBlockIndex(const CBlockHeader &block) { SetNull(); nVersion = block.nVersion; hashMerkleRoot = block.hashMerkleRoot; nTime = block.nTime; nTimeReceived = 0; nBits = block.nBits; nNonce = block.nNonce; } CDiskBlockPos GetBlockPos() const { CDiskBlockPos ret; if (nStatus.hasData()) { ret.nFile = nFile; ret.nPos = nDataPos; } return ret; } CDiskBlockPos GetUndoPos() const { CDiskBlockPos ret; if (nStatus.hasUndo()) { ret.nFile = nFile; ret.nPos = nUndoPos; } return ret; } CBlockHeader GetBlockHeader() const { CBlockHeader block; block.nVersion = nVersion; if (pprev) { block.hashPrevBlock = pprev->GetBlockHash(); } block.hashMerkleRoot = hashMerkleRoot; block.nTime = nTime; block.nBits = nBits; block.nNonce = nNonce; return block; } uint256 GetBlockHash() const { return *phashBlock; } int64_t GetBlockTime() const { return int64_t(nTime); } int64_t GetBlockTimeMax() const { return int64_t(nTimeMax); } int64_t GetHeaderReceivedTime() const { return nTimeReceived; } int64_t GetReceivedTimeDiff() const { return GetHeaderReceivedTime() - GetBlockTime(); } static constexpr int nMedianTimeSpan = 11; int64_t GetMedianTimePast() const { int64_t pmedian[nMedianTimeSpan]; int64_t *pbegin = &pmedian[nMedianTimeSpan]; int64_t *pend = &pmedian[nMedianTimeSpan]; const CBlockIndex *pindex = this; for (int i = 0; i < nMedianTimeSpan && pindex; i++, pindex = pindex->pprev) { *(--pbegin) = pindex->GetBlockTime(); } std::sort(pbegin, pend); return pbegin[(pend - pbegin) / 2]; } std::string ToString() const { return strprintf( "CBlockIndex(pprev=%p, nHeight=%d, merkle=%s, hashBlock=%s)", pprev, nHeight, hashMerkleRoot.ToString(), GetBlockHash().ToString()); } //! Check whether this block index entry is valid up to the passed validity //! level. bool IsValid(enum BlockValidity nUpTo = BlockValidity::TRANSACTIONS) const { return nStatus.isValid(nUpTo); } //! Raise the validity level of this block index entry. //! Returns true if the validity was changed. bool RaiseValidity(enum BlockValidity nUpTo) { // Only validity flags allowed. if (nStatus.isInvalid()) { return false; } if (nStatus.getValidity() >= nUpTo) { return false; } nStatus = nStatus.withValidity(nUpTo); return true; } //! Build the skiplist pointer for this entry. void BuildSkip(); //! Efficiently find an ancestor of this block. CBlockIndex *GetAncestor(int height); const CBlockIndex *GetAncestor(int height) const; }; /** * Maintain a map of CBlockIndex for all known headers. */ struct BlockHasher { size_t operator()(const uint256 &hash) const { return hash.GetCheapHash(); } }; typedef std::unordered_map<uint256, CBlockIndex *, BlockHasher> BlockMap; extern BlockMap &mapBlockIndex; extern CCriticalSection cs_main; inline CBlockIndex *LookupBlockIndex(const uint256 &hash) { AssertLockHeld(cs_main); BlockMap::const_iterator it = mapBlockIndex.find(hash); return it == mapBlockIndex.end() ? nullptr : it->second; } arith_uint256 GetBlockProof(const CBlockIndex &block); /** * Return the time it would take to redo the work difference between from and * to, assuming the current hashrate corresponds to the difficulty at tip, in * seconds. */ int64_t GetBlockProofEquivalentTime(const CBlockIndex &to, const CBlockIndex &from, const CBlockIndex &tip, const Consensus::Params &); /** * Find the forking point between two chain tips. */ const CBlockIndex *LastCommonAncestor(const CBlockIndex *pa, const CBlockIndex *pb); /** * Check if two block index are on the same fork. */ bool AreOnTheSameFork(const CBlockIndex *pa, const CBlockIndex *pb); /** Used to marshal pointers into hashes for db storage. */ class CDiskBlockIndex : public CBlockIndex { public: uint256 hashPrev; CDiskBlockIndex() { hashPrev = uint256(); } explicit CDiskBlockIndex(const CBlockIndex *pindex) : CBlockIndex(*pindex) { hashPrev = (pprev ? pprev->GetBlockHash() : uint256()); } ADD_SERIALIZE_METHODS; template <typename Stream, typename Operation> inline void SerializationOp(Stream &s, Operation ser_action) { int _nVersion = s.GetVersion(); if (!(s.GetType() & SER_GETHASH)) { READWRITE(VARINT(_nVersion)); } READWRITE(VARINT(nHeight)); READWRITE(nStatus); READWRITE(VARINT(nTx)); if (nStatus.hasData() || nStatus.hasUndo()) { READWRITE(VARINT(nFile)); } if (nStatus.hasData()) { READWRITE(VARINT(nDataPos)); } if (nStatus.hasUndo()) { READWRITE(VARINT(nUndoPos)); } // block header READWRITE(this->nVersion); READWRITE(hashPrev); READWRITE(hashMerkleRoot); READWRITE(nTime); READWRITE(nBits); READWRITE(nNonce); } uint256 GetBlockHash() const { CBlockHeader block; block.nVersion = nVersion; block.hashPrevBlock = hashPrev; block.hashMerkleRoot = hashMerkleRoot; block.nTime = nTime; block.nBits = nBits; block.nNonce = nNonce; return block.GetHash(); } std::string ToString() const { std::string str = "CDiskBlockIndex("; str += CBlockIndex::ToString(); str += strprintf("\n hashBlock=%s, hashPrev=%s)", GetBlockHash().ToString(), hashPrev.ToString()); return str; } }; /** * An in-memory indexed chain of blocks. */ class CChain { private: std::vector<CBlockIndex *> vChain; public: /** * Returns the index entry for the genesis block of this chain, or nullptr * if none. */ CBlockIndex *Genesis() const { return vChain.size() > 0 ? vChain[0] : nullptr; } /** * Returns the index entry for the tip of this chain, or nullptr if none. */ CBlockIndex *Tip() const { return vChain.size() > 0 ? vChain[vChain.size() - 1] : nullptr; } /** * Returns the index entry at a particular height in this chain, or nullptr * if no such height exists. */ CBlockIndex *operator[](int nHeight) const { if (nHeight < 0 || nHeight >= (int)vChain.size()) { return nullptr; } return vChain[nHeight]; } /** Compare two chains efficiently. */ friend bool operator==(const CChain &a, const CChain &b) { return a.vChain.size() == b.vChain.size() && a.vChain[a.vChain.size() - 1] == b.vChain[b.vChain.size() - 1]; } /** Efficiently check whether a block is present in this chain. */ bool Contains(const CBlockIndex *pindex) const { return (*this)[pindex->nHeight] == pindex; } /** * Find the successor of a block in this chain, or nullptr if the given * index is not found or is the tip. */ CBlockIndex *Next(const CBlockIndex *pindex) const { if (!Contains(pindex)) { return nullptr; } return (*this)[pindex->nHeight + 1]; } /** * Return the maximal height in the chain. Is equal to chain.Tip() ? * chain.Tip()->nHeight : -1. */ int Height() const { return vChain.size() - 1; } /** Set/initialize a chain with a given tip. */ void SetTip(CBlockIndex *pindex); /** * Return a CBlockLocator that refers to a block in this chain (by default * the tip). */ CBlockLocator GetLocator(const CBlockIndex *pindex = nullptr) const; /** * Find the last common block between this chain and a block index entry. */ const CBlockIndex *FindFork(const CBlockIndex *pindex) const; /** * Find the earliest block with timestamp equal or greater than the given. */ CBlockIndex *FindEarliestAtLeast(int64_t nTime) const; }; #endif // BITCOIN_CHAIN_H diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index ccdc88093..c025ff0b6 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -1,429 +1,430 @@ // 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/fees.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 <timedata.h> #include <ui_interface.h> #include <validation.h> #include <wallet/fees.h> #include <wallet/finaltx.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 tx struct. WalletTx MakeWalletTx(CWallet &wallet, const CWalletTx &wtx) { WalletTx result; result.tx = wtx.tx; result.txin_is_mine.reserve(wtx.tx->vin.size()); for (const auto &txin : wtx.tx->vin) { result.txin_is_mine.emplace_back(wallet.IsMine(txin)); } result.txout_is_mine.reserve(wtx.tx->vout.size()); result.txout_address.reserve(wtx.tx->vout.size()); result.txout_address_is_mine.reserve(wtx.tx->vout.size()); for (const auto &txout : wtx.tx->vout) { result.txout_is_mine.emplace_back(wallet.IsMine(txout)); result.txout_address.emplace_back(); result.txout_address_is_mine.emplace_back( ExtractDestination(txout.scriptPubKey, result.txout_address.back()) ? IsMine(wallet, result.txout_address.back()) : ISMINE_NO); } result.credit = wtx.GetCredit(ISMINE_ALL); result.debit = wtx.GetDebit(ISMINE_ALL); result.change = wtx.GetChange(); result.time = wtx.GetTxTime(); result.value_map = wtx.mapValue; result.is_coinbase = wtx.IsCoinBase(); return result; } //! Construct wallet tx status struct. WalletTxStatus MakeWalletTxStatus(const CWalletTx &wtx) { WalletTxStatus result; CBlockIndex *block = LookupBlockIndex(wtx.hashBlock); result.block_height = (block ? block->nHeight : std::numeric_limits<int>::max()), result.blocks_to_maturity = wtx.GetBlocksToMaturity(); result.depth_in_main_chain = wtx.GetDepthInMainChain(); result.time_received = wtx.nTimeReceived; result.lock_time = wtx.tx->nLockTime; result.is_final = CheckFinalTx(*wtx.tx); result.is_trusted = wtx.IsTrusted(); result.is_abandoned = wtx.isAbandoned(); result.is_coinbase = wtx.IsCoinBase(); result.is_in_main_chain = wtx.IsInMainChain(); return result; } //! 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(); } std::set<CTxDestination> getLabelAddresses(const std::string &label) override { return m_wallet.GetLabelAddresses(label); }; bool getKeyFromPool(bool internal, CPubKey &pub_key) override { return m_wallet.GetKeyFromPool(pub_key, internal); } 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 delAddressBook(const CTxDestination &dest) override { return m_wallet.DelAddressBook(dest); } 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; } std::vector<WalletAddress> getAddresses() override { LOCK(m_wallet.cs_wallet); std::vector<WalletAddress> result; for (const auto &item : m_wallet.mapAddressBook) { result.emplace_back(item.first, IsMine(m_wallet, item.first), item.second.name, item.second.purpose); } return result; } void learnRelatedScripts(const CPubKey &key, OutputType type) override { m_wallet.LearnRelatedScripts(key, type); } 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); } CTransactionRef getTx(const TxId &txid) override { LOCK2(::cs_main, m_wallet.cs_wallet); auto mi = m_wallet.mapWallet.find(txid); if (mi != m_wallet.mapWallet.end()) { return mi->second.tx; } return {}; } WalletTx getWalletTx(const TxId &txid) override { LOCK2(::cs_main, m_wallet.cs_wallet); auto mi = m_wallet.mapWallet.find(txid); if (mi != m_wallet.mapWallet.end()) { return MakeWalletTx(m_wallet, mi->second); } return {}; } std::vector<WalletTx> getWalletTxs() override { LOCK2(::cs_main, m_wallet.cs_wallet); std::vector<WalletTx> result; result.reserve(m_wallet.mapWallet.size()); for (const auto &entry : m_wallet.mapWallet) { result.emplace_back(MakeWalletTx(m_wallet, entry.second)); } return result; } bool tryGetTxStatus(const TxId &txid, interfaces::WalletTxStatus &tx_status, - int &num_blocks) override { + int &num_blocks, int64_t &block_time) 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; } auto mi = m_wallet.mapWallet.find(txid); if (mi == m_wallet.mapWallet.end()) { return false; } num_blocks = ::chainActive.Height(); + block_time = ::chainActive.Tip()->GetBlockTime(); tx_status = MakeWalletTxStatus(mi->second); return true; } WalletTx getWalletTxDetails(const TxId &txid, WalletTxStatus &tx_status, WalletOrderForm &order_form, bool &in_mempool, int &num_blocks) override { LOCK2(::cs_main, m_wallet.cs_wallet); auto mi = m_wallet.mapWallet.find(txid); if (mi != m_wallet.mapWallet.end()) { num_blocks = ::chainActive.Height(); in_mempool = mi->second.InMempool(); order_form = mi->second.vOrderForm; tx_status = MakeWalletTxStatus(mi->second); return MakeWalletTx(m_wallet, mi->second); } return {}; } 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); } isminetype txinIsMine(const CTxIn &txin) override { LOCK2(::cs_main, m_wallet.cs_wallet); return m_wallet.IsMine(txin); } isminetype txoutIsMine(const CTxOut &txout) override { LOCK2(::cs_main, m_wallet.cs_wallet); return m_wallet.IsMine(txout); } Amount getDebit(const CTxIn &txin, isminefilter filter) override { LOCK2(::cs_main, m_wallet.cs_wallet); return m_wallet.GetDebit(txin, filter); } Amount getCredit(const CTxOut &txout, isminefilter filter) override { LOCK2(::cs_main, m_wallet.cs_wallet); return m_wallet.GetCredit(txout, filter); } 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](CWallet *, const TxId &txid, ChangeType status) { fn(txid, status); })); } std::unique_ptr<Handler> handleWatchOnlyChanged(WatchOnlyChangedFn fn) override { return MakeHandler(m_wallet.NotifyWatchonlyChanged.connect(fn)); } Amount getRequiredFee(unsigned int tx_bytes) override { return GetRequiredFee(m_wallet, tx_bytes); } Amount getMinimumFee(unsigned int tx_bytes, const CCoinControl &coin_control) override { return GetMinimumFee(m_wallet, tx_bytes, coin_control, g_mempool); } 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 d2fe537a3..e628dfd94 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -1,344 +1,344 @@ // 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 <pubkey.h> // For CTxDestination (CKeyID and CScriptID) #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 WalletAddress; struct WalletBalances; struct WalletTx; struct WalletTxOut; struct WalletTxStatus; 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 set of addresses corresponding to a given label. virtual std::set<CTxDestination> getLabelAddresses(const std::string &label) = 0; //! Get key from pool. virtual bool getKeyFromPool(bool internal, CPubKey &pub_key) = 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; // Remove address. virtual bool delAddressBook(const CTxDestination &dest) = 0; //! Look up address in wallet, return whether exists. virtual bool getAddress(const CTxDestination &dest, std::string *name = nullptr, isminetype *is_mine = nullptr) = 0; //! Get wallet address list. virtual std::vector<WalletAddress> getAddresses() = 0; //! Add scripts to key store so old so software versions opening the wallet //! database can detect payments to newer address types. virtual void learnRelatedScripts(const CPubKey &key, OutputType type) = 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 a transaction. virtual CTransactionRef getTx(const TxId &txid) = 0; //! Get transaction information. virtual WalletTx getWalletTx(const TxId &txid) = 0; //! Get list of all wallet transactions. virtual std::vector<WalletTx> getWalletTxs() = 0; //! Try to get updated status for a particular transaction, if possible //! without blocking. virtual bool tryGetTxStatus(const TxId &txid, WalletTxStatus &tx_status, - int &num_blocks) = 0; + int &num_blocks, int64_t &block_time) = 0; //! Get transaction details. virtual WalletTx getWalletTxDetails(const TxId &txid, WalletTxStatus &tx_status, WalletOrderForm &order_form, bool &in_mempool, int &num_blocks) = 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 whether transaction input belongs to wallet. virtual isminetype txinIsMine(const CTxIn &txin) = 0; //! Return whether transaction output belongs to wallet. virtual isminetype txoutIsMine(const CTxOut &txout) = 0; //! Return debit amount if transaction input belongs to wallet. virtual Amount getDebit(const CTxIn &txin, isminefilter filter) = 0; //! Return credit amount if transaction input belongs to wallet. virtual Amount getCredit(const CTxOut &txout, isminefilter filter) = 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; //! Get required fee. virtual Amount getRequiredFee(unsigned int tx_bytes) = 0; //! Get minimum fee. virtual Amount getMinimumFee(unsigned int tx_bytes, const CCoinControl &coin_control) = 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; }; //! Information about one wallet address. struct WalletAddress { CTxDestination dest; isminetype is_mine; std::string name; std::string purpose; WalletAddress(CTxDestination destIn, isminetype isMineIn, std::string nameIn, std::string purposeIn) : dest(std::move(destIn)), is_mine(isMineIn), name(std::move(nameIn)), purpose(std::move(purposeIn)) {} }; //! 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 information. struct WalletTx { CTransactionRef tx; std::vector<isminetype> txin_is_mine; std::vector<isminetype> txout_is_mine; std::vector<CTxDestination> txout_address; std::vector<isminetype> txout_address_is_mine; Amount credit; Amount debit; Amount change; int64_t time; std::map<std::string, std::string> value_map; bool is_coinbase; }; //! Updated transaction status. struct WalletTxStatus { int block_height; int blocks_to_maturity; int depth_in_main_chain; unsigned int time_received; uint32_t lock_time; bool is_final; bool is_trusted; bool is_abandoned; bool is_coinbase; bool is_in_main_chain; }; //! 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/bitcoingui.cpp b/src/qt/bitcoingui.cpp index d888a5043..f3f73b1f9 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1,1334 +1,1335 @@ // 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. #if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> #endif #include <qt/bitcoingui.h> +#include <chain.h> #include <chainparams.h> #include <init.h> #include <interfaces/handler.h> #include <interfaces/node.h> #include <qt/bitcoinunits.h> #include <qt/clientmodel.h> #include <qt/guiconstants.h> #include <qt/guiutil.h> #ifdef Q_OS_MAC #include <qt/macdockiconhandler.h> #endif #include <qt/modaloverlay.h> #include <qt/networkstyle.h> #include <qt/notificator.h> #include <qt/openuridialog.h> #include <qt/optionsdialog.h> #include <qt/optionsmodel.h> #include <qt/platformstyle.h> #include <qt/rpcconsole.h> #include <qt/utilitydialog.h> #ifdef ENABLE_WALLET #include <qt/walletframe.h> #include <qt/walletmodel.h> #include <qt/walletview.h> #endif // ENABLE_WALLET #include <ui_interface.h> #include <util.h> #include <QAction> #include <QApplication> #include <QComboBox> #include <QDateTime> #include <QDesktopWidget> #include <QDragEnterEvent> #include <QListWidget> #include <QMenuBar> #include <QMessageBox> #include <QMimeData> #include <QProgressDialog> #include <QSettings> #include <QShortcut> #include <QStackedWidget> #include <QStatusBar> #include <QStyle> #include <QTimer> #include <QToolBar> #include <QUrlQuery> #include <QVBoxLayout> #include <iostream> const std::string BitcoinGUI::DEFAULT_UIPLATFORM = #if defined(Q_OS_MAC) "macosx" #elif defined(Q_OS_WIN) "windows" #else "other" #endif ; BitcoinGUI::BitcoinGUI(interfaces::Node &node, const Config *configIn, const PlatformStyle *_platformStyle, const NetworkStyle *networkStyle, QWidget *parent) : QMainWindow(parent), enableWallet(false), m_node(node), platformStyle(_platformStyle), config(configIn) { QSettings settings; if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) { // Restore failed (perhaps missing setting), center the window move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center()); } QString windowTitle = tr(PACKAGE_NAME) + " - "; #ifdef ENABLE_WALLET enableWallet = WalletModel::isWalletEnabled(); #endif // ENABLE_WALLET if (enableWallet) { windowTitle += tr("Wallet"); } else { windowTitle += tr("Node"); } windowTitle += " " + networkStyle->getTitleAddText(); #ifndef Q_OS_MAC QApplication::setWindowIcon(networkStyle->getTrayAndWindowIcon()); setWindowIcon(networkStyle->getTrayAndWindowIcon()); #else MacDockIconHandler::instance()->setIcon(networkStyle->getAppIcon()); #endif setWindowTitle(windowTitle); rpcConsole = new RPCConsole(node, _platformStyle, 0); helpMessageDialog = new HelpMessageDialog(node, this, false); #ifdef ENABLE_WALLET if (enableWallet) { /** Create wallet frame and make it the central widget */ walletFrame = new WalletFrame(_platformStyle, config, this); setCentralWidget(walletFrame); } else #endif // ENABLE_WALLET { /** * When compiled without wallet or -disablewallet is provided, the * central widget is the rpc console. */ setCentralWidget(rpcConsole); } // Accept D&D of URIs setAcceptDrops(true); // Create actions for the toolbar, menu bar and tray/dock icon // Needs walletFrame to be initialized createActions(); // Create application menu bar createMenuBar(); // Create the toolbars createToolBars(); // Create system tray icon and notification createTrayIcon(networkStyle); // Create status bar statusBar(); // Disable size grip because it looks ugly and nobody needs it statusBar()->setSizeGripEnabled(false); // Status bar notification icons QFrame *frameBlocks = new QFrame(); frameBlocks->setContentsMargins(0, 0, 0, 0); frameBlocks->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); QHBoxLayout *frameBlocksLayout = new QHBoxLayout(frameBlocks); frameBlocksLayout->setContentsMargins(3, 0, 3, 0); frameBlocksLayout->setSpacing(3); unitDisplayControl = new UnitDisplayStatusBarControl(platformStyle); labelWalletEncryptionIcon = new QLabel(); labelWalletHDStatusIcon = new QLabel(); connectionsControl = new GUIUtil::ClickableLabel(); labelBlocksIcon = new GUIUtil::ClickableLabel(); if (enableWallet) { frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(unitDisplayControl); frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(labelWalletEncryptionIcon); frameBlocksLayout->addWidget(labelWalletHDStatusIcon); } frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(connectionsControl); frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(labelBlocksIcon); frameBlocksLayout->addStretch(); // Progress bar and label for blocks download progressBarLabel = new QLabel(); progressBarLabel->setVisible(false); progressBar = new GUIUtil::ProgressBar(); progressBar->setAlignment(Qt::AlignCenter); progressBar->setVisible(false); // Override style sheet for progress bar for styles that have a segmented // progress bar, as they make the text unreadable (workaround for issue // #1071) // See https://doc.qt.io/qt-5/gallery.html QString curStyle = QApplication::style()->metaObject()->className(); if (curStyle == "QWindowsStyle" || curStyle == "QWindowsXPStyle") { progressBar->setStyleSheet( "QProgressBar { background-color: #e8e8e8; border: 1px solid grey; " "border-radius: 7px; padding: 1px; text-align: center; } " "QProgressBar::chunk { background: QLinearGradient(x1: 0, y1: 0, " "x2: 1, y2: 0, stop: 0 #FF8000, stop: 1 orange); border-radius: " "7px; margin: 0px; }"); } statusBar()->addWidget(progressBarLabel); statusBar()->addWidget(progressBar); statusBar()->addPermanentWidget(frameBlocks); // Install event filter to be able to catch status tip events // (QEvent::StatusTip) this->installEventFilter(this); // Initially wallet actions should be disabled setWalletActionsEnabled(false); // Subscribe to notifications from core subscribeToCoreSignals(); connect(connectionsControl, SIGNAL(clicked(QPoint)), this, SLOT(toggleNetworkActive())); modalOverlay = new ModalOverlay(this->centralWidget()); #ifdef ENABLE_WALLET if (enableWallet) { connect(walletFrame, SIGNAL(requestedSyncWarningInfo()), this, SLOT(showModalOverlay())); connect(labelBlocksIcon, SIGNAL(clicked(QPoint)), this, SLOT(showModalOverlay())); connect(progressBar, SIGNAL(clicked(QPoint)), this, SLOT(showModalOverlay())); } #endif } BitcoinGUI::~BitcoinGUI() { // Unsubscribe from notifications from core unsubscribeFromCoreSignals(); QSettings settings; settings.setValue("MainWindowGeometry", saveGeometry()); // Hide tray icon, as deleting will let it linger until quit (on Ubuntu) if (trayIcon) { trayIcon->hide(); } #ifdef Q_OS_MAC delete appMenuBar; MacDockIconHandler::cleanup(); #endif delete rpcConsole; } void BitcoinGUI::createActions() { QActionGroup *tabGroup = new QActionGroup(this); overviewAction = new QAction(platformStyle->SingleColorIcon(":/icons/overview"), tr("&Overview"), this); overviewAction->setStatusTip(tr("Show general overview of wallet")); overviewAction->setToolTip(overviewAction->statusTip()); overviewAction->setCheckable(true); overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1)); tabGroup->addAction(overviewAction); sendCoinsAction = new QAction( platformStyle->SingleColorIcon(":/icons/send"), tr("&Send"), this); sendCoinsAction->setStatusTip(tr("Send coins to a Bitcoin address")); sendCoinsAction->setToolTip(sendCoinsAction->statusTip()); sendCoinsAction->setCheckable(true); sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2)); tabGroup->addAction(sendCoinsAction); sendCoinsMenuAction = new QAction(platformStyle->TextColorIcon(":/icons/send"), sendCoinsAction->text(), this); sendCoinsMenuAction->setStatusTip(sendCoinsAction->statusTip()); sendCoinsMenuAction->setToolTip(sendCoinsMenuAction->statusTip()); receiveCoinsAction = new QAction( platformStyle->SingleColorIcon(":/icons/receiving_addresses"), tr("&Receive"), this); receiveCoinsAction->setStatusTip( tr("Request payments (generates QR codes and %1: URIs)") .arg(GUIUtil::bitcoinURIScheme(*config))); receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip()); receiveCoinsAction->setCheckable(true); receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3)); tabGroup->addAction(receiveCoinsAction); receiveCoinsMenuAction = new QAction(platformStyle->TextColorIcon(":/icons/receiving_addresses"), receiveCoinsAction->text(), this); receiveCoinsMenuAction->setStatusTip(receiveCoinsAction->statusTip()); receiveCoinsMenuAction->setToolTip(receiveCoinsMenuAction->statusTip()); historyAction = new QAction(platformStyle->SingleColorIcon(":/icons/history"), tr("&Transactions"), this); historyAction->setStatusTip(tr("Browse transaction history")); historyAction->setToolTip(historyAction->statusTip()); historyAction->setCheckable(true); historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4)); tabGroup->addAction(historyAction); #ifdef ENABLE_WALLET // These showNormalIfMinimized are needed because Send Coins and Receive // Coins can be triggered from the tray menu, and need to show the GUI to be // useful. connect(overviewAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(overviewAction, SIGNAL(triggered()), this, SLOT(gotoOverviewPage())); connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(sendCoinsAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage())); connect(sendCoinsMenuAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(sendCoinsMenuAction, SIGNAL(triggered()), this, SLOT(gotoSendCoinsPage())); connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(receiveCoinsAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage())); connect(receiveCoinsMenuAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(receiveCoinsMenuAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage())); connect(historyAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage())); #endif // ENABLE_WALLET quitAction = new QAction(platformStyle->TextColorIcon(":/icons/quit"), tr("E&xit"), this); quitAction->setStatusTip(tr("Quit application")); quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q)); quitAction->setMenuRole(QAction::QuitRole); aboutAction = new QAction(platformStyle->TextColorIcon(":/icons/about"), tr("&About %1").arg(tr(PACKAGE_NAME)), this); aboutAction->setStatusTip( tr("Show information about %1").arg(tr(PACKAGE_NAME))); aboutAction->setMenuRole(QAction::AboutRole); aboutAction->setEnabled(false); aboutQtAction = new QAction(platformStyle->TextColorIcon(":/icons/about_qt"), tr("About &Qt"), this); aboutQtAction->setStatusTip(tr("Show information about Qt")); aboutQtAction->setMenuRole(QAction::AboutQtRole); optionsAction = new QAction(platformStyle->TextColorIcon(":/icons/options"), tr("&Options..."), this); optionsAction->setStatusTip( tr("Modify configuration options for %1").arg(tr(PACKAGE_NAME))); optionsAction->setMenuRole(QAction::PreferencesRole); optionsAction->setEnabled(false); toggleHideAction = new QAction(platformStyle->TextColorIcon(":/icons/about"), tr("&Show / Hide"), this); toggleHideAction->setStatusTip(tr("Show or hide the main Window")); encryptWalletAction = new QAction(platformStyle->TextColorIcon(":/icons/lock_closed"), tr("&Encrypt Wallet..."), this); encryptWalletAction->setStatusTip( tr("Encrypt the private keys that belong to your wallet")); encryptWalletAction->setCheckable(true); backupWalletAction = new QAction(platformStyle->TextColorIcon(":/icons/filesave"), tr("&Backup Wallet..."), this); backupWalletAction->setStatusTip(tr("Backup wallet to another location")); changePassphraseAction = new QAction(platformStyle->TextColorIcon(":/icons/key"), tr("&Change Passphrase..."), this); changePassphraseAction->setStatusTip( tr("Change the passphrase used for wallet encryption")); signMessageAction = new QAction(platformStyle->TextColorIcon(":/icons/edit"), tr("Sign &message..."), this); signMessageAction->setStatusTip( tr("Sign messages with your Bitcoin addresses to prove you own them")); verifyMessageAction = new QAction(platformStyle->TextColorIcon(":/icons/verify"), tr("&Verify message..."), this); verifyMessageAction->setStatusTip( tr("Verify messages to ensure they were signed with specified Bitcoin " "addresses")); openRPCConsoleAction = new QAction(platformStyle->TextColorIcon(":/icons/debugwindow"), tr("&Debug window"), this); openRPCConsoleAction->setStatusTip( tr("Open debugging and diagnostic console")); // initially disable the debug window menu item openRPCConsoleAction->setEnabled(false); usedSendingAddressesAction = new QAction(platformStyle->TextColorIcon(":/icons/address-book"), tr("&Sending addresses..."), this); usedSendingAddressesAction->setStatusTip( tr("Show the list of used sending addresses and labels")); usedReceivingAddressesAction = new QAction(platformStyle->TextColorIcon(":/icons/address-book"), tr("&Receiving addresses..."), this); usedReceivingAddressesAction->setStatusTip( tr("Show the list of used receiving addresses and labels")); openAction = new QAction(platformStyle->TextColorIcon(":/icons/open"), tr("Open &URI..."), this); openAction->setStatusTip(tr("Open a %1: URI or payment request") .arg(GUIUtil::bitcoinURIScheme(*config))); showHelpMessageAction = new QAction(platformStyle->TextColorIcon(":/icons/info"), tr("&Command-line options"), this); showHelpMessageAction->setMenuRole(QAction::NoRole); showHelpMessageAction->setStatusTip( tr("Show the %1 help message to get a list with possible Bitcoin " "command-line options") .arg(tr(PACKAGE_NAME))); connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); connect(aboutAction, SIGNAL(triggered()), this, SLOT(aboutClicked())); connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt())); connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked())); connect(toggleHideAction, SIGNAL(triggered()), this, SLOT(toggleHidden())); connect(showHelpMessageAction, SIGNAL(triggered()), this, SLOT(showHelpMessageClicked())); connect(openRPCConsoleAction, SIGNAL(triggered()), this, SLOT(showDebugWindow())); // prevents an open debug window from becoming stuck/unusable on client // shutdown connect(quitAction, SIGNAL(triggered()), rpcConsole, SLOT(hide())); #ifdef ENABLE_WALLET if (walletFrame) { connect(encryptWalletAction, SIGNAL(triggered(bool)), walletFrame, SLOT(encryptWallet(bool))); connect(backupWalletAction, SIGNAL(triggered()), walletFrame, SLOT(backupWallet())); connect(changePassphraseAction, SIGNAL(triggered()), walletFrame, SLOT(changePassphrase())); connect(signMessageAction, SIGNAL(triggered()), this, SLOT(gotoSignMessageTab())); connect(verifyMessageAction, SIGNAL(triggered()), this, SLOT(gotoVerifyMessageTab())); connect(usedSendingAddressesAction, SIGNAL(triggered()), walletFrame, SLOT(usedSendingAddresses())); connect(usedReceivingAddressesAction, SIGNAL(triggered()), walletFrame, SLOT(usedReceivingAddresses())); connect(openAction, SIGNAL(triggered()), this, SLOT(openClicked())); } #endif // ENABLE_WALLET new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C), this, SLOT(showDebugWindowActivateConsole())); new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_D), this, SLOT(showDebugWindow())); } void BitcoinGUI::createMenuBar() { #ifdef Q_OS_MAC // Create a decoupled menu bar on Mac which stays even if the window is // closed appMenuBar = new QMenuBar(); #else // Get the main window's menu bar on other platforms appMenuBar = menuBar(); #endif // Configure the menus QMenu *file = appMenuBar->addMenu(tr("&File")); if (walletFrame) { file->addAction(openAction); file->addAction(backupWalletAction); file->addAction(signMessageAction); file->addAction(verifyMessageAction); file->addSeparator(); file->addAction(usedSendingAddressesAction); file->addAction(usedReceivingAddressesAction); file->addSeparator(); } file->addAction(quitAction); QMenu *settings = appMenuBar->addMenu(tr("&Settings")); if (walletFrame) { settings->addAction(encryptWalletAction); settings->addAction(changePassphraseAction); settings->addSeparator(); } settings->addAction(optionsAction); QMenu *help = appMenuBar->addMenu(tr("&Help")); if (walletFrame) { help->addAction(openRPCConsoleAction); } help->addAction(showHelpMessageAction); help->addSeparator(); help->addAction(aboutAction); help->addAction(aboutQtAction); } void BitcoinGUI::createToolBars() { if (walletFrame) { QToolBar *toolbar = addToolBar(tr("Tabs toolbar")); appToolBar = toolbar; toolbar->setContextMenuPolicy(Qt::PreventContextMenu); toolbar->setMovable(false); toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolbar->addAction(overviewAction); toolbar->addAction(sendCoinsAction); toolbar->addAction(receiveCoinsAction); toolbar->addAction(historyAction); overviewAction->setChecked(true); #ifdef ENABLE_WALLET QWidget *spacer = new QWidget(); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); toolbar->addWidget(spacer); m_wallet_selector = new QComboBox(); connect(m_wallet_selector, SIGNAL(currentIndexChanged(const QString &)), this, SLOT(setCurrentWallet(const QString &))); #endif } } void BitcoinGUI::setClientModel(ClientModel *_clientModel) { this->clientModel = _clientModel; if (_clientModel) { // Create system tray menu (or setup the dock menu) that late to prevent // users from calling actions, while the client has not yet fully loaded createTrayIconMenu(); // Keep up to date with client updateNetworkState(); connect(_clientModel, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int))); connect(_clientModel, SIGNAL(networkActiveChanged(bool)), this, SLOT(setNetworkActive(bool))); modalOverlay->setKnownBestHeight( _clientModel->getHeaderTipHeight(), QDateTime::fromTime_t(_clientModel->getHeaderTipTime())); setNumBlocks(m_node.getNumBlocks(), QDateTime::fromTime_t(m_node.getLastBlockTime()), m_node.getVerificationProgress(), false); connect(_clientModel, SIGNAL(numBlocksChanged(int, QDateTime, double, bool)), this, SLOT(setNumBlocks(int, QDateTime, double, bool))); // Receive and report messages from client model connect(_clientModel, SIGNAL(message(QString, QString, unsigned int)), this, SLOT(message(QString, QString, unsigned int))); // Show progress dialog connect(_clientModel, SIGNAL(showProgress(QString, int)), this, SLOT(showProgress(QString, int))); rpcConsole->setClientModel(_clientModel); #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->setClientModel(_clientModel); } #endif // ENABLE_WALLET unitDisplayControl->setOptionsModel(_clientModel->getOptionsModel()); OptionsModel *optionsModel = _clientModel->getOptionsModel(); if (optionsModel) { // be aware of the tray icon disable state change reported by the // OptionsModel object. connect(optionsModel, SIGNAL(hideTrayIconChanged(bool)), this, SLOT(setTrayIconVisible(bool))); // initialize the disable state of the tray icon with the current // value in the model. setTrayIconVisible(optionsModel->getHideTrayIcon()); } } else { // Disable possibility to show main window via action toggleHideAction->setEnabled(false); if (trayIconMenu) { // Disable context menu on tray icon trayIconMenu->clear(); } // Propagate cleared model to child objects rpcConsole->setClientModel(nullptr); #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->setClientModel(nullptr); } #endif // ENABLE_WALLET unitDisplayControl->setOptionsModel(nullptr); } } #ifdef ENABLE_WALLET bool BitcoinGUI::addWallet(WalletModel *walletModel) { if (!walletFrame) return false; const QString name = walletModel->getWalletName(); setWalletActionsEnabled(true); m_wallet_selector->addItem(name); if (m_wallet_selector->count() == 2) { m_wallet_selector_label = new QLabel(); m_wallet_selector_label->setText(tr("Wallet:") + " "); m_wallet_selector_label->setBuddy(m_wallet_selector); appToolBar->addWidget(m_wallet_selector_label); appToolBar->addWidget(m_wallet_selector); } rpcConsole->addWallet(walletModel); return walletFrame->addWallet(walletModel); } bool BitcoinGUI::setCurrentWallet(const QString &name) { if (!walletFrame) return false; return walletFrame->setCurrentWallet(name); } void BitcoinGUI::removeAllWallets() { if (!walletFrame) return; setWalletActionsEnabled(false); walletFrame->removeAllWallets(); } #endif // ENABLE_WALLET void BitcoinGUI::setWalletActionsEnabled(bool enabled) { overviewAction->setEnabled(enabled); sendCoinsAction->setEnabled(enabled); sendCoinsMenuAction->setEnabled(enabled); receiveCoinsAction->setEnabled(enabled); receiveCoinsMenuAction->setEnabled(enabled); historyAction->setEnabled(enabled); encryptWalletAction->setEnabled(enabled); backupWalletAction->setEnabled(enabled); changePassphraseAction->setEnabled(enabled); signMessageAction->setEnabled(enabled); verifyMessageAction->setEnabled(enabled); usedSendingAddressesAction->setEnabled(enabled); usedReceivingAddressesAction->setEnabled(enabled); openAction->setEnabled(enabled); } void BitcoinGUI::createTrayIcon(const NetworkStyle *networkStyle) { #ifndef Q_OS_MAC trayIcon = new QSystemTrayIcon(this); QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " + networkStyle->getTitleAddText(); trayIcon->setToolTip(toolTip); trayIcon->setIcon(networkStyle->getTrayAndWindowIcon()); trayIcon->hide(); #endif notificator = new Notificator(QApplication::applicationName(), trayIcon, this); } void BitcoinGUI::createTrayIconMenu() { #ifndef Q_OS_MAC // return if trayIcon is unset (only on non-Mac OSes) if (!trayIcon) return; trayIconMenu = new QMenu(this); trayIcon->setContextMenu(trayIconMenu); connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason))); #else // Note: On Mac, the dock icon is used to provide the tray's functionality. MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance(); dockIconHandler->setMainWindow(static_cast<QMainWindow *>(this)); trayIconMenu = dockIconHandler->dockMenu(); #endif // Configuration of the tray icon (or dock icon) icon menu trayIconMenu->addAction(toggleHideAction); trayIconMenu->addSeparator(); trayIconMenu->addAction(sendCoinsMenuAction); trayIconMenu->addAction(receiveCoinsMenuAction); trayIconMenu->addSeparator(); trayIconMenu->addAction(signMessageAction); trayIconMenu->addAction(verifyMessageAction); trayIconMenu->addSeparator(); trayIconMenu->addAction(optionsAction); trayIconMenu->addAction(openRPCConsoleAction); #ifndef Q_OS_MAC // This is built-in on Mac trayIconMenu->addSeparator(); trayIconMenu->addAction(quitAction); #endif } #ifndef Q_OS_MAC void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { if (reason == QSystemTrayIcon::Trigger) { // Click on system tray icon triggers show/hide of the main window toggleHidden(); } } #endif void BitcoinGUI::optionsClicked() { if (!clientModel || !clientModel->getOptionsModel()) return; OptionsDialog dlg(this, enableWallet); dlg.setModel(clientModel->getOptionsModel()); dlg.exec(); } void BitcoinGUI::aboutClicked() { if (!clientModel) return; HelpMessageDialog dlg(m_node, this, true); dlg.exec(); } void BitcoinGUI::showDebugWindow() { rpcConsole->showNormal(); rpcConsole->show(); rpcConsole->raise(); rpcConsole->activateWindow(); } void BitcoinGUI::showDebugWindowActivateConsole() { rpcConsole->setTabFocus(RPCConsole::TAB_CONSOLE); showDebugWindow(); } void BitcoinGUI::showHelpMessageClicked() { helpMessageDialog->show(); } #ifdef ENABLE_WALLET void BitcoinGUI::openClicked() { OpenURIDialog dlg(config, this); if (dlg.exec()) { Q_EMIT receivedURI(dlg.getURI()); } } void BitcoinGUI::gotoOverviewPage() { overviewAction->setChecked(true); if (walletFrame) walletFrame->gotoOverviewPage(); } void BitcoinGUI::gotoHistoryPage() { historyAction->setChecked(true); if (walletFrame) walletFrame->gotoHistoryPage(); } void BitcoinGUI::gotoReceiveCoinsPage() { receiveCoinsAction->setChecked(true); if (walletFrame) walletFrame->gotoReceiveCoinsPage(); } void BitcoinGUI::gotoSendCoinsPage(QString addr) { sendCoinsAction->setChecked(true); if (walletFrame) walletFrame->gotoSendCoinsPage(addr); } void BitcoinGUI::gotoSignMessageTab(QString addr) { if (walletFrame) walletFrame->gotoSignMessageTab(addr); } void BitcoinGUI::gotoVerifyMessageTab(QString addr) { if (walletFrame) walletFrame->gotoVerifyMessageTab(addr); } #endif // ENABLE_WALLET void BitcoinGUI::updateNetworkState() { int count = clientModel->getNumConnections(); QString icon; switch (count) { case 0: icon = ":/icons/connect_0"; break; case 1: case 2: case 3: icon = ":/icons/connect_1"; break; case 4: case 5: case 6: icon = ":/icons/connect_2"; break; case 7: case 8: case 9: icon = ":/icons/connect_3"; break; default: icon = ":/icons/connect_4"; break; } QString tooltip; if (m_node.getNetworkActive()) { tooltip = tr("%n active connection(s) to Bitcoin network", "", count) + QString(".<br>") + tr("Click to disable network activity."); } else { tooltip = tr("Network activity disabled.") + QString("<br>") + tr("Click to enable network activity again."); icon = ":/icons/network_disabled"; } // Don't word-wrap this (fixed-width) tooltip tooltip = QString("<nobr>") + tooltip + QString("</nobr>"); connectionsControl->setToolTip(tooltip); connectionsControl->setPixmap(platformStyle->SingleColorIcon(icon).pixmap( STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); } void BitcoinGUI::setNumConnections(int count) { updateNetworkState(); } void BitcoinGUI::setNetworkActive(bool networkActive) { updateNetworkState(); } void BitcoinGUI::updateHeadersSyncProgressLabel() { int64_t headersTipTime = clientModel->getHeaderTipTime(); int headersTipHeight = clientModel->getHeaderTipHeight(); int estHeadersLeft = (GetTime() - headersTipTime) / Params().GetConsensus().nPowTargetSpacing; if (estHeadersLeft > HEADER_HEIGHT_DELTA_SYNC) { progressBarLabel->setText( tr("Syncing Headers (%1%)...") .arg(QString::number(100.0 / (headersTipHeight + estHeadersLeft) * headersTipHeight, 'f', 1))); } } void BitcoinGUI::setNumBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, bool header) { if (modalOverlay) { if (header) { modalOverlay->setKnownBestHeight(count, blockDate); } else { modalOverlay->tipUpdate(count, blockDate, nVerificationProgress); } } if (!clientModel) { return; } // Prevent orphan statusbar messages (e.g. hover Quit in main menu, wait // until chain-sync starts -> garbled text) statusBar()->clearMessage(); // Acquire current block source enum BlockSource blockSource = clientModel->getBlockSource(); switch (blockSource) { case BlockSource::NETWORK: if (header) { updateHeadersSyncProgressLabel(); return; } progressBarLabel->setText(tr("Synchronizing with network...")); updateHeadersSyncProgressLabel(); break; case BlockSource::DISK: if (header) { progressBarLabel->setText(tr("Indexing blocks on disk...")); } else { progressBarLabel->setText(tr("Processing blocks on disk...")); } break; case BlockSource::REINDEX: progressBarLabel->setText(tr("Reindexing blocks on disk...")); break; case BlockSource::NONE: if (header) { return; } progressBarLabel->setText(tr("Connecting to peers...")); break; } QString tooltip; QDateTime currentDate = QDateTime::currentDateTime(); qint64 secs = blockDate.secsTo(currentDate); tooltip = tr("Processed %n block(s) of transaction history.", "", count); // Set icon state: spinning if catching up, tick otherwise - if (secs < 90 * 60) { + if (secs < MAX_BLOCK_TIME_GAP) { tooltip = tr("Up to date") + QString(".<br>") + tooltip; labelBlocksIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/synced") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->showOutOfSyncWarning(false); modalOverlay->showHide(true, true); } #endif // ENABLE_WALLET progressBarLabel->setVisible(false); progressBar->setVisible(false); } else { QString timeBehindText = GUIUtil::formatNiceTimeOffset(secs); progressBarLabel->setVisible(true); progressBar->setFormat(tr("%1 behind").arg(timeBehindText)); progressBar->setMaximum(1000000000); progressBar->setValue(nVerificationProgress * 1000000000.0 + 0.5); progressBar->setVisible(true); tooltip = tr("Catching up...") + QString("<br>") + tooltip; if (count != prevBlocks) { labelBlocksIcon->setPixmap( platformStyle ->SingleColorIcon(QString(":/movies/spinner-%1") .arg(spinnerFrame, 3, 10, QChar('0'))) .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES; } prevBlocks = count; #ifdef ENABLE_WALLET if (walletFrame) { walletFrame->showOutOfSyncWarning(true); modalOverlay->showHide(); } #endif // ENABLE_WALLET tooltip += QString("<br>"); tooltip += tr("Last received block was generated %1 ago.").arg(timeBehindText); tooltip += QString("<br>"); tooltip += tr("Transactions after this will not yet be visible."); } // Don't word-wrap this (fixed-width) tooltip tooltip = QString("<nobr>") + tooltip + QString("</nobr>"); labelBlocksIcon->setToolTip(tooltip); progressBarLabel->setToolTip(tooltip); progressBar->setToolTip(tooltip); } void BitcoinGUI::message(const QString &title, const QString &message, unsigned int style, bool *ret) { // default title QString strTitle = tr("Bitcoin"); // Default to information icon int nMBoxIcon = QMessageBox::Information; int nNotifyIcon = Notificator::Information; QString msgType; // Prefer supplied title over style based title if (!title.isEmpty()) { msgType = title; } else { switch (style) { case CClientUIInterface::MSG_ERROR: msgType = tr("Error"); break; case CClientUIInterface::MSG_WARNING: msgType = tr("Warning"); break; case CClientUIInterface::MSG_INFORMATION: msgType = tr("Information"); break; default: break; } } // Append title to "Bitcoin - " if (!msgType.isEmpty()) { strTitle += " - " + msgType; } // Check for error/warning icon if (style & CClientUIInterface::ICON_ERROR) { nMBoxIcon = QMessageBox::Critical; nNotifyIcon = Notificator::Critical; } else if (style & CClientUIInterface::ICON_WARNING) { nMBoxIcon = QMessageBox::Warning; nNotifyIcon = Notificator::Warning; } // Display message if (style & CClientUIInterface::MODAL) { // Check for buttons, use OK as default, if none was supplied QMessageBox::StandardButton buttons; if (!(buttons = (QMessageBox::StandardButton)( style & CClientUIInterface::BTN_MASK))) buttons = QMessageBox::Ok; showNormalIfMinimized(); QMessageBox mBox(static_cast<QMessageBox::Icon>(nMBoxIcon), strTitle, message, buttons, this); int r = mBox.exec(); if (ret != nullptr) { *ret = r == QMessageBox::Ok; } } else notificator->notify(static_cast<Notificator::Class>(nNotifyIcon), strTitle, message); } void BitcoinGUI::changeEvent(QEvent *e) { QMainWindow::changeEvent(e); #ifndef Q_OS_MAC // Ignored on Mac if (e->type() == QEvent::WindowStateChange) { if (clientModel && clientModel->getOptionsModel() && clientModel->getOptionsModel()->getMinimizeToTray()) { QWindowStateChangeEvent *wsevt = static_cast<QWindowStateChangeEvent *>(e); if (!(wsevt->oldState() & Qt::WindowMinimized) && isMinimized()) { QTimer::singleShot(0, this, SLOT(hide())); e->ignore(); } else if ((wsevt->oldState() & Qt::WindowMinimized) && !isMinimized()) { QTimer::singleShot(0, this, SLOT(show())); e->ignore(); } } } #endif } void BitcoinGUI::closeEvent(QCloseEvent *event) { #ifndef Q_OS_MAC // Ignored on Mac if (clientModel && clientModel->getOptionsModel()) { if (!clientModel->getOptionsModel()->getMinimizeOnClose()) { // close rpcConsole in case it was open to make some space for the // shutdown window rpcConsole->close(); QApplication::quit(); } else { QMainWindow::showMinimized(); event->ignore(); } } #else QMainWindow::closeEvent(event); #endif } void BitcoinGUI::showEvent(QShowEvent *event) { // enable the debug window when the main window shows up openRPCConsoleAction->setEnabled(true); aboutAction->setEnabled(true); optionsAction->setEnabled(true); } #ifdef ENABLE_WALLET void BitcoinGUI::incomingTransaction(const QString &date, int unit, const Amount amount, const QString &type, const QString &address, const QString &label, const QString &walletName) { // On new transaction, make an info balloon QString msg = tr("Date: %1\n").arg(date) + tr("Amount: %1\n") .arg(BitcoinUnits::formatWithUnit(unit, amount, true)); if (m_node.getWallets().size() > 1 && !walletName.isEmpty()) { msg += tr("Wallet: %1\n").arg(walletName); } msg += tr("Type: %1\n").arg(type); if (!label.isEmpty()) { msg += tr("Label: %1\n").arg(label); } else if (!address.isEmpty()) { msg += tr("Address: %1\n").arg(address); } message(amount < Amount::zero() ? tr("Sent transaction") : tr("Incoming transaction"), msg, CClientUIInterface::MSG_INFORMATION); } #endif // ENABLE_WALLET void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event) { // Accept only URIs if (event->mimeData()->hasUrls()) { event->acceptProposedAction(); } } void BitcoinGUI::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls()) { for (const QUrl &uri : event->mimeData()->urls()) { Q_EMIT receivedURI(uri.toString()); } } event->acceptProposedAction(); } bool BitcoinGUI::eventFilter(QObject *object, QEvent *event) { // Catch status tip events if (event->type() == QEvent::StatusTip) { // Prevent adding text from setStatusTip(), if we currently use the // status bar for displaying other stuff if (progressBarLabel->isVisible() || progressBar->isVisible()) { return true; } } return QMainWindow::eventFilter(object, event); } #ifdef ENABLE_WALLET bool BitcoinGUI::handlePaymentRequest(const SendCoinsRecipient &recipient) { // URI has to be valid if (walletFrame && walletFrame->handlePaymentRequest(recipient)) { showNormalIfMinimized(); gotoSendCoinsPage(); return true; } return false; } void BitcoinGUI::setHDStatus(int hdEnabled) { labelWalletHDStatusIcon->setPixmap( platformStyle ->SingleColorIcon(hdEnabled ? ":/icons/hd_enabled" : ":/icons/hd_disabled") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelWalletHDStatusIcon->setToolTip( hdEnabled ? tr("HD key generation is <b>enabled</b>") : tr("HD key generation is <b>disabled</b>")); // eventually disable the QLabel to set its opacity to 50% labelWalletHDStatusIcon->setEnabled(hdEnabled); } void BitcoinGUI::setEncryptionStatus(int status) { switch (status) { case WalletModel::Unencrypted: labelWalletEncryptionIcon->hide(); encryptWalletAction->setChecked(false); changePassphraseAction->setEnabled(false); encryptWalletAction->setEnabled(true); break; case WalletModel::Unlocked: labelWalletEncryptionIcon->show(); labelWalletEncryptionIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/lock_open") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelWalletEncryptionIcon->setToolTip( tr("Wallet is <b>encrypted</b> and currently <b>unlocked</b>")); encryptWalletAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled( false); // TODO: decrypt currently not supported break; case WalletModel::Locked: labelWalletEncryptionIcon->show(); labelWalletEncryptionIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/lock_closed") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelWalletEncryptionIcon->setToolTip( tr("Wallet is <b>encrypted</b> and currently <b>locked</b>")); encryptWalletAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled( false); // TODO: decrypt currently not supported break; } } void BitcoinGUI::updateWalletStatus() { if (!walletFrame) { return; } WalletView *const walletView = walletFrame->currentWalletView(); if (!walletView) { return; } WalletModel *const walletModel = walletView->getWalletModel(); setEncryptionStatus(walletModel->getEncryptionStatus()); setHDStatus(walletModel->wallet().hdEnabled()); } #endif // ENABLE_WALLET void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden) { if (!clientModel) { return; } // activateWindow() (sometimes) helps with keyboard focus on Windows if (isHidden()) { show(); activateWindow(); } else if (isMinimized()) { showNormal(); activateWindow(); } else if (GUIUtil::isObscured(this)) { raise(); activateWindow(); } else if (fToggleHidden) { hide(); } } void BitcoinGUI::toggleHidden() { showNormalIfMinimized(true); } void BitcoinGUI::detectShutdown() { if (m_node.shutdownRequested()) { if (rpcConsole) { rpcConsole->hide(); } qApp->quit(); } } void BitcoinGUI::showProgress(const QString &title, int nProgress) { if (nProgress == 0) { progressDialog = new QProgressDialog(title, "", 0, 100); progressDialog->setWindowModality(Qt::ApplicationModal); progressDialog->setMinimumDuration(0); progressDialog->setCancelButton(0); progressDialog->setAutoClose(false); progressDialog->setValue(0); } else if (progressDialog) { if (nProgress == 100) { progressDialog->close(); progressDialog->deleteLater(); } else { progressDialog->setValue(nProgress); } } } void BitcoinGUI::setTrayIconVisible(bool fHideTrayIcon) { if (trayIcon) { trayIcon->setVisible(!fHideTrayIcon); } } void BitcoinGUI::showModalOverlay() { if (modalOverlay && (progressBar->isVisible() || modalOverlay->isLayerVisible())) { modalOverlay->toggleVisibility(); } } static bool ThreadSafeMessageBox(BitcoinGUI *gui, const std::string &message, const std::string &caption, unsigned int style) { bool modal = (style & CClientUIInterface::MODAL); // The SECURE flag has no effect in the Qt GUI. // bool secure = (style & CClientUIInterface::SECURE); style &= ~CClientUIInterface::SECURE; bool ret = false; // In case of modal message, use blocking connection to wait for user to // click a button QMetaObject::invokeMethod(gui, "message", modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(caption)), Q_ARG(QString, QString::fromStdString(message)), Q_ARG(unsigned int, style), Q_ARG(bool *, &ret)); return ret; } void BitcoinGUI::subscribeToCoreSignals() { // Connect signals to client m_handler_message_box = m_node.handleMessageBox( boost::bind(ThreadSafeMessageBox, this, _1, _2, _3)); m_handler_question = m_node.handleQuestion( boost::bind(ThreadSafeMessageBox, this, _1, _3, _4)); } void BitcoinGUI::unsubscribeFromCoreSignals() { // Disconnect signals from client m_handler_message_box->disconnect(); m_handler_question->disconnect(); } void BitcoinGUI::toggleNetworkActive() { m_node.setNetworkActive(!m_node.getNetworkActive()); } UnitDisplayStatusBarControl::UnitDisplayStatusBarControl( const PlatformStyle *platformStyle) : optionsModel(0), menu(0) { createContextMenu(); setToolTip(tr("Unit to show amounts in. Click to select another unit.")); QList<BitcoinUnits::Unit> units = BitcoinUnits::availableUnits(); int max_width = 0; const QFontMetrics fm(font()); for (const BitcoinUnits::Unit unit : units) { max_width = qMax(max_width, fm.width(BitcoinUnits::name(unit))); } setMinimumSize(max_width, 0); setAlignment(Qt::AlignRight | Qt::AlignVCenter); setStyleSheet(QString("QLabel { color : %1 }") .arg(platformStyle->SingleColor().name())); } /** So that it responds to button clicks */ void UnitDisplayStatusBarControl::mousePressEvent(QMouseEvent *event) { onDisplayUnitsClicked(event->pos()); } /** Creates context menu, its actions, and wires up all the relevant signals for * mouse events. */ void UnitDisplayStatusBarControl::createContextMenu() { menu = new QMenu(this); for (BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) { QAction *menuAction = new QAction(QString(BitcoinUnits::name(u)), this); menuAction->setData(QVariant(u)); menu->addAction(menuAction); } connect(menu, SIGNAL(triggered(QAction *)), this, SLOT(onMenuSelection(QAction *))); } /** Lets the control know about the Options Model (and its signals) */ void UnitDisplayStatusBarControl::setOptionsModel(OptionsModel *_optionsModel) { if (_optionsModel) { this->optionsModel = _optionsModel; // be aware of a display unit change reported by the OptionsModel // object. connect(_optionsModel, SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit(int))); // initialize the display units label with the current value in the // model. updateDisplayUnit(_optionsModel->getDisplayUnit()); } } /** When Display Units are changed on OptionsModel it will refresh the display * text of the control on the status bar */ void UnitDisplayStatusBarControl::updateDisplayUnit(int newUnits) { setText(BitcoinUnits::name(newUnits)); } /** Shows context menu with Display Unit options by the mouse coordinates */ void UnitDisplayStatusBarControl::onDisplayUnitsClicked(const QPoint &point) { QPoint globalPos = mapToGlobal(point); menu->exec(globalPos); } /** Tells underlying optionsModel to update its current display unit. */ void UnitDisplayStatusBarControl::onMenuSelection(QAction *action) { if (action) { optionsModel->setDisplayUnit(action->data()); } } diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index bd8facf6b..aedce1476 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -1,211 +1,216 @@ // 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/transactionrecord.h> #include <chain.h> #include <consensus/consensus.h> #include <dstencode.h> #include <interfaces/wallet.h> #include <timedata.h> #include <validation.h> #include <wallet/finaltx.h> +#include <QDateTime> + #include <cstdint> /** * Return positive answer if transaction should be shown in list. */ bool TransactionRecord::showTransaction() { // There are currently no cases where we hide transactions, but we may want // to use this in the future for things like RBF. return true; } /** * Decompose CWallet transaction to model transaction records. */ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interfaces::WalletTx &wtx) { QList<TransactionRecord> parts; int64_t nTime = wtx.time; Amount nCredit = wtx.credit; Amount nDebit = wtx.debit; Amount nNet = nCredit - nDebit; const TxId &txid = wtx.tx->GetId(); std::map<std::string, std::string> mapValue = wtx.value_map; if (nNet > Amount::zero() || wtx.is_coinbase) { // // Credit // for (size_t i = 0; i < wtx.tx->vout.size(); i++) { const CTxOut &txout = wtx.tx->vout[i]; isminetype mine = wtx.txout_is_mine[i]; if (mine) { TransactionRecord sub(txid, nTime); CTxDestination address; sub.idx = i; // vout index sub.credit = txout.nValue; sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY; if (wtx.txout_address_is_mine[i]) { // Received by Bitcoin Address sub.type = TransactionRecord::RecvWithAddress; sub.address = EncodeDestination(wtx.txout_address[i]); } else { // Received by IP connection (deprecated features), or a // multisignature or other non-simple transaction sub.type = TransactionRecord::RecvFromOther; sub.address = mapValue["from"]; } if (wtx.is_coinbase) { // Generated sub.type = TransactionRecord::Generated; } parts.append(sub); } } } else { bool involvesWatchAddress = false; isminetype fAllFromMe = ISMINE_SPENDABLE; for (isminetype mine : wtx.txin_is_mine) { if (mine & ISMINE_WATCH_ONLY) { involvesWatchAddress = true; } if (fAllFromMe > mine) { fAllFromMe = mine; } } isminetype fAllToMe = ISMINE_SPENDABLE; for (isminetype mine : wtx.txout_is_mine) { if (mine & ISMINE_WATCH_ONLY) { involvesWatchAddress = true; } if (fAllToMe > mine) { fAllToMe = mine; } } if (fAllFromMe && fAllToMe) { // Payment to self Amount nChange = wtx.change; parts.append(TransactionRecord( txid, nTime, TransactionRecord::SendToSelf, "", -1 * (nDebit - nChange), (nCredit - nChange))); // maybe pass to TransactionRecord as constructor argument parts.last().involvesWatchAddress = involvesWatchAddress; } else if (fAllFromMe) { // // Debit // Amount nTxFee = nDebit - wtx.tx->GetValueOut(); for (size_t nOut = 0; nOut < wtx.tx->vout.size(); nOut++) { const CTxOut &txout = wtx.tx->vout[nOut]; TransactionRecord sub(txid, nTime); sub.idx = nOut; sub.involvesWatchAddress = involvesWatchAddress; if (wtx.txout_is_mine[nOut]) { // Ignore parts sent to self, as this is usually the change // from a transaction sent back to our own address. continue; } if (!boost::get<CNoDestination>(&wtx.txout_address[nOut])) { // Sent to Bitcoin Address sub.type = TransactionRecord::SendToAddress; sub.address = EncodeDestination(wtx.txout_address[nOut]); } else { // Sent to IP, or other non-address transaction like OP_EVAL sub.type = TransactionRecord::SendToOther; sub.address = mapValue["to"]; } Amount nValue = txout.nValue; /* Add fee to first output */ if (nTxFee > Amount::zero()) { nValue += nTxFee; nTxFee = Amount::zero(); } sub.debit = -1 * nValue; parts.append(sub); } } else { // // Mixed debit transaction, can't break down payees // parts.append(TransactionRecord(txid, nTime, TransactionRecord::Other, "", nNet, Amount::zero())); parts.last().involvesWatchAddress = involvesWatchAddress; } } return parts; } void TransactionRecord::updateStatus(const interfaces::WalletTxStatus &wtx, - int numBlocks) { + int numBlocks, int64_t block_time) { // Determine transaction status // Sort order, unrecorded transactions sort to the top status.sortKey = strprintf("%010d-%01d-%010u-%03d", wtx.block_height, wtx.is_coinbase ? 1 : 0, wtx.time_received, idx); status.countsForBalance = wtx.is_trusted && !(wtx.blocks_to_maturity > 0); status.depth = wtx.depth_in_main_chain; status.cur_num_blocks = numBlocks; - if (!wtx.is_final) { + const bool up_to_date = + (int64_t(QDateTime::currentMSecsSinceEpoch()) / 1000 - block_time < + MAX_BLOCK_TIME_GAP); + if (up_to_date && !wtx.is_final) { if (wtx.lock_time < LOCKTIME_THRESHOLD) { status.status = TransactionStatus::OpenUntilBlock; status.open_for = wtx.lock_time - numBlocks; } else { status.status = TransactionStatus::OpenUntilDate; status.open_for = wtx.lock_time; } } else if (type == TransactionRecord::Generated) { // For generated transactions, determine maturity if (wtx.blocks_to_maturity > 0) { status.status = TransactionStatus::Immature; if (wtx.is_in_main_chain) { status.matures_in = wtx.blocks_to_maturity; } else { status.status = TransactionStatus::NotAccepted; } } else { status.status = TransactionStatus::Confirmed; } } else { if (status.depth < 0) { status.status = TransactionStatus::Conflicted; } else if (status.depth == 0) { status.status = TransactionStatus::Unconfirmed; if (wtx.is_abandoned) { status.status = TransactionStatus::Abandoned; } } else if (status.depth < RecommendedNumConfirmations) { status.status = TransactionStatus::Confirming; } else { status.status = TransactionStatus::Confirmed; } } } bool TransactionRecord::statusUpdateNeeded(int numBlocks) const { return status.cur_num_blocks != numBlocks; } QString TransactionRecord::getTxID() const { return QString::fromStdString(txid.ToString()); } int TransactionRecord::getOutputIndex() const { return idx; } diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index c1d0b0b09..8660e0ef3 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -1,154 +1,155 @@ // 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_TRANSACTIONRECORD_H #define BITCOIN_QT_TRANSACTIONRECORD_H #include <amount.h> #include <primitives/txid.h> #include <QList> #include <QString> namespace interfaces { class Node; class Wallet; struct WalletTx; struct WalletTxStatus; } /** * UI model for transaction status. The transaction status is the part of a * transaction that will change over time. */ class TransactionStatus { public: TransactionStatus() : countsForBalance(false), sortKey(""), matures_in(0), status(Unconfirmed), depth(0), open_for(0), cur_num_blocks(-1) {} enum Status { /**< Have 6 or more confirmations (normal tx) or fully mature (mined tx) **/ Confirmed, /// Normal (sent/received) transactions /**< Transaction not yet final, waiting for date */ OpenUntilDate, /**< Transaction not yet final, waiting for block */ OpenUntilBlock, /**< Not yet mined into a block **/ Unconfirmed, /**< Confirmed, but waiting for the recommended number of confirmations **/ Confirming, /**< Conflicts with other transaction or mempool **/ Conflicted, /**< Abandoned from the wallet **/ Abandoned, /// Generated (mined) transactions /**< Mined but waiting for maturity */ Immature, /**< Mined but not accepted */ NotAccepted }; /// Transaction counts towards available balance bool countsForBalance; /// Sorting key based on status std::string sortKey; /** @name Generated (mined) transactions @{*/ int matures_in; /**@}*/ /** @name Reported status @{*/ Status status; qint64 depth; /**< Timestamp if status==OpenUntilDate, otherwise number of additional * blocks that need to be mined before finalization */ qint64 open_for; /**@}*/ /** Current number of blocks (to know whether cached status is still valid) */ int cur_num_blocks; }; /** * UI model for a transaction. A core transaction can be represented by multiple * UI transactions if it has multiple outputs. */ class TransactionRecord { public: enum Type { Other, Generated, SendToAddress, SendToOther, RecvWithAddress, RecvFromOther, SendToSelf }; /** Number of confirmation recommended for accepting a transaction */ static const int RecommendedNumConfirmations = 6; TransactionRecord() : txid(), time(0), type(Other), address(""), debit(), credit(), idx(0) { } TransactionRecord(TxId _txid, qint64 _time) : txid(_txid), time(_time), type(Other), address(""), debit(), credit(), idx(0) {} TransactionRecord(TxId _txid, qint64 _time, Type _type, const std::string &_address, const Amount _debit, const Amount _credit) : txid(_txid), time(_time), type(_type), address(_address), debit(_debit), credit(_credit), idx(0) {} /** Decompose CWallet transaction to model transaction records. */ static bool showTransaction(); static QList<TransactionRecord> decomposeTransaction(const interfaces::WalletTx &wtx); /** @name Immutable transaction attributes @{*/ TxId txid; qint64 time; Type type; std::string address; Amount debit; Amount credit; /**@}*/ /** Subtransaction index, for sort key */ int idx; /** Status: can change with block chain update */ TransactionStatus status; /** Whether the transaction was sent/received with a watch-only address */ bool involvesWatchAddress; /** Return the unique identifier for this transaction (part) */ QString getTxID() const; /** Return the output index of the subtransaction */ int getOutputIndex() const; /** Update status from core wallet tx. */ - void updateStatus(const interfaces::WalletTxStatus &wtx, int numBlocks); + void updateStatus(const interfaces::WalletTxStatus &wtx, int numBlocks, + int64_t block_time); /** Return whether a status update is needed. */ bool statusUpdateNeeded(int numBlocks) const; }; #endif // BITCOIN_QT_TRANSACTIONRECORD_H diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 5a8e2845d..f7daf4df0 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -1,768 +1,769 @@ // 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/transactiontablemodel.h> #include <core_io.h> #include <interfaces/handler.h> #include <interfaces/node.h> #include <qt/addresstablemodel.h> #include <qt/guiconstants.h> #include <qt/guiutil.h> #include <qt/optionsmodel.h> #include <qt/platformstyle.h> #include <qt/transactiondesc.h> #include <qt/transactionrecord.h> #include <qt/walletmodel.h> #include <sync.h> #include <uint256.h> #include <util.h> #include <validation.h> #include <QColor> #include <QDateTime> #include <QDebug> #include <QIcon> #include <QList> // Amount column is right-aligned it contains numbers static int column_alignments[] = { Qt::AlignLeft | Qt::AlignVCenter, /* status */ Qt::AlignLeft | Qt::AlignVCenter, /* watchonly */ Qt::AlignLeft | Qt::AlignVCenter, /* date */ Qt::AlignLeft | Qt::AlignVCenter, /* type */ Qt::AlignLeft | Qt::AlignVCenter, /* address */ Qt::AlignRight | Qt::AlignVCenter /* amount */ }; // Comparison operator for sort/binary search of model tx list struct TxLessThan { bool operator()(const TransactionRecord &a, const TransactionRecord &b) const { return a.txid < b.txid; } bool operator()(const TransactionRecord &a, const TxId &b) const { return a.txid < b; } bool operator()(const TxId &a, const TransactionRecord &b) const { return a < b.txid; } }; // Private implementation class TransactionTablePriv { public: TransactionTablePriv(TransactionTableModel *_parent) : parent(_parent) {} TransactionTableModel *parent; /* Local cache of wallet. * As it is in the same order as the CWallet, by definition this is sorted * by sha256. */ QList<TransactionRecord> cachedWallet; /** * Query entire wallet anew from core. */ void refreshWallet(interfaces::Wallet &wallet) { qDebug() << "TransactionTablePriv::refreshWallet"; cachedWallet.clear(); for (const auto &wtx : wallet.getWalletTxs()) { if (TransactionRecord::showTransaction()) { cachedWallet.append( TransactionRecord::decomposeTransaction(wtx)); } } } /** * Update our model of the wallet incrementally, to synchronize our model of * the wallet with that of the core. * Call with transaction that was added, removed or changed. */ void updateWallet(interfaces::Wallet &wallet, const TxId &txid, int status, bool showTransaction) { qDebug() << "TransactionTablePriv::updateWallet: " + QString::fromStdString(txid.ToString()) + " " + QString::number(status); // Find bounds of this transaction in model QList<TransactionRecord>::iterator lower = qLowerBound( cachedWallet.begin(), cachedWallet.end(), txid, TxLessThan()); QList<TransactionRecord>::iterator upper = qUpperBound( cachedWallet.begin(), cachedWallet.end(), txid, TxLessThan()); int lowerIndex = (lower - cachedWallet.begin()); int upperIndex = (upper - cachedWallet.begin()); bool inModel = (lower != upper); if (status == CT_UPDATED) { // Not in model, but want to show, treat as new. if (showTransaction && !inModel) { status = CT_NEW; } // In model, but want to hide, treat as deleted. if (!showTransaction && inModel) { status = CT_DELETED; } } qDebug() << " inModel=" + QString::number(inModel) + " Index=" + QString::number(lowerIndex) + "-" + QString::number(upperIndex) + " showTransaction=" + QString::number(showTransaction) + " derivedStatus=" + QString::number(status); switch (status) { case CT_NEW: if (inModel) { qWarning() << "TransactionTablePriv::updateWallet: " "Warning: Got CT_NEW, but transaction is " "already in model"; break; } if (showTransaction) { // Find transaction in wallet interfaces::WalletTx wtx = wallet.getWalletTx(txid); if (!wtx.tx) { qWarning() << "TransactionTablePriv::updateWallet: " "Warning: Got CT_NEW, but transaction is " "not in wallet"; break; } // Added -- insert at the right position QList<TransactionRecord> toInsert = TransactionRecord::decomposeTransaction(wtx); /* only if something to insert */ if (!toInsert.isEmpty()) { parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex + toInsert.size() - 1); int insert_idx = lowerIndex; for (const TransactionRecord &rec : toInsert) { cachedWallet.insert(insert_idx, rec); insert_idx += 1; } parent->endInsertRows(); } } break; case CT_DELETED: if (!inModel) { qWarning() << "TransactionTablePriv::updateWallet: " "Warning: Got CT_DELETED, but transaction is " "not in model"; break; } // Removed -- remove entire transaction from table parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex - 1); cachedWallet.erase(lower, upper); parent->endRemoveRows(); break; case CT_UPDATED: // Miscellaneous updates -- nothing to do, status update will // take care of this, and is only computed for visible // transactions. break; } } int size() { return cachedWallet.size(); } TransactionRecord *index(interfaces::Wallet &wallet, int idx) { if (idx >= 0 && idx < cachedWallet.size()) { TransactionRecord *rec = &cachedWallet[idx]; // Get required locks upfront. This avoids the GUI from getting // stuck if the core is holding the locks for a longer time - for // example, during a wallet rescan. // // If a status update is needed (blocks came in since last check), // update the status of this transaction from the wallet. Otherwise, // simply re-use the cached status. interfaces::WalletTxStatus wtx; int numBlocks; - if (wallet.tryGetTxStatus(rec->txid, wtx, numBlocks) && + int64_t block_time; + if (wallet.tryGetTxStatus(rec->txid, wtx, numBlocks, block_time) && rec->statusUpdateNeeded(numBlocks)) { - rec->updateStatus(wtx, numBlocks); + rec->updateStatus(wtx, numBlocks, block_time); } return rec; } return 0; } QString describe(interfaces::Node &node, interfaces::Wallet &wallet, TransactionRecord *rec, int unit) { return TransactionDesc::toHTML(node, wallet, rec, unit); } QString getTxHex(interfaces::Wallet &wallet, TransactionRecord *rec) { auto tx = wallet.getTx(rec->txid); if (tx) { std::string strHex = EncodeHexTx(*tx); return QString::fromStdString(strHex); } return QString(); } }; TransactionTableModel::TransactionTableModel( const PlatformStyle *_platformStyle, WalletModel *parent) : QAbstractTableModel(parent), walletModel(parent), priv(new TransactionTablePriv(this)), fProcessingQueuedTransactions(false), platformStyle(_platformStyle) { columns << QString() << QString() << tr("Date") << tr("Type") << tr("Label") << BitcoinUnits::getAmountColumnTitle( walletModel->getOptionsModel()->getDisplayUnit()); priv->refreshWallet(walletModel->wallet()); connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); subscribeToCoreSignals(); } TransactionTableModel::~TransactionTableModel() { unsubscribeFromCoreSignals(); delete priv; } /** Updates the column title to "Amount (DisplayUnit)" and emits * headerDataChanged() signal for table headers to react. */ void TransactionTableModel::updateAmountColumnTitle() { columns[Amount] = BitcoinUnits::getAmountColumnTitle( walletModel->getOptionsModel()->getDisplayUnit()); Q_EMIT headerDataChanged(Qt::Horizontal, Amount, Amount); } void TransactionTableModel::updateTransaction(const QString &hash, int status, bool showTransaction) { TxId updated; updated.SetHex(hash.toStdString()); priv->updateWallet(walletModel->wallet(), updated, status, showTransaction); } void TransactionTableModel::updateConfirmations() { // Blocks came in since last poll. // Invalidate status (number of confirmations) and (possibly) description // for all rows. Qt is smart enough to only actually request the data for // the visible rows. Q_EMIT dataChanged(index(0, Status), index(priv->size() - 1, Status)); Q_EMIT dataChanged(index(0, ToAddress), index(priv->size() - 1, ToAddress)); } int TransactionTableModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return priv->size(); } int TransactionTableModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return columns.length(); } QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const { QString status; switch (wtx->status.status) { case TransactionStatus::OpenUntilBlock: status = tr("Open for %n more block(s)", "", wtx->status.open_for); break; case TransactionStatus::OpenUntilDate: status = tr("Open until %1") .arg(GUIUtil::dateTimeStr(wtx->status.open_for)); break; case TransactionStatus::Unconfirmed: status = tr("Unconfirmed"); break; case TransactionStatus::Abandoned: status = tr("Abandoned"); break; case TransactionStatus::Confirming: status = tr("Confirming (%1 of %2 recommended confirmations)") .arg(wtx->status.depth) .arg(TransactionRecord::RecommendedNumConfirmations); break; case TransactionStatus::Confirmed: status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth); break; case TransactionStatus::Conflicted: status = tr("Conflicted"); break; case TransactionStatus::Immature: status = tr("Immature (%1 confirmations, will be available after %2)") .arg(wtx->status.depth) .arg(wtx->status.depth + wtx->status.matures_in); break; case TransactionStatus::NotAccepted: status = tr("Generated but not accepted"); break; } return status; } QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const { if (wtx->time) { return GUIUtil::dateTimeStr(wtx->time); } return QString(); } /** * Look up address in address book, if found return label (address) otherwise * just return (address) */ QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const { QString label = walletModel->getAddressTableModel()->labelForAddress( QString::fromStdString(address)); QString description; if (!label.isEmpty()) { description += label; } if (label.isEmpty() || tooltip) { description += QString(" (") + QString::fromStdString(address) + QString(")"); } return description; } QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const { switch (wtx->type) { case TransactionRecord::RecvWithAddress: return tr("Received with"); case TransactionRecord::RecvFromOther: return tr("Received from"); case TransactionRecord::SendToAddress: case TransactionRecord::SendToOther: return tr("Sent to"); case TransactionRecord::SendToSelf: return tr("Payment to yourself"); case TransactionRecord::Generated: return tr("Mined"); default: return QString(); } } QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const { switch (wtx->type) { case TransactionRecord::Generated: return QIcon(":/icons/tx_mined"); case TransactionRecord::RecvWithAddress: case TransactionRecord::RecvFromOther: return QIcon(":/icons/tx_input"); case TransactionRecord::SendToAddress: case TransactionRecord::SendToOther: return QIcon(":/icons/tx_output"); default: return QIcon(":/icons/tx_inout"); } } QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const { QString watchAddress; if (tooltip) { // Mark transactions involving watch-only addresses by adding " // (watch-only)" watchAddress = wtx->involvesWatchAddress ? QString(" (") + tr("watch-only") + QString(")") : ""; } switch (wtx->type) { case TransactionRecord::RecvFromOther: return QString::fromStdString(wtx->address) + watchAddress; case TransactionRecord::RecvWithAddress: case TransactionRecord::SendToAddress: case TransactionRecord::Generated: return lookupAddress(wtx->address, tooltip) + watchAddress; case TransactionRecord::SendToOther: return QString::fromStdString(wtx->address) + watchAddress; case TransactionRecord::SendToSelf: default: return tr("(n/a)") + watchAddress; } } QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const { // Show addresses without label in a less visible color switch (wtx->type) { case TransactionRecord::RecvWithAddress: case TransactionRecord::SendToAddress: case TransactionRecord::Generated: { QString label = walletModel->getAddressTableModel()->labelForAddress( QString::fromStdString(wtx->address)); if (label.isEmpty()) return COLOR_BAREADDRESS; } break; case TransactionRecord::SendToSelf: return COLOR_BAREADDRESS; default: break; } return QVariant(); } QString TransactionTableModel::formatTxAmount( const TransactionRecord *wtx, bool showUnconfirmed, BitcoinUnits::SeparatorStyle separators) const { QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit, false, separators); if (showUnconfirmed) { if (!wtx->status.countsForBalance) { str = QString("[") + str + QString("]"); } } return QString(str); } QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const { switch (wtx->status.status) { case TransactionStatus::OpenUntilBlock: case TransactionStatus::OpenUntilDate: return COLOR_TX_STATUS_OPENUNTILDATE; case TransactionStatus::Unconfirmed: return QIcon(":/icons/transaction_0"); case TransactionStatus::Abandoned: return QIcon(":/icons/transaction_abandoned"); case TransactionStatus::Confirming: switch (wtx->status.depth) { case 1: return QIcon(":/icons/transaction_1"); case 2: return QIcon(":/icons/transaction_2"); case 3: return QIcon(":/icons/transaction_3"); case 4: return QIcon(":/icons/transaction_4"); default: return QIcon(":/icons/transaction_5"); }; case TransactionStatus::Confirmed: return QIcon(":/icons/transaction_confirmed"); case TransactionStatus::Conflicted: return QIcon(":/icons/transaction_conflicted"); case TransactionStatus::Immature: { int total = wtx->status.depth + wtx->status.matures_in; int part = (wtx->status.depth * 4 / total) + 1; return QIcon(QString(":/icons/transaction_%1").arg(part)); } case TransactionStatus::NotAccepted: return QIcon(":/icons/transaction_0"); default: return COLOR_BLACK; } } QVariant TransactionTableModel::txWatchonlyDecoration( const TransactionRecord *wtx) const { if (wtx->involvesWatchAddress) { return QIcon(":/icons/eye"); } return QVariant(); } QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const { QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec); if (rec->type == TransactionRecord::RecvFromOther || rec->type == TransactionRecord::SendToOther || rec->type == TransactionRecord::SendToAddress || rec->type == TransactionRecord::RecvWithAddress) { tooltip += QString(" ") + formatTxToAddress(rec, true); } return tooltip; } QVariant TransactionTableModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } TransactionRecord *rec = static_cast<TransactionRecord *>(index.internalPointer()); switch (role) { case RawDecorationRole: switch (index.column()) { case Status: return txStatusDecoration(rec); case Watchonly: return txWatchonlyDecoration(rec); case ToAddress: return txAddressDecoration(rec); } break; case Qt::DecorationRole: { QIcon icon = qvariant_cast<QIcon>(index.data(RawDecorationRole)); return platformStyle->TextColorIcon(icon); } case Qt::DisplayRole: switch (index.column()) { case Date: return formatTxDate(rec); case Type: return formatTxType(rec); case ToAddress: return formatTxToAddress(rec, false); case Amount: return formatTxAmount(rec, true, BitcoinUnits::separatorAlways); } break; case Qt::EditRole: // Edit role is used for sorting, so return the unformatted values switch (index.column()) { case Status: return QString::fromStdString(rec->status.sortKey); case Date: return rec->time; case Type: return formatTxType(rec); case Watchonly: return (rec->involvesWatchAddress ? 1 : 0); case ToAddress: return formatTxToAddress(rec, true); case Amount: return qint64((rec->credit + rec->debit) / SATOSHI); } break; case Qt::ToolTipRole: return formatTooltip(rec); case Qt::TextAlignmentRole: return column_alignments[index.column()]; case Qt::ForegroundRole: // Use the "danger" color for abandoned transactions if (rec->status.status == TransactionStatus::Abandoned) { return COLOR_TX_STATUS_DANGER; } // Non-confirmed (but not immature) as transactions are grey if (!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature) { return COLOR_UNCONFIRMED; } if (index.column() == Amount && (rec->credit + rec->debit) < ::Amount::zero()) { return COLOR_NEGATIVE; } if (index.column() == ToAddress) { return addressColor(rec); } break; case TypeRole: return rec->type; case DateRole: return QDateTime::fromTime_t(static_cast<uint>(rec->time)); case WatchonlyRole: return rec->involvesWatchAddress; case WatchonlyDecorationRole: return txWatchonlyDecoration(rec); case LongDescriptionRole: return priv->describe( walletModel->node(), walletModel->wallet(), rec, walletModel->getOptionsModel()->getDisplayUnit()); case AddressRole: return QString::fromStdString(rec->address); case LabelRole: return walletModel->getAddressTableModel()->labelForAddress( QString::fromStdString(rec->address)); case AmountRole: return qint64((rec->credit + rec->debit) / SATOSHI); case TxIDRole: return rec->getTxID(); case TxHashRole: return QString::fromStdString(rec->txid.ToString()); case TxHexRole: return priv->getTxHex(walletModel->wallet(), rec); case TxPlainTextRole: { QString details; QDateTime date = QDateTime::fromTime_t(static_cast<uint>(rec->time)); QString txLabel = walletModel->getAddressTableModel()->labelForAddress( QString::fromStdString(rec->address)); details.append(date.toString("M/d/yy HH:mm")); details.append(" "); details.append(formatTxStatus(rec)); details.append(". "); if (!formatTxType(rec).isEmpty()) { details.append(formatTxType(rec)); details.append(" "); } if (!rec->address.empty()) { if (txLabel.isEmpty()) { details.append(tr("(no label)") + " "); } else { details.append("("); details.append(txLabel); details.append(") "); } details.append(QString::fromStdString(rec->address)); details.append(" "); } details.append( formatTxAmount(rec, false, BitcoinUnits::separatorNever)); return details; } case ConfirmedRole: return rec->status.countsForBalance; case FormattedAmountRole: // Used for copy/export, so don't include separators return formatTxAmount(rec, false, BitcoinUnits::separatorNever); case StatusRole: return rec->status.status; } return QVariant(); } QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole) { return columns[section]; } else if (role == Qt::TextAlignmentRole) { return column_alignments[section]; } else if (role == Qt::ToolTipRole) { switch (section) { case Status: return tr("Transaction status. Hover over this field to " "show number of confirmations."); case Date: return tr( "Date and time that the transaction was received."); case Type: return tr("Type of transaction."); case Watchonly: return tr("Whether or not a watch-only address is involved " "in this transaction."); case ToAddress: return tr( "User-defined intent/purpose of the transaction."); case Amount: return tr("Amount removed from or added to balance."); } } } return QVariant(); } QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const { Q_UNUSED(parent); TransactionRecord *data = priv->index(walletModel->wallet(), row); if (data) { return createIndex(row, column, priv->index(walletModel->wallet(), row)); } return QModelIndex(); } void TransactionTableModel::updateDisplayUnit() { // emit dataChanged to update Amount column with the current unit updateAmountColumnTitle(); Q_EMIT dataChanged(index(0, Amount), index(priv->size() - 1, Amount)); } // queue notifications to show a non freezing progress dialog e.g. for rescan struct TransactionNotification { public: TransactionNotification() {} TransactionNotification(TxId _txid, ChangeType _status, bool _showTransaction) : txid(_txid), status(_status), showTransaction(_showTransaction) {} void invoke(QObject *ttm) { QString strHash = QString::fromStdString(txid.GetHex()); qDebug() << "NotifyTransactionChanged: " + strHash + " status= " + QString::number(status); QMetaObject::invokeMethod(ttm, "updateTransaction", Qt::QueuedConnection, Q_ARG(QString, strHash), Q_ARG(int, status), Q_ARG(bool, showTransaction)); } private: TxId txid; ChangeType status; bool showTransaction; }; static bool fQueueNotifications = false; static std::vector<TransactionNotification> vQueueNotifications; static void NotifyTransactionChanged(TransactionTableModel *ttm, const TxId &txid, ChangeType status) { // Find transaction in wallet // Determine whether to show transaction or not (determine this here so that // no relocking is needed in GUI thread) bool showTransaction = TransactionRecord::showTransaction(); TransactionNotification notification(txid, status, showTransaction); if (fQueueNotifications) { vQueueNotifications.push_back(notification); return; } notification.invoke(ttm); } static void ShowProgress(TransactionTableModel *ttm, const std::string &title, int nProgress) { if (nProgress == 0) { fQueueNotifications = true; } if (nProgress == 100) { fQueueNotifications = false; if (vQueueNotifications.size() > 10) { // prevent balloon spam, show maximum 10 balloons QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true)); } for (size_t i = 0; i < vQueueNotifications.size(); ++i) { if (vQueueNotifications.size() - i <= 10) { QMetaObject::invokeMethod( ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false)); } vQueueNotifications[i].invoke(ttm); } // clear std::vector<TransactionNotification>().swap(vQueueNotifications); } } void TransactionTableModel::subscribeToCoreSignals() { // Connect signals to wallet m_handler_transaction_changed = walletModel->wallet().handleTransactionChanged( boost::bind(NotifyTransactionChanged, this, _1, _2)); m_handler_show_progress = walletModel->wallet().handleShowProgress( boost::bind(ShowProgress, this, _1, _2)); } void TransactionTableModel::unsubscribeFromCoreSignals() { // Disconnect signals from wallet m_handler_transaction_changed->disconnect(); m_handler_show_progress->disconnect(); }