diff options
Diffstat (limited to 'src')
88 files changed, 1814 insertions, 993 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index be2d569173..09daaebd23 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -126,6 +126,7 @@ BITCOIN_CORE_H = \ index/txindex.h \ indirectmap.h \ init.h \ + interfaces/chain.h \ interfaces/handler.h \ interfaces/node.h \ interfaces/wallet.h \ @@ -184,6 +185,7 @@ BITCOIN_CORE_H = \ txmempool.h \ ui_interface.h \ undo.h \ + util/bytevectorhash.h \ util/system.h \ util/memory.h \ util/moneystr.h \ @@ -233,6 +235,7 @@ libbitcoin_server_a_SOURCES = \ httpserver.cpp \ index/base.cpp \ index/txindex.cpp \ + interfaces/chain.cpp \ interfaces/handler.cpp \ interfaces/node.cpp \ init.cpp \ @@ -322,7 +325,9 @@ crypto_libbitcoin_crypto_base_a_SOURCES = \ crypto/sha256.cpp \ crypto/sha256.h \ crypto/sha512.cpp \ - crypto/sha512.h + crypto/sha512.h \ + crypto/siphash.cpp \ + crypto/siphash.h if USE_ASM crypto_libbitcoin_crypto_base_a_SOURCES += crypto/sha256_sse4.cpp @@ -428,6 +433,7 @@ libbitcoin_util_a_SOURCES = \ support/cleanse.cpp \ sync.cpp \ threadinterrupt.cpp \ + util/bytevectorhash.cpp \ util/system.cpp \ util/moneystr.cpp \ util/strencodings.cpp \ @@ -462,6 +468,7 @@ endif bitcoind_LDADD = \ $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_WALLET) \ + $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_COMMON) \ $(LIBUNIVALUE) \ $(LIBBITCOIN_UTIL) \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 3ca2a7451d..445849e3d8 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -162,7 +162,8 @@ QT_MOC_CPP = \ BITCOIN_MM = \ qt/macdockiconhandler.mm \ - qt/macnotificationhandler.mm + qt/macnotificationhandler.mm \ + qt/macos_appnap.mm QT_MOC = \ qt/bitcoin.moc \ @@ -205,6 +206,7 @@ BITCOIN_QT_H = \ qt/intro.h \ qt/macdockiconhandler.h \ qt/macnotificationhandler.h \ + qt/macos_appnap.h \ qt/modaloverlay.h \ qt/networkstyle.h \ qt/notificator.h \ @@ -421,6 +423,10 @@ qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) if ENABLE_BIP70 qt_bitcoin_qt_LDADD += $(SSL_LIBS) +else +if TARGET_WINDOWS +qt_bitcoin_qt_LDADD += $(SSL_LIBS) +endif endif qt_bitcoin_qt_LDADD += $(CRYPTO_LIBS) qt_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp index ac2299374c..2def0b23e2 100644 --- a/src/bench/block_assemble.cpp +++ b/src/bench/block_assemble.cpp @@ -7,6 +7,7 @@ #include <coins.h> #include <consensus/merkle.h> #include <consensus/validation.h> +#include <crypto/sha256.h> #include <miner.h> #include <policy/policy.h> #include <pow.h> diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index decdadfb26..8552ed34fd 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <bench/bench.h> +#include <interfaces/chain.h> #include <wallet/wallet.h> #include <wallet/coinselection.h> @@ -33,7 +34,8 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<Ou // (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484) static void CoinSelection(benchmark::State& state) { - const CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy()); + auto chain = interfaces::MakeChain(); + const CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); LOCK(wallet.cs_wallet); // Add coins. @@ -57,7 +59,8 @@ static void CoinSelection(benchmark::State& state) } typedef std::set<CInputCoin> CoinSet; -static const CWallet testWallet(WalletLocation(), WalletDatabase::CreateDummy()); +static auto testChain = interfaces::MakeChain(); +static const CWallet testWallet(*testChain, WalletLocation(), WalletDatabase::CreateDummy()); std::vector<std::unique_ptr<CWalletTx>> wtxn; // Copied from src/wallet/test/coinselector_tests.cpp diff --git a/src/bench/crypto_hash.cpp b/src/bench/crypto_hash.cpp index d7499a3767..dc0b054420 100644 --- a/src/bench/crypto_hash.cpp +++ b/src/bench/crypto_hash.cpp @@ -14,6 +14,7 @@ #include <crypto/sha1.h> #include <crypto/sha256.h> #include <crypto/sha512.h> +#include <crypto/siphash.h> /* Number of bytes to hash per iteration */ static const uint64_t BUFFER_SIZE = 1000*1000; diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index bdc064b9fb..bc91ca3641 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -255,7 +255,7 @@ static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInpu throw std::runtime_error("invalid TX input vout '" + strVout + "'"); // extract the optional sequence number - uint32_t nSequenceIn=std::numeric_limits<unsigned int>::max(); + uint32_t nSequenceIn = CTxIn::SEQUENCE_FINAL; if (vStrInputParts.size() > 2) nSequenceIn = std::stoul(vStrInputParts[2]); diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index c6e2a7c20a..1306ba3821 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -11,6 +11,7 @@ #include <clientversion.h> #include <compat.h> #include <fs.h> +#include <interfaces/chain.h> #include <rpc/server.h> #include <init.h> #include <noui.h> @@ -58,6 +59,9 @@ static void WaitForShutdown() // static bool AppInit(int argc, char* argv[]) { + InitInterfaces interfaces; + interfaces.chain = interfaces::MakeChain(); + bool fRet = false; // @@ -164,7 +168,7 @@ static bool AppInit(int argc, char* argv[]) // If locking the data directory failed, exit immediately return false; } - fRet = AppInitMain(); + fRet = AppInitMain(interfaces); } catch (const std::exception& e) { PrintExceptionContinue(&e, "AppInit()"); @@ -178,7 +182,7 @@ static bool AppInit(int argc, char* argv[]) } else { WaitForShutdown(); } - Shutdown(); + Shutdown(interfaces); return fRet; } diff --git a/src/blockencodings.cpp b/src/blockencodings.cpp index a06bced11b..10f51931f0 100644 --- a/src/blockencodings.cpp +++ b/src/blockencodings.cpp @@ -6,7 +6,8 @@ #include <consensus/consensus.h> #include <consensus/validation.h> #include <chainparams.h> -#include <hash.h> +#include <crypto/sha256.h> +#include <crypto/siphash.h> #include <random.h> #include <streams.h> #include <txmempool.h> diff --git a/src/blockfilter.cpp b/src/blockfilter.cpp index 91623fe70a..163e2a52ef 100644 --- a/src/blockfilter.cpp +++ b/src/blockfilter.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <blockfilter.h> +#include <crypto/siphash.h> #include <hash.h> #include <primitives/transaction.h> #include <script/script.h> diff --git a/src/blockfilter.h b/src/blockfilter.h index 46833ac0be..871be11769 100644 --- a/src/blockfilter.h +++ b/src/blockfilter.h @@ -5,14 +5,15 @@ #ifndef BITCOIN_BLOCKFILTER_H #define BITCOIN_BLOCKFILTER_H -#include <set> #include <stdint.h> +#include <unordered_set> #include <vector> #include <primitives/block.h> #include <serialize.h> #include <uint256.h> #include <undo.h> +#include <util/bytevectorhash.h> /** * This implements a Golomb-coded set as defined in BIP 158. It is a @@ -22,7 +23,7 @@ class GCSFilter { public: typedef std::vector<unsigned char> Element; - typedef std::set<Element> ElementSet; + typedef std::unordered_set<Element, ByteVectorHash> ElementSet; private: uint64_t m_siphash_k0; diff --git a/src/coins.cpp b/src/coins.cpp index f125b483bb..3ef9e0463c 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -6,6 +6,7 @@ #include <consensus/consensus.h> #include <random.h> +#include <version.h> bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; } uint256 CCoinsView::GetBestBlock() const { return uint256(); } diff --git a/src/coins.h b/src/coins.h index 3867a37b39..94493453f0 100644 --- a/src/coins.h +++ b/src/coins.h @@ -9,7 +9,7 @@ #include <primitives/transaction.h> #include <compressor.h> #include <core_memusage.h> -#include <hash.h> +#include <crypto/siphash.h> #include <memusage.h> #include <serialize.h> #include <uint256.h> diff --git a/src/crypto/siphash.cpp b/src/crypto/siphash.cpp new file mode 100644 index 0000000000..e81957111a --- /dev/null +++ b/src/crypto/siphash.cpp @@ -0,0 +1,173 @@ +// Copyright (c) 2016-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 <crypto/siphash.h> + +#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) + +#define SIPROUND do { \ + v0 += v1; v1 = ROTL(v1, 13); v1 ^= v0; \ + v0 = ROTL(v0, 32); \ + v2 += v3; v3 = ROTL(v3, 16); v3 ^= v2; \ + v0 += v3; v3 = ROTL(v3, 21); v3 ^= v0; \ + v2 += v1; v1 = ROTL(v1, 17); v1 ^= v2; \ + v2 = ROTL(v2, 32); \ +} while (0) + +CSipHasher::CSipHasher(uint64_t k0, uint64_t k1) +{ + v[0] = 0x736f6d6570736575ULL ^ k0; + v[1] = 0x646f72616e646f6dULL ^ k1; + v[2] = 0x6c7967656e657261ULL ^ k0; + v[3] = 0x7465646279746573ULL ^ k1; + count = 0; + tmp = 0; +} + +CSipHasher& CSipHasher::Write(uint64_t data) +{ + uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; + + assert(count % 8 == 0); + + v3 ^= data; + SIPROUND; + SIPROUND; + v0 ^= data; + + v[0] = v0; + v[1] = v1; + v[2] = v2; + v[3] = v3; + + count += 8; + return *this; +} + +CSipHasher& CSipHasher::Write(const unsigned char* data, size_t size) +{ + uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; + uint64_t t = tmp; + int c = count; + + while (size--) { + t |= ((uint64_t)(*(data++))) << (8 * (c % 8)); + c++; + if ((c & 7) == 0) { + v3 ^= t; + SIPROUND; + SIPROUND; + v0 ^= t; + t = 0; + } + } + + v[0] = v0; + v[1] = v1; + v[2] = v2; + v[3] = v3; + count = c; + tmp = t; + + return *this; +} + +uint64_t CSipHasher::Finalize() const +{ + uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; + + uint64_t t = tmp | (((uint64_t)count) << 56); + + v3 ^= t; + SIPROUND; + SIPROUND; + v0 ^= t; + v2 ^= 0xFF; + SIPROUND; + SIPROUND; + SIPROUND; + SIPROUND; + return v0 ^ v1 ^ v2 ^ v3; +} + +uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val) +{ + /* Specialized implementation for efficiency */ + uint64_t d = val.GetUint64(0); + + uint64_t v0 = 0x736f6d6570736575ULL ^ k0; + uint64_t v1 = 0x646f72616e646f6dULL ^ k1; + uint64_t v2 = 0x6c7967656e657261ULL ^ k0; + uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; + + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(1); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(2); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(3); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + v3 ^= ((uint64_t)4) << 59; + SIPROUND; + SIPROUND; + v0 ^= ((uint64_t)4) << 59; + v2 ^= 0xFF; + SIPROUND; + SIPROUND; + SIPROUND; + SIPROUND; + return v0 ^ v1 ^ v2 ^ v3; +} + +uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra) +{ + /* Specialized implementation for efficiency */ + uint64_t d = val.GetUint64(0); + + uint64_t v0 = 0x736f6d6570736575ULL ^ k0; + uint64_t v1 = 0x646f72616e646f6dULL ^ k1; + uint64_t v2 = 0x6c7967656e657261ULL ^ k0; + uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; + + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(1); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(2); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(3); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = (((uint64_t)36) << 56) | extra; + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + v2 ^= 0xFF; + SIPROUND; + SIPROUND; + SIPROUND; + SIPROUND; + return v0 ^ v1 ^ v2 ^ v3; +} diff --git a/src/crypto/siphash.h b/src/crypto/siphash.h new file mode 100644 index 0000000000..b312f913f9 --- /dev/null +++ b/src/crypto/siphash.h @@ -0,0 +1,47 @@ +// Copyright (c) 2016-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_CRYPTO_SIPHASH_H +#define BITCOIN_CRYPTO_SIPHASH_H + +#include <stdint.h> + +#include <uint256.h> + +/** SipHash-2-4 */ +class CSipHasher +{ +private: + uint64_t v[4]; + uint64_t tmp; + int count; + +public: + /** Construct a SipHash calculator initialized with 128-bit key (k0, k1) */ + CSipHasher(uint64_t k0, uint64_t k1); + /** Hash a 64-bit integer worth of data + * It is treated as if this was the little-endian interpretation of 8 bytes. + * This function can only be used when a multiple of 8 bytes have been written so far. + */ + CSipHasher& Write(uint64_t data); + /** Hash arbitrary bytes. */ + CSipHasher& Write(const unsigned char* data, size_t size); + /** Compute the 64-bit SipHash-2-4 of the data written so far. The object remains untouched. */ + uint64_t Finalize() const; +}; + +/** Optimized SipHash-2-4 implementation for uint256. + * + * It is identical to: + * SipHasher(k0, k1) + * .Write(val.GetUint64(0)) + * .Write(val.GetUint64(1)) + * .Write(val.GetUint64(2)) + * .Write(val.GetUint64(3)) + * .Finalize() + */ +uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val); +uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra); + +#endif // BITCOIN_CRYPTO_SIPHASH_H diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index 2a9b297029..9211a7596b 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -14,13 +14,7 @@ public: bool HasWalletSupport() const override {return false;} void AddWalletOptions() const override; bool ParameterInteraction() const override {return true;} - void RegisterRPC(CRPCTable &) const override {} - bool Verify() const override {return true;} - bool Open() const override {LogPrintf("No wallet support compiled in!\n"); return true;} - void Start(CScheduler& scheduler) const override {} - void Flush() const override {} - void Stop() const override {} - void Close() const override {} + void Construct(InitInterfaces& interfaces) const override {LogPrintf("No wallet support compiled in!\n");} }; void DummyWalletInit::AddWalletOptions() const diff --git a/src/hash.cpp b/src/hash.cpp index c049eea716..26150e5ca8 100644 --- a/src/hash.cpp +++ b/src/hash.cpp @@ -77,171 +77,3 @@ void BIP32Hash(const ChainCode &chainCode, unsigned int nChild, unsigned char he num[3] = (nChild >> 0) & 0xFF; CHMAC_SHA512(chainCode.begin(), chainCode.size()).Write(&header, 1).Write(data, 32).Write(num, 4).Finalize(output); } - -#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) - -#define SIPROUND do { \ - v0 += v1; v1 = ROTL(v1, 13); v1 ^= v0; \ - v0 = ROTL(v0, 32); \ - v2 += v3; v3 = ROTL(v3, 16); v3 ^= v2; \ - v0 += v3; v3 = ROTL(v3, 21); v3 ^= v0; \ - v2 += v1; v1 = ROTL(v1, 17); v1 ^= v2; \ - v2 = ROTL(v2, 32); \ -} while (0) - -CSipHasher::CSipHasher(uint64_t k0, uint64_t k1) -{ - v[0] = 0x736f6d6570736575ULL ^ k0; - v[1] = 0x646f72616e646f6dULL ^ k1; - v[2] = 0x6c7967656e657261ULL ^ k0; - v[3] = 0x7465646279746573ULL ^ k1; - count = 0; - tmp = 0; -} - -CSipHasher& CSipHasher::Write(uint64_t data) -{ - uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; - - assert(count % 8 == 0); - - v3 ^= data; - SIPROUND; - SIPROUND; - v0 ^= data; - - v[0] = v0; - v[1] = v1; - v[2] = v2; - v[3] = v3; - - count += 8; - return *this; -} - -CSipHasher& CSipHasher::Write(const unsigned char* data, size_t size) -{ - uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; - uint64_t t = tmp; - int c = count; - - while (size--) { - t |= ((uint64_t)(*(data++))) << (8 * (c % 8)); - c++; - if ((c & 7) == 0) { - v3 ^= t; - SIPROUND; - SIPROUND; - v0 ^= t; - t = 0; - } - } - - v[0] = v0; - v[1] = v1; - v[2] = v2; - v[3] = v3; - count = c; - tmp = t; - - return *this; -} - -uint64_t CSipHasher::Finalize() const -{ - uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; - - uint64_t t = tmp | (((uint64_t)count) << 56); - - v3 ^= t; - SIPROUND; - SIPROUND; - v0 ^= t; - v2 ^= 0xFF; - SIPROUND; - SIPROUND; - SIPROUND; - SIPROUND; - return v0 ^ v1 ^ v2 ^ v3; -} - -uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val) -{ - /* Specialized implementation for efficiency */ - uint64_t d = val.GetUint64(0); - - uint64_t v0 = 0x736f6d6570736575ULL ^ k0; - uint64_t v1 = 0x646f72616e646f6dULL ^ k1; - uint64_t v2 = 0x6c7967656e657261ULL ^ k0; - uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; - - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(1); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(2); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(3); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - v3 ^= ((uint64_t)4) << 59; - SIPROUND; - SIPROUND; - v0 ^= ((uint64_t)4) << 59; - v2 ^= 0xFF; - SIPROUND; - SIPROUND; - SIPROUND; - SIPROUND; - return v0 ^ v1 ^ v2 ^ v3; -} - -uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra) -{ - /* Specialized implementation for efficiency */ - uint64_t d = val.GetUint64(0); - - uint64_t v0 = 0x736f6d6570736575ULL ^ k0; - uint64_t v1 = 0x646f72616e646f6dULL ^ k1; - uint64_t v2 = 0x6c7967656e657261ULL ^ k0; - uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; - - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(1); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(2); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(3); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = (((uint64_t)36) << 56) | extra; - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - v2 ^= 0xFF; - SIPROUND; - SIPROUND; - SIPROUND; - SIPROUND; - return v0 ^ v1 ^ v2 ^ v3; -} diff --git a/src/hash.h b/src/hash.h index 3534a400b3..6acab0b161 100644 --- a/src/hash.h +++ b/src/hash.h @@ -194,39 +194,4 @@ unsigned int MurmurHash3(unsigned int nHashSeed, const std::vector<unsigned char void BIP32Hash(const ChainCode &chainCode, unsigned int nChild, unsigned char header, const unsigned char data[32], unsigned char output[64]); -/** SipHash-2-4 */ -class CSipHasher -{ -private: - uint64_t v[4]; - uint64_t tmp; - int count; - -public: - /** Construct a SipHash calculator initialized with 128-bit key (k0, k1) */ - CSipHasher(uint64_t k0, uint64_t k1); - /** Hash a 64-bit integer worth of data - * It is treated as if this was the little-endian interpretation of 8 bytes. - * This function can only be used when a multiple of 8 bytes have been written so far. - */ - CSipHasher& Write(uint64_t data); - /** Hash arbitrary bytes. */ - CSipHasher& Write(const unsigned char* data, size_t size); - /** Compute the 64-bit SipHash-2-4 of the data written so far. The object remains untouched. */ - uint64_t Finalize() const; -}; - -/** Optimized SipHash-2-4 implementation for uint256. - * - * It is identical to: - * SipHasher(k0, k1) - * .Write(val.GetUint64(0)) - * .Write(val.GetUint64(1)) - * .Write(val.GetUint64(2)) - * .Write(val.GetUint64(3)) - * .Finalize() - */ -uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val); -uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra); - #endif // BITCOIN_HASH_H diff --git a/src/init.cpp b/src/init.cpp index d54d4a8782..3ab97be329 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -19,6 +19,7 @@ #include <fs.h> #include <httpserver.h> #include <httprpc.h> +#include <interfaces/chain.h> #include <index/txindex.h> #include <key.h> #include <validation.h> @@ -32,6 +33,7 @@ #include <rpc/server.h> #include <rpc/register.h> #include <rpc/blockchain.h> +#include <rpc/util.h> #include <script/standard.h> #include <script/sigcache.h> #include <scheduler.h> @@ -157,7 +159,7 @@ void Interrupt() } } -void Shutdown() +void Shutdown(InitInterfaces& interfaces) { LogPrintf("%s: In progress...\n", __func__); static CCriticalSection cs_Shutdown; @@ -176,7 +178,9 @@ void Shutdown() StopREST(); StopRPC(); StopHTTPServer(); - g_wallet_init_interface.Flush(); + for (const auto& client : interfaces.chain_clients) { + client->flush(); + } StopMapPort(); // Because these depend on each-other, we make sure that neither can be @@ -239,7 +243,9 @@ void Shutdown() pcoinsdbview.reset(); pblocktree.reset(); } - g_wallet_init_interface.Stop(); + for (const auto& client : interfaces.chain_clients) { + client->stop(); + } #if ENABLE_ZMQ if (g_zmq_notification_interface) { @@ -259,7 +265,7 @@ void Shutdown() UnregisterAllValidationInterfaces(); GetMainSignals().UnregisterBackgroundSignalScheduler(); GetMainSignals().UnregisterWithMempoolSignals(mempool); - g_wallet_init_interface.Close(); + interfaces.chain_clients.clear(); globalVerifyHandle.reset(); ECC_Stop(); LogPrintf("%s: done\n", __func__); @@ -1158,7 +1164,7 @@ bool AppInitLockDataDirectory() return true; } -bool AppInitMain() +bool AppInitMain(InitInterfaces& interfaces) { const CChainParams& chainparams = Params(); // ********************************************************* Step 4a: application initialization @@ -1221,11 +1227,20 @@ bool AppInitMain() GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); GetMainSignals().RegisterWithMempoolSignals(mempool); + // Create client interfaces for wallets that are supposed to be loaded + // according to -wallet and -disablewallet options. This only constructs + // the interfaces, it doesn't load wallet data. Wallets actually get loaded + // when load() and start() interface methods are called below. + g_wallet_init_interface.Construct(interfaces); + /* Register RPC commands regardless of -server setting so they will be * available in the GUI RPC console even if external calls are disabled. */ RegisterAllCoreRPCCommands(tableRPC); - g_wallet_init_interface.RegisterRPC(tableRPC); + for (const auto& client : interfaces.chain_clients) { + client->registerRpcs(); + } + g_rpc_interfaces = &interfaces; #if ENABLE_ZMQ RegisterZMQRPCCommands(tableRPC); #endif @@ -1243,7 +1258,11 @@ bool AppInitMain() } // ********************************************************* Step 5: verify wallet database integrity - if (!g_wallet_init_interface.Verify()) return false; + for (const auto& client : interfaces.chain_clients) { + if (!client->verify()) { + return false; + } + } // ********************************************************* Step 6: network initialization // Note that we absolutely cannot open any actual connections @@ -1562,7 +1581,11 @@ bool AppInitMain() } // ********************************************************* Step 9: load wallet - if (!g_wallet_init_interface.Open()) return false; + for (const auto& client : interfaces.chain_clients) { + if (!client->load()) { + return false; + } + } // ********************************************************* Step 10: data directory maintenance @@ -1708,7 +1731,9 @@ bool AppInitMain() SetRPCWarmupFinished(); uiInterface.InitMessage(_("Done loading")); - g_wallet_init_interface.Start(scheduler); + for (const auto& client : interfaces.chain_clients) { + client->start(scheduler); + } return true; } diff --git a/src/init.h b/src/init.h index b106353d08..1c59ca069e 100644 --- a/src/init.h +++ b/src/init.h @@ -10,8 +10,17 @@ #include <string> #include <util/system.h> -class CScheduler; -class CWallet; +namespace interfaces { +class Chain; +class ChainClient; +} // namespace interfaces + +//! Pointers to interfaces used during init and destroyed on shutdown. +struct InitInterfaces +{ + std::unique_ptr<interfaces::Chain> chain; + std::vector<std::unique_ptr<interfaces::ChainClient>> chain_clients; +}; namespace boost { @@ -20,7 +29,7 @@ class thread_group; /** Interrupt threads */ void Interrupt(); -void Shutdown(); +void Shutdown(InitInterfaces& interfaces); //!Initialize the logging infrastructure void InitLogging(); //!Parameter interaction: change current parameters depending on various rules @@ -54,7 +63,7 @@ bool AppInitLockDataDirectory(); * @note This should only be done after daemonization. Call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read, AppInitLockDataDirectory should have been called. */ -bool AppInitMain(); +bool AppInitMain(InitInterfaces& interfaces); /** * Setup the arguments for gArgs diff --git a/src/interfaces/README.md b/src/interfaces/README.md index e93b91d23c..57d41df746 100644 --- a/src/interfaces/README.md +++ b/src/interfaces/README.md @@ -4,7 +4,7 @@ The following interfaces are defined here: * [`Chain`](chain.h) — used by wallet to access blockchain and mempool state. Added in [#10973](https://github.com/bitcoin/bitcoin/pull/10973). -* [`Chain::Client`](chain.h) — used by node to start & stop `Chain` clients. Added in [#10973](https://github.com/bitcoin/bitcoin/pull/10973). +* [`ChainClient`](chain.h) — used by node to start & stop `Chain` clients. Added in [#10973](https://github.com/bitcoin/bitcoin/pull/10973). * [`Node`](node.h) — used by GUI to start & stop bitcoin node. Added in [#10244](https://github.com/bitcoin/bitcoin/pull/10244). diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp new file mode 100644 index 0000000000..2571a91031 --- /dev/null +++ b/src/interfaces/chain.cpp @@ -0,0 +1,44 @@ +// 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/chain.h> + +#include <sync.h> +#include <util/system.h> +#include <validation.h> + +#include <memory> +#include <utility> + +namespace interfaces { +namespace { + +class LockImpl : public Chain::Lock +{ +}; + +class LockingStateImpl : public LockImpl, public UniqueLock<CCriticalSection> +{ + using UniqueLock::UniqueLock; +}; + +class ChainImpl : public Chain +{ +public: + std::unique_ptr<Chain::Lock> lock(bool try_lock) override + { + auto result = MakeUnique<LockingStateImpl>(::cs_main, "cs_main", __FILE__, __LINE__, try_lock); + if (try_lock && result && !*result) return {}; + // std::move necessary on some compilers due to conversion from + // LockingStateImpl to Lock pointer + return std::move(result); + } + std::unique_ptr<Chain::Lock> assumeLocked() override { return MakeUnique<LockImpl>(); } +}; + +} // namespace + +std::unique_ptr<Chain> MakeChain() { return MakeUnique<ChainImpl>(); } + +} // namespace interfaces diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h new file mode 100644 index 0000000000..fe5658de4b --- /dev/null +++ b/src/interfaces/chain.h @@ -0,0 +1,84 @@ +// 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_CHAIN_H +#define BITCOIN_INTERFACES_CHAIN_H + +#include <memory> +#include <string> +#include <vector> + +class CScheduler; + +namespace interfaces { + +//! Interface for giving wallet processes access to blockchain state. +class Chain +{ +public: + virtual ~Chain() {} + + //! Interface for querying locked chain state, used by legacy code that + //! assumes state won't change between calls. New code should avoid using + //! the Lock interface and instead call higher-level Chain methods + //! that return more information so the chain doesn't need to stay locked + //! between calls. + class Lock + { + public: + virtual ~Lock() {} + }; + + //! Return Lock interface. Chain is locked when this is called, and + //! unlocked when the returned interface is freed. + virtual std::unique_ptr<Lock> lock(bool try_lock = false) = 0; + + //! Return Lock interface assuming chain is already locked. This + //! method is temporary and is only used in a few places to avoid changing + //! behavior while code is transitioned to use the Chain::Lock interface. + virtual std::unique_ptr<Lock> assumeLocked() = 0; +}; + +//! Interface to let node manage chain clients (wallets, or maybe tools for +//! monitoring and analysis in the future). +class ChainClient +{ +public: + virtual ~ChainClient() {} + + //! Register rpcs. + virtual void registerRpcs() = 0; + + //! Check for errors before loading. + virtual bool verify() = 0; + + //! Load saved state. + virtual bool load() = 0; + + //! Start client execution and provide a scheduler. + virtual void start(CScheduler& scheduler) = 0; + + //! Save state to disk. + virtual void flush() = 0; + + //! Shut down client. + virtual void stop() = 0; +}; + +//! Return implementation of Chain interface. +std::unique_ptr<Chain> MakeChain(); + +//! Return implementation of ChainClient interface for a wallet client. This +//! function will be undefined in builds where ENABLE_WALLET is false. +//! +//! Currently, wallets are the only chain clients. But in the future, other +//! types of chain clients could be added, such as tools for monitoring, +//! analysis, or fee estimation. These clients need to expose their own +//! MakeXXXClient functions returning their implementations of the ChainClient +//! interface. +std::unique_ptr<ChainClient> MakeWalletClient(Chain& chain, std::vector<std::string> wallet_filenames); + +} // namespace interfaces + +#endif // BITCOIN_INTERFACES_CHAIN_H diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index 490c456e6e..1919e16a66 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -9,6 +9,7 @@ #include <chain.h> #include <chainparams.h> #include <init.h> +#include <interfaces/chain.h> #include <interfaces/handler.h> #include <interfaces/wallet.h> #include <net.h> @@ -50,6 +51,8 @@ namespace { class NodeImpl : public Node { +public: + NodeImpl() { m_interfaces.chain = MakeChain(); } bool parseParameters(int argc, const char* const argv[], std::string& error) override { return gArgs.ParseParameters(argc, argv, error); @@ -68,11 +71,11 @@ class NodeImpl : public Node return AppInitBasicSetup() && AppInitParameterInteraction() && AppInitSanityChecks() && AppInitLockDataDirectory(); } - bool appInitMain() override { return AppInitMain(); } + bool appInitMain() override { return AppInitMain(m_interfaces); } void appShutdown() override { Interrupt(); - Shutdown(); + Shutdown(m_interfaces); } void startShutdown() override { StartShutdown(); } bool shutdownRequested() override { return ShutdownRequested(); } @@ -291,6 +294,7 @@ class NodeImpl : public Node GuessVerificationProgress(Params().TxData(), block)); })); } + InitInterfaces m_interfaces; }; } // namespace diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 14c6bd0330..672a557d41 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -7,12 +7,16 @@ #include <amount.h> #include <chain.h> #include <consensus/validation.h> +#include <init.h> +#include <interfaces/chain.h> #include <interfaces/handler.h> #include <net.h> #include <policy/feerate.h> #include <policy/fees.h> #include <policy/policy.h> #include <primitives/transaction.h> +#include <rpc/server.h> +#include <scheduler.h> #include <script/ismine.h> #include <script/standard.h> #include <support/allocators/secure.h> @@ -20,10 +24,18 @@ #include <timedata.h> #include <ui_interface.h> #include <uint256.h> +#include <util/system.h> #include <validation.h> #include <wallet/feebumper.h> #include <wallet/fees.h> +#include <wallet/rpcwallet.h> #include <wallet/wallet.h> +#include <wallet/walletutil.h> + +#include <memory> +#include <string> +#include <utility> +#include <vector> namespace interfaces { namespace { @@ -41,7 +53,8 @@ public: WalletOrderForm order_form, std::string& reject_reason) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); CValidationState state; if (!m_wallet.CommitTransaction(m_tx, std::move(value_map), std::move(order_form), m_key, g_connman.get(), state)) { reject_reason = state.GetRejectReason(); @@ -56,7 +69,7 @@ public: }; //! Construct wallet tx struct. -static WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, const CWalletTx& wtx) { WalletTx result; result.tx = wtx.tx; @@ -74,7 +87,7 @@ static WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) EXCLUSIVE_LO IsMine(wallet, result.txout_address.back()) : ISMINE_NO); } - result.credit = wtx.GetCredit(ISMINE_ALL); + result.credit = wtx.GetCredit(locked_chain, ISMINE_ALL); result.debit = wtx.GetDebit(ISMINE_ALL); result.change = wtx.GetChange(); result.time = wtx.GetTxTime(); @@ -84,32 +97,38 @@ static WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) EXCLUSIVE_LO } //! Construct wallet tx status struct. -static WalletTxStatus MakeWalletTxStatus(const CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx) { + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + WalletTxStatus result; auto mi = ::mapBlockIndex.find(wtx.hashBlock); CBlockIndex* block = mi != ::mapBlockIndex.end() ? mi->second : nullptr; 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.blocks_to_maturity = wtx.GetBlocksToMaturity(locked_chain); + result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain); result.time_received = wtx.nTimeReceived; result.lock_time = wtx.tx->nLockTime; result.is_final = CheckFinalTx(*wtx.tx); - result.is_trusted = wtx.IsTrusted(); + result.is_trusted = wtx.IsTrusted(locked_chain); result.is_abandoned = wtx.isAbandoned(); result.is_coinbase = wtx.IsCoinBase(); - result.is_in_main_chain = wtx.IsInMainChain(); + result.is_in_main_chain = wtx.IsInMainChain(locked_chain); return result; } //! Construct wallet TxOut struct. -static WalletTxOut MakeWalletTxOut(CWallet& wallet, const CWalletTx& wtx, int n, int depth) EXCLUSIVE_LOCKS_REQUIRED(cs_main, wallet.cs_wallet) +WalletTxOut MakeWalletTxOut(interfaces::Chain::Lock& locked_chain, + CWallet& wallet, + const CWalletTx& wtx, + int n, + int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { WalletTxOut result; result.txout = wtx.tx->vout[n]; result.time = wtx.GetTxTime(); result.depth_in_main_chain = depth; - result.is_spent = wallet.IsSpent(wtx.GetHash(), n); + result.is_spent = wallet.IsSpent(locked_chain, wtx.GetHash(), n); return result; } @@ -197,22 +216,26 @@ public: } void lockCoin(const COutPoint& output) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.LockCoin(output); } void unlockCoin(const COutPoint& output) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.UnlockCoin(output); } bool isLockedCoin(const COutPoint& output) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.IsLockedCoin(output.hash, output.n); } void listLockedCoins(std::vector<COutPoint>& outputs) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.ListLockedCoins(outputs); } std::unique_ptr<PendingWalletTx> createTransaction(const std::vector<CRecipient>& recipients, @@ -222,9 +245,10 @@ public: CAmount& fee, std::string& fail_reason) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); auto pending = MakeUnique<PendingWalletTxImpl>(m_wallet); - if (!m_wallet.CreateTransaction(recipients, pending->m_tx, pending->m_key, fee, change_pos, + if (!m_wallet.CreateTransaction(*locked_chain, recipients, pending->m_tx, pending->m_key, fee, change_pos, fail_reason, coin_control, sign)) { return {}; } @@ -233,8 +257,9 @@ public: bool transactionCanBeAbandoned(const uint256& txid) override { return m_wallet.TransactionCanBeAbandoned(txid); } bool abandonTransaction(const uint256& txid) override { - LOCK2(cs_main, m_wallet.cs_wallet); - return m_wallet.AbandonTransaction(txid); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); + return m_wallet.AbandonTransaction(*locked_chain, txid); } bool transactionCanBeBumped(const uint256& txid) override { @@ -262,7 +287,8 @@ public: } CTransactionRef getTx(const uint256& txid) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); auto mi = m_wallet.mapWallet.find(txid); if (mi != m_wallet.mapWallet.end()) { return mi->second.tx; @@ -271,20 +297,22 @@ public: } WalletTx getWalletTx(const uint256& txid) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); auto mi = m_wallet.mapWallet.find(txid); if (mi != m_wallet.mapWallet.end()) { - return MakeWalletTx(m_wallet, mi->second); + return MakeWalletTx(*locked_chain, m_wallet, mi->second); } return {}; } std::vector<WalletTx> getWalletTxs() override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(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)); + result.emplace_back(MakeWalletTx(*locked_chain, m_wallet, entry.second)); } return result; } @@ -292,7 +320,7 @@ public: interfaces::WalletTxStatus& tx_status, int& num_blocks) override { - TRY_LOCK(::cs_main, locked_chain); + auto locked_chain = m_wallet.chain().lock(true /* try_lock */); if (!locked_chain) { return false; } @@ -305,7 +333,7 @@ public: return false; } num_blocks = ::chainActive.Height(); - tx_status = MakeWalletTxStatus(mi->second); + tx_status = MakeWalletTxStatus(*locked_chain, mi->second); return true; } WalletTx getWalletTxDetails(const uint256& txid, @@ -314,14 +342,15 @@ public: bool& in_mempool, int& num_blocks) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(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); + tx_status = MakeWalletTxStatus(*locked_chain, mi->second); + return MakeWalletTx(*locked_chain, m_wallet, mi->second); } return {}; } @@ -341,7 +370,7 @@ public: } bool tryGetBalances(WalletBalances& balances, int& num_blocks) override { - TRY_LOCK(cs_main, locked_chain); + auto locked_chain = m_wallet.chain().lock(true /* try_lock */); if (!locked_chain) return false; TRY_LOCK(m_wallet.cs_wallet, locked_wallet); if (!locked_wallet) { @@ -358,49 +387,55 @@ public: } isminetype txinIsMine(const CTxIn& txin) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.IsMine(txin); } isminetype txoutIsMine(const CTxOut& txout) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.IsMine(txout); } CAmount getDebit(const CTxIn& txin, isminefilter filter) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.GetDebit(txin, filter); } CAmount getCredit(const CTxOut& txout, isminefilter filter) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.GetCredit(txout, filter); } CoinsList listCoins() override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); CoinsList result; - for (const auto& entry : m_wallet.ListCoins()) { + for (const auto& entry : m_wallet.ListCoins(*locked_chain)) { auto& group = result[entry.first]; for (const auto& coin : entry.second) { - group.emplace_back( - COutPoint(coin.tx->GetHash(), coin.i), MakeWalletTxOut(m_wallet, *coin.tx, coin.i, coin.nDepth)); + group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i), + MakeWalletTxOut(*locked_chain, 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); + auto locked_chain = m_wallet.chain().lock(); + LOCK(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.hash); if (it != m_wallet.mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(); + int depth = it->second.GetDepthInMainChain(*locked_chain); if (depth >= 0) { - result.back() = MakeWalletTxOut(m_wallet, it->second, output.n, depth); + result.back() = MakeWalletTxOut(*locked_chain, m_wallet, it->second, output.n, depth); } } } @@ -456,8 +491,32 @@ public: CWallet& m_wallet; }; +class WalletClientImpl : public ChainClient +{ +public: + WalletClientImpl(Chain& chain, std::vector<std::string> wallet_filenames) + : m_chain(chain), m_wallet_filenames(std::move(wallet_filenames)) + { + } + void registerRpcs() override { return RegisterWalletRPCCommands(::tableRPC); } + bool verify() override { return VerifyWallets(m_chain, m_wallet_filenames); } + bool load() override { return LoadWallets(m_chain, m_wallet_filenames); } + void start(CScheduler& scheduler) override { return StartWallets(scheduler); } + void flush() override { return FlushWallets(); } + void stop() override { return StopWallets(); } + ~WalletClientImpl() override { UnloadWallets(); } + + Chain& m_chain; + std::vector<std::string> m_wallet_filenames; +}; + } // namespace std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet) { return MakeUnique<WalletImpl>(wallet); } +std::unique_ptr<ChainClient> MakeWalletClient(Chain& chain, std::vector<std::string> wallet_filenames) +{ + return MakeUnique<WalletClientImpl>(chain, std::move(wallet_filenames)); +} + } // namespace interfaces @@ -11,6 +11,7 @@ #include <amount.h> #include <bloom.h> #include <compat.h> +#include <crypto/siphash.h> #include <hash.h> #include <limitedmap.h> #include <netaddress.h> diff --git a/src/net_processing.cpp b/src/net_processing.cpp index f37312d155..f4ab3aa153 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -2357,6 +2357,23 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr for (const CTransactionRef& removedTx : lRemovedTxn) AddToCompactExtraTransactions(removedTx); + // If a tx has been detected by recentRejects, we will have reached + // this point and the tx will have been ignored. Because we haven't run + // the tx through AcceptToMemoryPool, we won't have computed a DoS + // score for it or determined exactly why we consider it invalid. + // + // This means we won't penalize any peer subsequently relaying a DoSy + // tx (even if we penalized the first peer who gave it to us) because + // we have to account for recentRejects showing false positives. In + // other words, we shouldn't penalize a peer if we aren't *sure* they + // submitted a DoSy tx. + // + // Note that recentRejects doesn't just record DoSy or invalid + // transactions, but any tx not accepted by the mempool, which may be + // due to node policy (vs. consensus). So we can't blanket penalize a + // peer simply for relaying a tx that our recentRejects has caught, + // regardless of false positives. + int nDoS = 0; if (state.IsInvalid(nDoS)) { diff --git a/src/policy/rbf.cpp b/src/policy/rbf.cpp index 18f9c0c2a8..0dc130d104 100644 --- a/src/policy/rbf.cpp +++ b/src/policy/rbf.cpp @@ -7,7 +7,7 @@ bool SignalsOptInRBF(const CTransaction &tx) { for (const CTxIn &txin : tx.vin) { - if (txin.nSequence < std::numeric_limits<unsigned int>::max()-1) { + if (txin.nSequence <= MAX_BIP125_RBF_SEQUENCE) { return true; } } diff --git a/src/prevector.h b/src/prevector.h index 6ddb6f321f..aa77573746 100644 --- a/src/prevector.h +++ b/src/prevector.h @@ -10,6 +10,7 @@ #include <stdint.h> #include <string.h> +#include <algorithm> #include <cstddef> #include <iterator> #include <type_traits> @@ -198,22 +199,11 @@ private: const T* item_ptr(difference_type pos) const { return is_direct() ? direct_ptr(pos) : indirect_ptr(pos); } void fill(T* dst, ptrdiff_t count) { - if (IS_TRIVIALLY_CONSTRUCTIBLE<T>::value) { - // The most common use of prevector is where T=unsigned char. For - // trivially constructible types, we can use memset() to avoid - // looping. - ::memset(dst, 0, count * sizeof(T)); - } else { - for (auto i = 0; i < count; ++i) { - new(static_cast<void*>(dst + i)) T(); - } - } + std::fill_n(dst, count, T{}); } void fill(T* dst, ptrdiff_t count, const T& value) { - for (auto i = 0; i < count; ++i) { - new(static_cast<void*>(dst + i)) T(value); - } + std::fill_n(dst, count, value); } template<typename InputIterator> diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index a0270daf99..821c23c467 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -152,14 +152,15 @@ void AskPassphraseDialog::accept() } } break; case Unlock: - if(!model->setWalletLocked(false, oldpass)) - { - QMessageBox::critical(this, tr("Wallet unlock failed"), - tr("The passphrase entered for the wallet decryption was incorrect.")); - } - else - { - QDialog::accept(); // Success + try { + if (!model->setWalletLocked(false, oldpass)) { + QMessageBox::critical(this, tr("Wallet unlock failed"), + tr("The passphrase entered for the wallet decryption was incorrect.")); + } else { + QDialog::accept(); // Success + } + } catch (const std::runtime_error& e) { + QMessageBox::critical(this, tr("Wallet unlock failed"), e.what()); } break; case Decrypt: diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 7508f596e6..de236a016f 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -570,10 +570,8 @@ int main(int argc, char *argv[]) Q_INIT_RESOURCE(bitcoin_locale); BitcoinApplication app(*node, argc, argv); -#if QT_VERSION > 0x050100 // Generate high-dpi pixmaps QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); -#endif #if QT_VERSION >= 0x050600 QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index b68f3a439b..558fcf50ba 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -23,9 +23,7 @@ class AmountSpinBox: public QAbstractSpinBox public: explicit AmountSpinBox(QWidget *parent): - QAbstractSpinBox(parent), - currentUnit(BitcoinUnits::BTC), - singleStep(100000) // satoshis + QAbstractSpinBox(parent) { setAlignment(Qt::AlignRight); @@ -44,10 +42,19 @@ public: void fixup(QString &input) const { - bool valid = false; - CAmount val = parse(input, &valid); - if(valid) - { + bool valid; + CAmount val; + + if (input.isEmpty() && !m_allow_empty) { + valid = true; + val = m_min_amount; + } else { + valid = false; + val = parse(input, &valid); + } + + if (valid) { + val = qBound(m_min_amount, val, m_max_amount); input = BitcoinUnits::format(currentUnit, val, false, BitcoinUnits::separatorAlways); lineEdit()->setText(input); } @@ -64,12 +71,27 @@ public: Q_EMIT valueChanged(); } + void SetAllowEmpty(bool allow) + { + m_allow_empty = allow; + } + + void SetMinValue(const CAmount& value) + { + m_min_amount = value; + } + + void SetMaxValue(const CAmount& value) + { + m_max_amount = value; + } + void stepBy(int steps) { bool valid = false; CAmount val = value(&valid); val = val + steps * singleStep; - val = qMin(qMax(val, CAmount(0)), BitcoinUnits::maxMoney()); + val = qBound(m_min_amount, val, m_max_amount); setValue(val); } @@ -125,9 +147,12 @@ public: } private: - int currentUnit; - CAmount singleStep; + int currentUnit{BitcoinUnits::BTC}; + CAmount singleStep{CAmount(100000)}; // satoshis mutable QSize cachedMinimumSizeHint; + bool m_allow_empty{true}; + CAmount m_min_amount{CAmount(0)}; + CAmount m_max_amount{BitcoinUnits::maxMoney()}; /** * Parse a string into a number of base monetary units and @@ -174,11 +199,10 @@ protected: StepEnabled rv = 0; bool valid = false; CAmount val = value(&valid); - if(valid) - { - if(val > 0) + if (valid) { + if (val > m_min_amount) rv |= StepDownEnabled; - if(val < BitcoinUnits::maxMoney()) + if (val < m_max_amount) rv |= StepUpEnabled; } return rv; @@ -275,6 +299,21 @@ void BitcoinAmountField::setValue(const CAmount& value) amount->setValue(value); } +void BitcoinAmountField::SetAllowEmpty(bool allow) +{ + amount->SetAllowEmpty(allow); +} + +void BitcoinAmountField::SetMinValue(const CAmount& value) +{ + amount->SetMinValue(value); +} + +void BitcoinAmountField::SetMaxValue(const CAmount& value) +{ + amount->SetMaxValue(value); +} + void BitcoinAmountField::setReadOnly(bool fReadOnly) { amount->setReadOnly(fReadOnly); diff --git a/src/qt/bitcoinamountfield.h b/src/qt/bitcoinamountfield.h index f93579c492..650481e30d 100644 --- a/src/qt/bitcoinamountfield.h +++ b/src/qt/bitcoinamountfield.h @@ -31,6 +31,15 @@ public: CAmount value(bool *value=0) const; void setValue(const CAmount& value); + /** If allow empty is set to false the field will be set to the minimum allowed value if left empty. **/ + void SetAllowEmpty(bool allow); + + /** Set the minimum value in satoshis **/ + void SetMinValue(const CAmount& value); + + /** Set the maximum value in satoshis **/ + void SetMaxValue(const CAmount& value); + /** Set single step in satoshis **/ void setSingleStep(const CAmount& step); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 072334ebb0..ef82351551 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -92,12 +92,8 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty 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); @@ -131,7 +127,9 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty createToolBars(); // Create system tray icon and notification - createTrayIcon(networkStyle); + if (QSystemTrayIcon::isSystemTrayAvailable()) { + createTrayIcon(networkStyle); + } // Create status bar statusBar(); @@ -211,6 +209,10 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty connect(progressBar, &GUIUtil::ClickableProgressBar::clicked, this, &BitcoinGUI::showModalOverlay); } #endif + +#ifdef Q_OS_MAC + m_app_nap_inhibitor = new CAppNapInhibitor; +#endif } BitcoinGUI::~BitcoinGUI() @@ -223,6 +225,7 @@ BitcoinGUI::~BitcoinGUI() if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu) trayIcon->hide(); #ifdef Q_OS_MAC + delete m_app_nap_inhibitor; delete appMenuBar; MacDockIconHandler::cleanup(); #endif @@ -273,17 +276,17 @@ void BitcoinGUI::createActions() #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, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(overviewAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(overviewAction, &QAction::triggered, this, &BitcoinGUI::gotoOverviewPage); - connect(sendCoinsAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(sendCoinsAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(sendCoinsAction, &QAction::triggered, [this]{ gotoSendCoinsPage(); }); - connect(sendCoinsMenuAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(sendCoinsMenuAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(sendCoinsMenuAction, &QAction::triggered, [this]{ gotoSendCoinsPage(); }); - connect(receiveCoinsAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(receiveCoinsAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(receiveCoinsAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage); - connect(receiveCoinsMenuAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(receiveCoinsMenuAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(receiveCoinsMenuAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage); - connect(historyAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(historyAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(historyAction, &QAction::triggered, this, &BitcoinGUI::gotoHistoryPage); #endif // ENABLE_WALLET @@ -350,7 +353,9 @@ void BitcoinGUI::createActions() connect(encryptWalletAction, &QAction::triggered, walletFrame, &WalletFrame::encryptWallet); connect(backupWalletAction, &QAction::triggered, walletFrame, &WalletFrame::backupWallet); connect(changePassphraseAction, &QAction::triggered, walletFrame, &WalletFrame::changePassphrase); + connect(signMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(signMessageAction, &QAction::triggered, [this]{ gotoSignMessageTab(); }); + connect(verifyMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(verifyMessageAction, &QAction::triggered, [this]{ gotoVerifyMessageTab(); }); connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses); connect(usedReceivingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedReceivingAddresses); @@ -585,6 +590,8 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) void BitcoinGUI::createTrayIcon(const NetworkStyle *networkStyle) { + assert(QSystemTrayIcon::isSystemTrayAvailable()); + #ifndef Q_OS_MAC trayIcon = new QSystemTrayIcon(this); QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " + networkStyle->getTitleAddText(); @@ -599,7 +606,7 @@ void BitcoinGUI::createTrayIcon(const NetworkStyle *networkStyle) void BitcoinGUI::createTrayIconMenu() { #ifndef Q_OS_MAC - // return if trayIcon is unset (only on non-Mac OSes) + // return if trayIcon is unset (only on non-macOSes) if (!trayIcon) return; @@ -608,15 +615,17 @@ void BitcoinGUI::createTrayIconMenu() connect(trayIcon, &QSystemTrayIcon::activated, this, &BitcoinGUI::trayIconActivated); #else - // Note: On Mac, the dock icon is used to provide the tray's functionality. + // Note: On macOS, the Dock icon is used to provide the tray's functionality. MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance(); - dockIconHandler->setMainWindow(static_cast<QMainWindow*>(this)); - trayIconMenu = dockIconHandler->dockMenu(); + connect(dockIconHandler, &MacDockIconHandler::dockIconClicked, this, &BitcoinGUI::macosDockIconActivated); + + trayIconMenu = new QMenu(this); + trayIconMenu->setAsDockMenu(); #endif - // Configuration of the tray icon (or dock icon) icon menu + // Configuration of the tray icon (or Dock icon) menu #ifndef Q_OS_MAC - // Note: On Mac, the dock icon's menu already has show / hide action. + // Note: On macOS, the Dock icon's menu already has Show / Hide action. trayIconMenu->addAction(toggleHideAction); trayIconMenu->addSeparator(); #endif @@ -630,7 +639,7 @@ void BitcoinGUI::createTrayIconMenu() trayIconMenu->addAction(openRPCConsoleAction); } trayIconMenu->addAction(optionsAction); -#ifndef Q_OS_MAC // This is built-in on Mac +#ifndef Q_OS_MAC // This is built-in on macOS trayIconMenu->addSeparator(); trayIconMenu->addAction(quitAction); #endif @@ -645,6 +654,12 @@ void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason) toggleHidden(); } } +#else +void BitcoinGUI::macosDockIconActivated() +{ + show(); + activateWindow(); +} #endif void BitcoinGUI::optionsClicked() @@ -663,10 +678,7 @@ void BitcoinGUI::aboutClicked() void BitcoinGUI::showDebugWindow() { - rpcConsole->showNormal(); - rpcConsole->show(); - rpcConsole->raise(); - rpcConsole->activateWindow(); + GUIUtil::bringToFront(rpcConsole); } void BitcoinGUI::showDebugWindowActivateConsole() @@ -786,6 +798,11 @@ void BitcoinGUI::openOptionsDialogWithTab(OptionsDialog::Tab tab) void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool header) { +// Disabling macOS App Nap on initial sync, disk and reindex operations. +#ifdef Q_OS_MAC + (m_node.isInitialBlockDownload() || m_node.getReindex() || m_node.getImporting()) ? m_app_nap_inhibitor->disableAppNap() : m_app_nap_inhibitor->enableAppNap(); +#endif + if (modalOverlay) { if (header) @@ -1148,24 +1165,11 @@ 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) + if (!isHidden() && !isMinimized() && !GUIUtil::isObscured(this) && fToggleHidden) { hide(); + } else { + GUIUtil::bringToFront(this); + } } void BitcoinGUI::toggleHidden() diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index dcaca10557..e8b857c17c 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -20,6 +20,10 @@ #include <QPoint> #include <QSystemTrayIcon> +#ifdef Q_OS_MAC +#include <qt/macos_appnap.h> +#endif + #include <memory> class ClientModel; @@ -143,6 +147,10 @@ private: HelpMessageDialog* helpMessageDialog = nullptr; ModalOverlay* modalOverlay = nullptr; +#ifdef Q_OS_MAC + CAppNapInhibitor* m_app_nap_inhibitor = nullptr; +#endif + /** Keep track of previous number of blocks, to detect progress */ int prevBlocks = 0; int spinnerFrame = 0; @@ -260,6 +268,9 @@ public Q_SLOTS: #ifndef Q_OS_MAC /** Handle tray icon clicked */ void trayIconActivated(QSystemTrayIcon::ActivationReason reason); +#else + /** Handle macOS Dock icon clicked */ + void macosDockIconActivated(); #endif /** Show window if hidden, unminimize when minimized, rise when obscured or show if hidden and fToggleHidden is true */ diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index 6b31ddea90..386d559281 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -878,28 +878,15 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satoshis p <item> <layout class="QHBoxLayout" name="horizontalLayoutFee8"> <item> - <widget class="QCheckBox" name="checkBoxMinimumFee"> - <property name="toolTip"> - <string>Paying only the minimum fee is just fine as long as there is less transaction volume than space in the blocks. But be aware that this can end up in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</string> - </property> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="labelMinFeeWarning"> + <widget class="QLabel" name="labelCustomFeeWarning"> <property name="enabled"> <bool>true</bool> </property> <property name="toolTip"> - <string>Paying only the minimum fee is just fine as long as there is less transaction volume than space in the blocks. But be aware that this can end up in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</string> + <string>When there is less transaction volume than space in the blocks, miners as well as relaying nodes may enforce a minimum fee. Paying only this minimum fee is just fine, but be aware that this can result in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</string> </property> <property name="text"> - <string>(read the tooltip)</string> - </property> - <property name="margin"> - <number>5</number> + <string>A too low fee might result in a never confirming transaction (read the tooltip)</string> </property> </widget> </item> @@ -992,9 +979,6 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satoshis p <property name="text"> <string/> </property> - <property name="margin"> - <number>2</number> - </property> </widget> </item> <item> @@ -1009,9 +993,6 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satoshis p <property name="text"> <string>(Smart fee not initialized yet. This usually takes a few blocks...)</string> </property> - <property name="margin"> - <number>2</number> - </property> </widget> </item> <item> @@ -1038,24 +1019,8 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satoshis p <property name="text"> <string>Confirmation time target:</string> </property> - <property name="margin"> - <number>2</number> - </property> </widget> </item> - <item> - <spacer name="verticalSpacer_3"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>1</width> - <height>1</height> - </size> - </property> - </spacer> - </item> </layout> </item> <item> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index a68140ccf9..0e9aca21b1 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -47,6 +47,7 @@ #include <QDoubleValidator> #include <QFileDialog> #include <QFont> +#include <QFontDatabase> #include <QKeyEvent> #include <QLineEdit> #include <QSettings> @@ -55,9 +56,12 @@ #include <QUrlQuery> #include <QMouseEvent> +#if defined(Q_OS_MAC) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#if QT_VERSION >= 0x50200 -#include <QFontDatabase> +#include <objc/objc-runtime.h> +#include <CoreServices/CoreServices.h> #endif namespace GUIUtil { @@ -74,13 +78,7 @@ QString dateTimeStr(qint64 nTime) QFont fixedPitchFont() { -#if QT_VERSION >= 0x50200 return QFontDatabase::systemFont(QFontDatabase::FixedFont); -#else - QFont font("Monospace"); - font.setStyleHint(QFont::Monospace); - return font; -#endif } // Just some dummy data to generate a convincing random-looking (but consistent) address @@ -353,6 +351,27 @@ bool isObscured(QWidget *w) && checkPoint(QPoint(w->width() / 2, w->height() / 2), w)); } +void bringToFront(QWidget* w) +{ +#ifdef Q_OS_MAC + // Force application activation on macOS. With Qt 5.4 this is required when + // an action in the dock menu is triggered. + id app = objc_msgSend((id) objc_getClass("NSApplication"), sel_registerName("sharedApplication")); + objc_msgSend(app, sel_registerName("activateIgnoringOtherApps:"), YES); +#endif + + if (w) { + // activateWindow() (sometimes) helps with keyboard focus on Windows + if (w->isMinimized()) { + w->showNormal(); + } else { + w->show(); + } + w->activateWindow(); + w->raise(); + } +} + void openDebugLogfile() { fs::path pathDebug = GetDataDir() / "debug.log"; @@ -663,13 +682,8 @@ bool SetStartOnSystemStartup(bool fAutoStart) #elif defined(Q_OS_MAC) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // based on: https://github.com/Mozketo/LaunchAtLoginController/blob/master/LaunchAtLoginController.m -#include <CoreFoundation/CoreFoundation.h> -#include <CoreServices/CoreServices.h> - LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl); LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl) { diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 011827e134..f1d0aa48ef 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -115,6 +115,9 @@ namespace GUIUtil // Determine whether a widget is hidden behind other windows bool isObscured(QWidget *w); + // Activate, show and raise the widget + void bringToFront(QWidget* w); + // Open debug.log void openDebugLogfile(); diff --git a/src/qt/macdockiconhandler.h b/src/qt/macdockiconhandler.h index 1c28593d4a..ff867e21a7 100644 --- a/src/qt/macdockiconhandler.h +++ b/src/qt/macdockiconhandler.h @@ -1,44 +1,27 @@ -// Copyright (c) 2011-2015 The Bitcoin Core developers +// Copyright (c) 2011-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_QT_MACDOCKICONHANDLER_H #define BITCOIN_QT_MACDOCKICONHANDLER_H -#include <QMainWindow> #include <QObject> -QT_BEGIN_NAMESPACE -class QIcon; -class QMenu; -class QWidget; -QT_END_NAMESPACE - -/** Macintosh-specific dock icon handler. +/** macOS-specific Dock icon handler. */ class MacDockIconHandler : public QObject { Q_OBJECT public: - ~MacDockIconHandler(); - - QMenu *dockMenu(); - void setIcon(const QIcon &icon); - void setMainWindow(QMainWindow *window); static MacDockIconHandler *instance(); static void cleanup(); - void handleDockIconClickEvent(); Q_SIGNALS: void dockIconClicked(); private: MacDockIconHandler(); - - QWidget *m_dummyWidget; - QMenu *m_dockMenu; - QMainWindow *mainWindow; }; #endif // BITCOIN_QT_MACDOCKICONHANDLER_H diff --git a/src/qt/macdockiconhandler.mm b/src/qt/macdockiconhandler.mm index b9ad191da7..102adce6c5 100644 --- a/src/qt/macdockiconhandler.mm +++ b/src/qt/macdockiconhandler.mm @@ -1,107 +1,36 @@ -// Copyright (c) 2011-2013 The Bitcoin Core developers +// Copyright (c) 2011-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 "macdockiconhandler.h" -#include <QImageWriter> -#include <QMenu> -#include <QBuffer> -#include <QWidget> - #undef slots -#include <Cocoa/Cocoa.h> #include <objc/objc.h> #include <objc/message.h> static MacDockIconHandler *s_instance = nullptr; -bool dockClickHandler(id self,SEL _cmd,...) { +bool dockClickHandler(id self, SEL _cmd, ...) { Q_UNUSED(self) Q_UNUSED(_cmd) - s_instance->handleDockIconClickEvent(); + Q_EMIT s_instance->dockIconClicked(); - // Return NO (false) to suppress the default OS X actions + // Return NO (false) to suppress the default macOS actions return false; } void setupDockClickHandler() { - Class cls = objc_getClass("NSApplication"); - id appInst = objc_msgSend((id)cls, sel_registerName("sharedApplication")); - - if (appInst != nullptr) { - id delegate = objc_msgSend(appInst, sel_registerName("delegate")); - Class delClass = (Class)objc_msgSend(delegate, sel_registerName("class")); - SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"); - if (class_getInstanceMethod(delClass, shouldHandle)) - class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:"); - else - class_addMethod(delClass, shouldHandle, (IMP)dockClickHandler,"B@:"); - } + id app = objc_msgSend((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); + id delegate = objc_msgSend(app, sel_registerName("delegate")); + Class delClass = (Class)objc_msgSend(delegate, sel_registerName("class")); + SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"); + class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:"); } - MacDockIconHandler::MacDockIconHandler() : QObject() { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - setupDockClickHandler(); - this->m_dummyWidget = new QWidget(); - this->m_dockMenu = new QMenu(this->m_dummyWidget); - this->setMainWindow(nullptr); -#if QT_VERSION >= 0x050200 - this->m_dockMenu->setAsDockMenu(); -#endif - [pool release]; -} - -void MacDockIconHandler::setMainWindow(QMainWindow *window) { - this->mainWindow = window; -} - -MacDockIconHandler::~MacDockIconHandler() -{ - delete this->m_dummyWidget; - this->setMainWindow(nullptr); -} - -QMenu *MacDockIconHandler::dockMenu() -{ - return this->m_dockMenu; -} - -void MacDockIconHandler::setIcon(const QIcon &icon) -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSImage *image = nil; - if (icon.isNull()) - image = [[NSImage imageNamed:@"NSApplicationIcon"] retain]; - else { - // generate NSImage from QIcon and use this as dock icon. - QSize size = icon.actualSize(QSize(128, 128)); - QPixmap pixmap = icon.pixmap(size); - - // Write image into a R/W buffer from raw pixmap, then save the image. - QBuffer notificationBuffer; - if (!pixmap.isNull() && notificationBuffer.open(QIODevice::ReadWrite)) { - QImageWriter writer(¬ificationBuffer, "PNG"); - if (writer.write(pixmap.toImage())) { - NSData* macImgData = [NSData dataWithBytes:notificationBuffer.buffer().data() - length:notificationBuffer.buffer().size()]; - image = [[NSImage alloc] initWithData:macImgData]; - } - } - - if(!image) { - // if testnet image could not be created, load std. app icon - image = [[NSImage imageNamed:@"NSApplicationIcon"] retain]; - } - } - - [NSApp setApplicationIconImage:image]; - [image release]; - [pool release]; } MacDockIconHandler *MacDockIconHandler::instance() @@ -115,14 +44,3 @@ void MacDockIconHandler::cleanup() { delete s_instance; } - -void MacDockIconHandler::handleDockIconClickEvent() -{ - if (this->mainWindow) - { - this->mainWindow->activateWindow(); - this->mainWindow->show(); - } - - Q_EMIT this->dockIconClicked(); -} diff --git a/src/qt/macos_appnap.h b/src/qt/macos_appnap.h new file mode 100644 index 0000000000..8c2cd840b0 --- /dev/null +++ b/src/qt/macos_appnap.h @@ -0,0 +1,24 @@ +// Copyright (c) 2011-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_QT_MACOS_APPNAP_H +#define BITCOIN_QT_MACOS_APPNAP_H + +#include <memory> + +class CAppNapInhibitor final +{ +public: + explicit CAppNapInhibitor(); + ~CAppNapInhibitor(); + + void disableAppNap(); + void enableAppNap(); + +private: + class CAppNapImpl; + std::unique_ptr<CAppNapImpl> impl; +}; + +#endif // BITCOIN_QT_MACOS_APPNAP_H diff --git a/src/qt/macos_appnap.mm b/src/qt/macos_appnap.mm new file mode 100644 index 0000000000..22a88782ab --- /dev/null +++ b/src/qt/macos_appnap.mm @@ -0,0 +1,71 @@ +// Copyright (c) 2011-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 "macos_appnap.h" + +#include <AvailabilityMacros.h> +#include <Foundation/NSProcessInfo.h> +#include <Foundation/Foundation.h> + +class CAppNapInhibitor::CAppNapImpl +{ +public: + ~CAppNapImpl() + { + if(activityId) + enableAppNap(); + } + + void disableAppNap() + { + if (!activityId) + { + @autoreleasepool { + const NSActivityOptions activityOptions = + NSActivityUserInitiatedAllowingIdleSystemSleep & + ~(NSActivitySuddenTerminationDisabled | + NSActivityAutomaticTerminationDisabled); + + id processInfo = [NSProcessInfo processInfo]; + if ([processInfo respondsToSelector:@selector(beginActivityWithOptions:reason:)]) + { + activityId = [processInfo beginActivityWithOptions: activityOptions reason:@"Temporarily disable App Nap for bitcoin-qt."]; + [activityId retain]; + } + } + } + } + + void enableAppNap() + { + if(activityId) + { + @autoreleasepool { + id processInfo = [NSProcessInfo processInfo]; + if ([processInfo respondsToSelector:@selector(endActivity:)]) + [processInfo endActivity:activityId]; + + [activityId release]; + activityId = nil; + } + } + } + +private: + NSObject* activityId; +}; + +CAppNapInhibitor::CAppNapInhibitor() : impl(new CAppNapImpl()) {} + +CAppNapInhibitor::~CAppNapInhibitor() = default; + +void CAppNapInhibitor::disableAppNap() +{ + impl->disableAppNap(); +} + +void CAppNapInhibitor::enableAppNap() +{ + impl->enableAppNap(); +} diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index b51322394f..c9871f6c66 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -23,6 +23,7 @@ #include <QIntValidator> #include <QLocale> #include <QMessageBox> +#include <QSystemTrayIcon> #include <QTimer> OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : @@ -126,6 +127,13 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : connect(ui->proxyIpTor, &QValidatedLineEdit::validationDidChange, this, &OptionsDialog::updateProxyValidationState); connect(ui->proxyPort, &QLineEdit::textChanged, this, &OptionsDialog::updateProxyValidationState); connect(ui->proxyPortTor, &QLineEdit::textChanged, this, &OptionsDialog::updateProxyValidationState); + + if (!QSystemTrayIcon::isSystemTrayAvailable()) { + ui->hideTrayIcon->setChecked(true); + ui->hideTrayIcon->setEnabled(false); + ui->minimizeToTray->setChecked(false); + ui->minimizeToTray->setEnabled(false); + } } OptionsDialog::~OptionsDialog() @@ -211,8 +219,10 @@ void OptionsDialog::setMapper() /* Window */ #ifndef Q_OS_MAC - mapper->addMapping(ui->hideTrayIcon, OptionsModel::HideTrayIcon); - mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray); + if (QSystemTrayIcon::isSystemTrayAvailable()) { + mapper->addMapping(ui->hideTrayIcon, OptionsModel::HideTrayIcon); + mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray); + } mapper->addMapping(ui->minimizeOnClose, OptionsModel::MinimizeOnClose); #endif diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 858128f9f9..65db0280b7 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -119,13 +119,11 @@ SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *p settings.setValue("nSmartFeeSliderPosition", 0); if (!settings.contains("nTransactionFee")) settings.setValue("nTransactionFee", (qint64)DEFAULT_PAY_TX_FEE); - if (!settings.contains("fPayOnlyMinFee")) - settings.setValue("fPayOnlyMinFee", false); ui->groupFee->setId(ui->radioSmartFee, 0); ui->groupFee->setId(ui->radioCustomFee, 1); ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true); + ui->customFee->SetAllowEmpty(false); ui->customFee->setValue(settings.value("nTransactionFee").toLongLong()); - ui->checkBoxMinimumFee->setChecked(settings.value("fPayOnlyMinFee").toBool()); minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool()); } @@ -174,14 +172,15 @@ void SendCoinsDialog::setModel(WalletModel *_model) connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls); connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels); connect(ui->customFee, &BitcoinAmountField::valueChanged, this, &SendCoinsDialog::coinControlUpdateLabels); - connect(ui->checkBoxMinimumFee, &QCheckBox::stateChanged, this, &SendCoinsDialog::setMinimumFee); - connect(ui->checkBoxMinimumFee, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateFeeSectionControls); - connect(ui->checkBoxMinimumFee, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels); connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateSmartFeeLabel); connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels); - ui->customFee->setSingleStep(model->wallet().getRequiredFee(1000)); + CAmount requiredFee = model->wallet().getRequiredFee(1000); + ui->customFee->SetMinValue(requiredFee); + if (ui->customFee->value() < requiredFee) { + ui->customFee->setValue(requiredFee); + } + ui->customFee->setSingleStep(requiredFee); updateFeeSectionControls(); - updateMinFeeLabel(); updateSmartFeeLabel(); // set default rbf checkbox state @@ -210,7 +209,6 @@ SendCoinsDialog::~SendCoinsDialog() settings.setValue("nFeeRadio", ui->groupFee->checkedId()); settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex())); settings.setValue("nTransactionFee", (qint64)ui->customFee->value()); - settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked()); delete ui; } @@ -542,7 +540,6 @@ void SendCoinsDialog::updateDisplayUnit() { setBalance(model->wallet().getBalances()); ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); - updateMinFeeLabel(); updateSmartFeeLabel(); } @@ -642,11 +639,6 @@ void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry) } } -void SendCoinsDialog::setMinimumFee() -{ - ui->customFee->setValue(model->wallet().getRequiredFee(1000)); -} - void SendCoinsDialog::updateFeeSectionControls() { ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked()); @@ -654,10 +646,9 @@ void SendCoinsDialog::updateFeeSectionControls() ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked()); ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked()); ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked()); - ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked()); - ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked()); - ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); - ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); + ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked()); + ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked()); + ui->customFee ->setEnabled(ui->radioCustomFee->isChecked()); } void SendCoinsDialog::updateFeeMinimizedLabel() @@ -672,14 +663,6 @@ void SendCoinsDialog::updateFeeMinimizedLabel() } } -void SendCoinsDialog::updateMinFeeLabel() -{ - if (model && model->getOptionsModel()) - ui->checkBoxMinimumFee->setText(tr("Pay only the required fee of %1").arg( - BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getRequiredFee(1000)) + "/kB") - ); -} - void SendCoinsDialog::updateCoinControlState(CCoinControl& ctrl) { if (ui->radioCustomFee->isChecked()) { diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 7009855f17..e1ebc77d59 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -92,9 +92,7 @@ private Q_SLOTS: void coinControlClipboardBytes(); void coinControlClipboardLowOutput(); void coinControlClipboardChange(); - void setMinimumFee(); void updateFeeSectionControls(); - void updateMinFeeLabel(); void updateSmartFeeLabel(); Q_SIGNALS: diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index b6235e91f7..df38285d08 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -37,9 +37,7 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw float fontFactor = 1.0; float devicePixelRatio = 1.0; -#if QT_VERSION > 0x050100 devicePixelRatio = static_cast<QGuiApplication*>(QCoreApplication::instance())->devicePixelRatio(); -#endif // define text to place QString titleText = tr(PACKAGE_NAME); @@ -53,10 +51,8 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw QSize splashSize(480*devicePixelRatio,320*devicePixelRatio); pixmap = QPixmap(splashSize); -#if QT_VERSION > 0x050100 // change to HiDPI if it makes sense pixmap.setDevicePixelRatio(devicePixelRatio); -#endif QPainter pixPaint(&pixmap); pixPaint.setPen(QColor(100,100,100)); diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 0cabb5f0be..3e414df1f0 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -2,6 +2,7 @@ #include <qt/test/util.h> #include <test/test_bitcoin.h> +#include <interfaces/chain.h> #include <interfaces/node.h> #include <qt/addressbookpage.h> #include <qt/addresstablemodel.h> @@ -56,7 +57,8 @@ void EditAddressAndSubmit( void TestAddAddressesToSendBook() { TestChain100Setup test; - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateMock()); + auto chain = interfaces::MakeChain(); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); diff --git a/src/qt/test/util.h b/src/qt/test/util.h index 5363c94547..377f07dcba 100644 --- a/src/qt/test/util.h +++ b/src/qt/test/util.h @@ -1,6 +1,8 @@ #ifndef BITCOIN_QT_TEST_UTIL_H #define BITCOIN_QT_TEST_UTIL_H +#include <QString> + /** * Press "Ok" button in message box dialog. * diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 12dbf922f1..f02fd8aea7 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -1,6 +1,7 @@ #include <qt/test/wallettests.h> #include <qt/test/util.h> +#include <interfaces/chain.h> #include <interfaces/node.h> #include <base58.h> #include <qt/bitcoinamountfield.h> @@ -132,7 +133,8 @@ void TestGUI() for (int i = 0; i < 5; ++i) { test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey())); } - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateMock()); + auto chain = interfaces::MakeChain(); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); { @@ -141,7 +143,7 @@ void TestGUI() wallet->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); } { - LOCK(cs_main); + auto locked_chain = wallet->chain().lock(); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true); diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 053e951921..a619992344 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -292,9 +292,7 @@ void WalletView::usedSendingAddresses() if(!walletModel) return; - usedSendingAddressesPage->show(); - usedSendingAddressesPage->raise(); - usedSendingAddressesPage->activateWindow(); + GUIUtil::bringToFront(usedSendingAddressesPage); } void WalletView::usedReceivingAddresses() @@ -302,9 +300,7 @@ void WalletView::usedReceivingAddresses() if(!walletModel) return; - usedReceivingAddressesPage->show(); - usedReceivingAddressesPage->raise(); - usedReceivingAddressesPage->activateWindow(); + GUIUtil::bringToFront(usedReceivingAddressesPage); } void WalletView::showProgress(const QString &title, int nProgress) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 92f6f0fe11..95915b5488 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -12,8 +12,8 @@ #include <checkpoints.h> #include <coins.h> #include <consensus/validation.h> -#include <validation.h> #include <core_io.h> +#include <hash.h> #include <index/txindex.h> #include <key_io.h> #include <policy/feerate.h> @@ -21,14 +21,15 @@ #include <policy/rbf.h> #include <primitives/transaction.h> #include <rpc/server.h> +#include <rpc/util.h> #include <script/descriptor.h> #include <streams.h> #include <sync.h> #include <txdb.h> #include <txmempool.h> -#include <util/system.h> #include <util/strencodings.h> -#include <hash.h> +#include <util/system.h> +#include <validation.h> #include <validationinterface.h> #include <versionbitsinfo.h> #include <warnings.h> @@ -286,12 +287,12 @@ static UniValue waitforblockheight(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "waitforblockheight <height> (timeout)\n" + "waitforblockheight height ( timeout )\n" "\nWaits for (at least) block height and returns the height and hash\n" "of the current tip.\n" "\nReturns the current block on timeout or exit.\n" "\nArguments:\n" - "1. height (required, int) Block height to wait for (int)\n" + "1. height (int, required) Block height to wait for.\n" "2. timeout (int, optional, default=0) Time in milliseconds to wait for a response. 0 indicates no timeout.\n" "\nResult:\n" "{ (json object)\n" @@ -695,11 +696,11 @@ static UniValue getblockheader(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "getblockheader \"hash\" ( verbose )\n" + "getblockheader \"blockhash\" ( verbose )\n" "\nIf verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'.\n" "If verbose is true, returns an Object with information about blockheader <hash>.\n" "\nArguments:\n" - "1. \"hash\" (string, required) The block hash\n" + "1. \"blockhash\" (string, required) The block hash\n" "2. verbose (boolean, optional, default=true) true for a json object, false for the hex-encoded data\n" "\nResult (for verbose = true):\n" "{\n" @@ -925,7 +926,7 @@ static UniValue pruneblockchain(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "pruneblockchain\n" + "pruneblockchain height\n" "\nArguments:\n" "1. \"height\" (numeric, required) The block height to prune up to. May be set to a discrete height, or a unix timestamp\n" " to prune blocks whose block time is at least 2 hours older than the provided timestamp.\n" @@ -1562,7 +1563,7 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 2) throw std::runtime_error( - "getchaintxstats ( nblocks blockhash )\n" + "getchaintxstats ( nblocks \"blockhash\" )\n" "\nCompute statistics about the total number and rate of transactions in the chain.\n" "\nArguments:\n" "1. nblocks (numeric, optional) Size of the window in number of blocks (default: one month).\n" @@ -2039,7 +2040,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "scantxoutset <action> ( <scanobjects> )\n" + "scantxoutset \"action\" [scanobjects,...]\n" "\nEXPERIMENTAL warning: this call may be removed or changed in future releases.\n" "\nScans the unspent transaction output set for entries that match certain output descriptors.\n" "Examples of output descriptors are:\n" diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 58b54c66de..4f314ef215 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -18,10 +18,11 @@ #include <rpc/blockchain.h> #include <rpc/mining.h> #include <rpc/server.h> +#include <rpc/util.h> #include <shutdown.h> #include <txmempool.h> -#include <util/system.h> #include <util/strencodings.h> +#include <util/system.h> #include <validation.h> #include <validationinterface.h> #include <versionbitsinfo.h> @@ -231,7 +232,7 @@ static UniValue prioritisetransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 3) throw std::runtime_error( - "prioritisetransaction <txid> <dummy value> <fee delta>\n" + "prioritisetransaction \"txid\" dummy fee_delta\n" "Accepts the transaction into mined blocks at a higher (or lower) priority\n" "\nArguments:\n" "1. \"txid\" (string, required) The transaction id.\n" @@ -294,7 +295,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) throw std::runtime_error( - "getblocktemplate ( TemplateRequest )\n" + "getblocktemplate ( \"template_request\" )\n" "\nIf the request parameters include a 'mode' key, that is used to explicitly select between the default 'template' request or a 'proposal'.\n" "It returns data needed to construct a block to work on.\n" "For full specification, see BIPs 22, 23, 9, and 145:\n" diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 4d893bb838..fc1498a224 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -7,17 +7,18 @@ #include <chainparams.h> #include <clientversion.h> #include <core_io.h> -#include <validation.h> #include <net.h> #include <net_processing.h> #include <netbase.h> #include <policy/policy.h> #include <rpc/protocol.h> +#include <rpc/util.h> #include <sync.h> #include <timedata.h> #include <ui_interface.h> -#include <util/system.h> #include <util/strencodings.h> +#include <util/system.h> +#include <validation.h> #include <version.h> #include <warnings.h> @@ -200,7 +201,7 @@ static UniValue addnode(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 2 || (strCommand != "onetry" && strCommand != "add" && strCommand != "remove")) throw std::runtime_error( - "addnode \"node\" \"add|remove|onetry\"\n" + "addnode \"node\" \"command\"\n" "\nAttempts to add or remove a node from the addnode list.\n" "Or try a connection to a node once.\n" "Nodes added using addnode (or -connect) are protected from DoS disconnection and are not required to be\n" @@ -243,13 +244,13 @@ static UniValue disconnectnode(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() == 0 || request.params.size() >= 3) throw std::runtime_error( - "disconnectnode \"[address]\" [nodeid]\n" + "disconnectnode ( \"address\" nodeid )\n" "\nImmediately disconnects from the specified peer node.\n" "\nStrictly one out of 'address' and 'nodeid' can be provided to identify the node.\n" "\nTo disconnect by nodeid, either set 'address' to the empty string, or call using the named 'nodeid' argument only.\n" "\nArguments:\n" "1. \"address\" (string, optional) The IP address/port of the node\n" - "2. \"nodeid\" (number, optional) The node ID (see getpeerinfo for node IDs)\n" + "2. nodeid (number, optional) The node ID (see getpeerinfo for node IDs)\n" "\nExamples:\n" + HelpExampleCli("disconnectnode", "\"192.168.0.6:8333\"") + HelpExampleCli("disconnectnode", "\"\" 1") @@ -499,7 +500,7 @@ static UniValue setban(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 2 || (strCommand != "add" && strCommand != "remove")) throw std::runtime_error( - "setban \"subnet\" \"add|remove\" (bantime) (absolute)\n" + "setban \"subnet\" \"command\" ( bantime absolute )\n" "\nAttempts to add or remove an IP/Subnet from the banned list.\n" "\nArguments:\n" "1. \"subnet\" (string, required) The IP/Subnet (see getpeerinfo for nodes IP) with an optional netmask (default is /32 = single IP)\n" @@ -610,7 +611,7 @@ static UniValue setnetworkactive(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( - "setnetworkactive true|false\n" + "setnetworkactive state\n" "\nDisable/enable all p2p network activity.\n" "\nArguments:\n" "1. \"state\" (boolean, required) true to enable networking, false to disable\n" diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 0f87646b08..480cb0b9d2 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -9,10 +9,9 @@ #include <consensus/validation.h> #include <core_io.h> #include <index/txindex.h> -#include <keystore.h> -#include <validation.h> -#include <validationinterface.h> +#include <init.h> #include <key_io.h> +#include <keystore.h> #include <merkleblock.h> #include <net.h> #include <policy/policy.h> @@ -20,6 +19,7 @@ #include <primitives/transaction.h> #include <rpc/rawtransaction.h> #include <rpc/server.h> +#include <rpc/util.h> #include <script/script.h> #include <script/script_error.h> #include <script/sign.h> @@ -27,6 +27,8 @@ #include <txmempool.h> #include <uint256.h> #include <util/strencodings.h> +#include <validation.h> +#include <validationinterface.h> #include <future> #include <stdint.h> @@ -204,7 +206,16 @@ static UniValue gettxoutproof(const JSONRPCRequest& request) { if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2)) throw std::runtime_error( - "gettxoutproof [\"txid\",...] ( blockhash )\n" + RPCHelpMan{"gettxoutproof", + { + {"txids", RPCArg::Type::ARR, + { + {"txid", RPCArg::Type::STR_HEX, false}, + }, + false}, + {"blockhash", RPCArg::Type::STR_HEX, true}, + }} + .ToString() + "\nReturns a hex-encoded proof that \"txid\" was included in a block.\n" "\nNOTE: By default this function only works sometimes. This is when there is an\n" "unspent output in the utxo for this transaction. To make it always work,\n" @@ -346,7 +357,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal if (!locktime.isNull()) { int64_t nLockTime = locktime.get_int64(); - if (nLockTime < 0 || nLockTime > std::numeric_limits<uint32_t>::max()) + if (nLockTime < 0 || nLockTime > LOCKTIME_MAX) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); rawTx.nLockTime = nLockTime; } @@ -368,18 +379,18 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal uint32_t nSequence; if (rbfOptIn) { - nSequence = MAX_BIP125_RBF_SEQUENCE; + nSequence = MAX_BIP125_RBF_SEQUENCE; /* CTxIn::SEQUENCE_FINAL - 2 */ } else if (rawTx.nLockTime) { - nSequence = std::numeric_limits<uint32_t>::max() - 1; + nSequence = CTxIn::SEQUENCE_FINAL - 1; } else { - nSequence = std::numeric_limits<uint32_t>::max(); + nSequence = CTxIn::SEQUENCE_FINAL; } // set the sequence number if passed in the parameters object const UniValue& sequenceObj = find_value(o, "sequence"); if (sequenceObj.isNum()) { int64_t seqNr64 = sequenceObj.get_int64(); - if (seqNr64 < 0 || seqNr64 > std::numeric_limits<uint32_t>::max()) { + if (seqNr64 < 0 || seqNr64 > CTxIn::SEQUENCE_FINAL) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range"); } else { nSequence = (uint32_t)seqNr64; @@ -671,10 +682,17 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std:: static UniValue combinerawtransaction(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "combinerawtransaction [\"hexstring\",...]\n" + RPCHelpMan{"combinerawtransaction", + { + {"txs", RPCArg::Type::ARR, + { + {"hexstring", RPCArg::Type::STR_HEX, false}, + }, + false}, + }} + .ToString() + "\nCombine multiple partially signed transactions into one transaction.\n" "The combined transaction may be another partially signed transaction or a \n" "fully signed transaction." @@ -754,7 +772,7 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request) return EncodeHexTx(mergedTx); } -UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore *keystore, bool is_temp_keystore, const UniValue& hashType) +UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore *keystore, bool is_temp_keystore, const UniValue& hashType) { // Fetch previous transactions (inputs): CCoinsView viewDummy; @@ -897,7 +915,30 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) throw std::runtime_error( - "signrawtransactionwithkey \"hexstring\" [\"privatekey1\",...] ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] sighashtype )\n" + RPCHelpMan{"signrawtransactionwithkey", + { + {"hexstring", RPCArg::Type::STR, false}, + {"privkyes", RPCArg::Type::ARR, + { + {"privatekey", RPCArg::Type::STR_HEX, false}, + }, + false}, + {"prevtxs", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"txid", RPCArg::Type::STR_HEX, false}, + {"vout", RPCArg::Type::NUM, false}, + {"scriptPubKey", RPCArg::Type::STR_HEX, false}, + {"redeemScript", RPCArg::Type::STR_HEX, false}, + {"amount", RPCArg::Type::AMOUNT, false}, + }, + true}, + }, + true}, + {"sighashtype", RPCArg::Type::STR, true}, + }} + .ToString() + "\nSign inputs for raw transaction (serialized, hex-encoded).\n" "The second argument is an array of base58-encoded private\n" "keys that will be the only keys used to sign the transaction.\n" @@ -969,7 +1010,7 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) keystore.AddKey(key); } - return SignTransaction(mtx, request.params[2], &keystore, true, request.params[3]); + return SignTransaction(*g_rpc_interfaces->chain, mtx, request.params[2], &keystore, true, request.params[3]); } UniValue signrawtransaction(const JSONRPCRequest& request) @@ -1452,7 +1493,15 @@ UniValue combinepsbt(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "combinepsbt [\"psbt\",...]\n" + RPCHelpMan{"combinepsbt", + { + {"txs", RPCArg::Type::ARR, + { + {"psbt", RPCArg::Type::STR_HEX, false}, + }, + false}, + }} + .ToString() + "\nCombine multiple partially signed Bitcoin transactions into one transaction.\n" "Implements the Combiner role.\n" "\nArguments:\n" @@ -1536,12 +1585,13 @@ UniValue finalizepsbt(const JSONRPCRequest& request) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); } - // Get all of the previous transactions + // Finalize input signatures -- in case we have partial signatures that add up to a complete + // signature, but have not combined them yet (e.g. because the combiner that created this + // PartiallySignedTransaction did not understand them), this will combine them into a final + // script. bool complete = true; for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - PSBTInput& input = psbtx.inputs.at(i); - - complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, *psbtx.tx, input, i, 1); + complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL); } UniValue result(UniValue::VOBJ); @@ -1554,10 +1604,10 @@ UniValue finalizepsbt(const JSONRPCRequest& request) mtx.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness; } ssTx << mtx; - result.pushKV("hex", HexStr(ssTx.begin(), ssTx.end())); + result.pushKV("hex", HexStr(ssTx.str())); } else { ssTx << psbtx; - result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size())); + result.pushKV("psbt", EncodeBase64(ssTx.str())); } result.pushKV("complete", complete); @@ -1568,7 +1618,37 @@ UniValue createpsbt(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) throw std::runtime_error( - "createpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n" + RPCHelpMan{"createpsbt", + { + {"inputs", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"txid", RPCArg::Type::STR_HEX, false}, + {"vout", RPCArg::Type::NUM, false}, + {"sequence", RPCArg::Type::NUM, true}, + }, + false}, + }, + false}, + {"outputs", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"address", RPCArg::Type::AMOUNT, false}, + }, + true}, + {"", RPCArg::Type::OBJ, + { + {"data", RPCArg::Type::STR_HEX, false}, + }, + true}, + }, + false}, + {"locktime", RPCArg::Type::NUM, true}, + {"replaceable", RPCArg::Type::BOOL, true}, + }} + .ToString() + "\nCreates a transaction in the Partially Signed Transaction format.\n" "Implements the Creator role.\n" "\nArguments:\n" @@ -1669,7 +1749,7 @@ UniValue converttopsbt(const JSONRPCRequest& request) // Remove all scriptSigs and scriptWitnesses from inputs for (CTxIn& input : tx.vin) { - if ((!input.scriptSig.empty() || !input.scriptWitness.IsNull()) && (request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool()))) { + if ((!input.scriptSig.empty() || !input.scriptWitness.IsNull()) && !permitsigdata) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Inputs must not have scriptSigs and scriptWitnesses"); } input.scriptSig.clear(); diff --git a/src/rpc/rawtransaction.h b/src/rpc/rawtransaction.h index 924611ed5a..52d701d1c3 100644 --- a/src/rpc/rawtransaction.h +++ b/src/rpc/rawtransaction.h @@ -9,8 +9,12 @@ class CBasicKeyStore; struct CMutableTransaction; class UniValue; +namespace interfaces { +class Chain; +} // namespace interfaces + /** Sign a transaction with the given keystore and previous transactions */ -UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType); +UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType); /** Create a transaction from univalue parameters */ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf); diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 78abd7b610..f890baba51 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -8,11 +8,12 @@ #include <fs.h> #include <key_io.h> #include <random.h> +#include <rpc/util.h> #include <shutdown.h> #include <sync.h> #include <ui_interface.h> -#include <util/system.h> #include <util/strencodings.h> +#include <util/system.h> #include <boost/bind.hpp> #include <boost/signals2/signal.hpp> @@ -234,7 +235,7 @@ UniValue stop(const JSONRPCRequest& jsonRequest) static UniValue uptime(const JSONRPCRequest& jsonRequest) { - if (jsonRequest.fHelp || jsonRequest.params.size() > 1) + if (jsonRequest.fHelp || jsonRequest.params.size() > 0) throw std::runtime_error( "uptime\n" "\nReturns the total uptime of the server.\n" diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 8a66191a6a..6f2450708a 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -9,6 +9,8 @@ #include <tinyformat.h> #include <util/strencodings.h> +InitInterfaces* g_rpc_interfaces = nullptr; + // Converts a hex string to a public key if possible CPubKey HexToPubKey(const std::string& hex_in) { @@ -126,3 +128,95 @@ UniValue DescribeAddress(const CTxDestination& dest) { return boost::apply_visitor(DescribeAddressVisitor(), dest); } + +std::string RPCHelpMan::ToString() const +{ + std::string ret; + + ret += m_name; + bool is_optional{false}; + for (const auto& arg : m_args) { + ret += " "; + if (arg.m_optional) { + if (!is_optional) ret += "( "; + is_optional = true; + } else { + // Currently we still support unnamed arguments, so any argument following an optional argument must also be optional + // If support for positional arguments is deprecated in the future, remove this line + assert(!is_optional); + } + ret += arg.ToString(); + } + if (is_optional) ret += " )"; + ret += "\n"; + + return ret; +} + +std::string RPCArg::ToStringObj() const +{ + std::string res = "\"" + m_name + "\":"; + switch (m_type) { + case Type::STR: + return res + "\"str\""; + case Type::STR_HEX: + return res + "\"hex\""; + case Type::NUM: + return res + "n"; + case Type::AMOUNT: + return res + "amount"; + case Type::BOOL: + return res + "bool"; + case Type::ARR: + res += "["; + for (const auto& i : m_inner) { + res += i.ToString() + ","; + } + return res + "...]"; + case Type::OBJ: + case Type::OBJ_USER_KEYS: + // Currently unused, so avoid writing dead code + assert(false); + + // no default case, so the compiler can warn about missing cases + } + assert(false); +} + +std::string RPCArg::ToString() const +{ + switch (m_type) { + case Type::STR_HEX: + case Type::STR: { + return "\"" + m_name + "\""; + } + case Type::NUM: + case Type::AMOUNT: + case Type::BOOL: { + return m_name; + } + case Type::OBJ: + case Type::OBJ_USER_KEYS: { + std::string res; + for (size_t i = 0; i < m_inner.size();) { + res += m_inner[i].ToStringObj(); + if (++i < m_inner.size()) res += ","; + } + if (m_type == Type::OBJ) { + return "{" + res + "}"; + } else { + return "{" + res + ",...}"; + } + } + case Type::ARR: { + std::string res; + for (const auto& i : m_inner) { + res += i.ToString() + ","; + } + return "[" + res + "...]"; + } + + // no default case, so the compiler can warn about missing cases + } + assert(false); +} diff --git a/src/rpc/util.h b/src/rpc/util.h index 0a3a156e45..55ae2fa363 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -17,6 +17,12 @@ class CKeyStore; class CPubKey; class CScript; +struct InitInterfaces; + +//! Pointers to interfaces that need to be accessible from RPC methods. Due to +//! limitations of the RPC framework, there's currently no direct way to pass in +//! state to RPC method implementations. +extern InitInterfaces* g_rpc_interfaces; CPubKey HexToPubKey(const std::string& hex_in); CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in); @@ -24,4 +30,53 @@ CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey UniValue DescribeAddress(const CTxDestination& dest); +struct RPCArg { + enum class Type { + OBJ, + ARR, + STR, + NUM, + BOOL, + OBJ_USER_KEYS, //!< Special type where the user must set the keys e.g. to define multiple addresses; as opposed to e.g. an options object where the keys are predefined + AMOUNT, //!< Special type representing a floating point amount (can be either NUM or STR) + STR_HEX, //!< Special type that is a STR with only hex chars + }; + const std::string m_name; //!< The name of the arg (can be empty for inner args) + const Type m_type; + const std::vector<RPCArg> m_inner; //!< Only used for arrays or dicts + const bool m_optional; + + RPCArg(const std::string& name, const Type& type, const bool optional) + : m_name{name}, m_type{type}, m_optional{optional} + { + assert(type != Type::ARR && type != Type::OBJ); + } + + RPCArg(const std::string& name, const Type& type, const std::vector<RPCArg>& inner, const bool optional) + : m_name{name}, m_type{type}, m_inner{inner}, m_optional{optional} + { + assert(type == Type::ARR || type == Type::OBJ); + } + + std::string ToString() const; + +private: + std::string ToStringObj() const; +}; + +class RPCHelpMan +{ +public: + RPCHelpMan(const std::string& name, const std::vector<RPCArg>& args) + : m_name{name}, m_args{args} + { + } + + std::string ToString() const; + +private: + const std::string m_name; + const std::vector<RPCArg> m_args; +}; + #endif // BITCOIN_RPC_UTIL_H diff --git a/src/script/script.h b/src/script/script.h index 00065a24be..1d8ddba2f2 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -38,6 +38,12 @@ static const int MAX_STACK_SIZE = 1000; // otherwise as UNIX timestamp. static const unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC +// Maximum nLockTime. Since a lock time indicates the last invalid timestamp, a +// transaction with this lock time will never be valid unless lock time +// checking is disabled (by setting all input sequence numbers to +// SEQUENCE_FINAL). +static const uint32_t LOCKTIME_MAX = 0xFFFFFFFFU; + template <typename T> std::vector<unsigned char> ToByteVector(const T& in) { diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 89cc7c808c..2795dc96d3 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -123,7 +123,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator case TX_PUBKEYHASH: { CKeyID keyID = CKeyID(uint160(vSolutions[0])); CPubKey pubkey; - GetPubKey(provider, sigdata, keyID, pubkey); + if (!GetPubKey(provider, sigdata, keyID, pubkey)) return false; if (!CreateSig(creator, sigdata, provider, sig, pubkey, scriptPubKey, sigversion)) return false; ret.push_back(std::move(sig)); ret.push_back(ToByteVector(pubkey)); @@ -239,10 +239,17 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato return sigdata.complete; } -bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, int index, int sighash) +bool PSBTInputSigned(PSBTInput& input) { - // if this input has a final scriptsig or scriptwitness, don't do anything with it - if (!input.final_script_sig.empty() || !input.final_script_witness.IsNull()) { + return !input.final_script_sig.empty() || !input.final_script_witness.IsNull(); +} + +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash) +{ + PSBTInput& input = psbt.inputs.at(index); + const CMutableTransaction& tx = *psbt.tx; + + if (PSBTInputSigned(input)) { return true; } @@ -253,15 +260,19 @@ bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& t // Get UTXO bool require_witness_sig = false; CTxOut utxo; + + // Verify input sanity, which checks that at most one of witness or non-witness utxos is provided. + if (!input.IsSane()) { + return false; + } + if (input.non_witness_utxo) { // If we're taking our information from a non-witness UTXO, verify that it matches the prevout. - if (input.non_witness_utxo->GetHash() != tx.vin[index].prevout.hash) return false; - // If both witness and non-witness UTXO are provided, verify that they match. This check shouldn't - // matter, as the PSBT deserializer enforces only one of both is provided, and the only way both - // can be present is when they're added simultaneously by FillPSBT (in which case they always match). - // Still, check in order to not rely on callers to enforce this. - if (!input.witness_utxo.IsNull() && input.non_witness_utxo->vout[tx.vin[index].prevout.n] != input.witness_utxo) return false; - utxo = input.non_witness_utxo->vout[tx.vin[index].prevout.n]; + COutPoint prevout = tx.vin[index].prevout; + if (input.non_witness_utxo->GetHash() != prevout.hash) { + return false; + } + utxo = input.non_witness_utxo->vout[prevout.n]; } else if (!input.witness_utxo.IsNull()) { utxo = input.witness_utxo; // When we're taking our information from a witness UTXO, we can't verify it is actually data from @@ -280,18 +291,10 @@ bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& t if (require_witness_sig && !sigdata.witness) return false; input.FromSignatureData(sigdata); + // If we have a witness signature, use the smaller witness UTXO. if (sigdata.witness) { - assert(!utxo.IsNull()); input.witness_utxo = utxo; - } - - // If both UTXO types are present, drop the unnecessary one. - if (input.non_witness_utxo && !input.witness_utxo.IsNull()) { - if (sigdata.witness) { - input.non_witness_utxo = nullptr; - } else { - input.witness_utxo.SetNull(); - } + input.non_witness_utxo = nullptr; } return sig_complete; @@ -513,6 +516,12 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script) return false; } +PartiallySignedTransaction::PartiallySignedTransaction(const CTransaction& tx) : tx(tx) +{ + inputs.resize(tx.vin.size()); + outputs.resize(tx.vout.size()); +} + bool PartiallySignedTransaction::IsNull() const { return !tx && inputs.empty() && outputs.empty() && unknown.empty(); diff --git a/src/script/sign.h b/src/script/sign.h index d47aada17d..a478f49789 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -206,6 +206,9 @@ template<typename Stream> void SerializeHDKeypaths(Stream& s, const std::map<CPubKey, KeyOriginInfo>& hd_keypaths, uint8_t type) { for (auto keypath_pair : hd_keypaths) { + if (!keypath_pair.first.IsValid()) { + throw std::ios_base::failure("Invalid CPubKey being serialized"); + } SerializeToVector(s, type, MakeSpan(keypath_pair.first)); WriteCompactSize(s, (keypath_pair.second.path.size() + 1) * sizeof(uint32_t)); s << keypath_pair.second.fingerprint; @@ -566,6 +569,7 @@ struct PartiallySignedTransaction bool IsSane() const; PartiallySignedTransaction() {} PartiallySignedTransaction(const PartiallySignedTransaction& psbt_in) : tx(psbt_in.tx), inputs(psbt_in.inputs), outputs(psbt_in.outputs), unknown(psbt_in.unknown) {} + explicit PartiallySignedTransaction(const CTransaction& tx); // Only checks if they refer to the same transaction friend bool operator==(const PartiallySignedTransaction& a, const PartiallySignedTransaction &b) @@ -729,8 +733,11 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType); bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType); +/** Checks whether a PSBTInput is already signed. */ +bool PSBTInputSigned(PSBTInput& input); + /** Signs a PSBTInput, verifying that all provided data matches what is being signed. */ -bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, int index, int sighash = SIGHASH_ALL); +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL); /** Extract signature data from a transaction input, and insert it. */ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout); diff --git a/src/test/hash_tests.cpp b/src/test/hash_tests.cpp index fd3a6b0dec..e8e5040855 100644 --- a/src/test/hash_tests.cpp +++ b/src/test/hash_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <crypto/siphash.h> #include <hash.h> #include <util/strencodings.h> #include <test/test_bitcoin.h> diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index f788c34e05..26cdc9bc5c 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2015-2018 The Bitcoin Core developers -// Distributed under the MIT/X11 software license, see the accompanying +// Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chain.h> diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index a49796d6f4..121b72a5f7 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -4,8 +4,11 @@ #include <rpc/server.h> #include <rpc/client.h> +#include <rpc/util.h> #include <core_io.h> +#include <init.h> +#include <interfaces/chain.h> #include <key_io.h> #include <netbase.h> @@ -112,10 +115,14 @@ BOOST_AUTO_TEST_CASE(rpc_rawsign) std::string notsigned = r.get_str(); std::string privkey1 = "\"KzsXybp9jX64P5ekX1KUxRQ79Jht9uzW7LorgwE65i5rWACL6LQe\""; std::string privkey2 = "\"Kyhdf5LuKTRx4ge69ybABsiUAWjVRK4XGxAKk2FQLp2HjGMy87Z4\""; + InitInterfaces interfaces; + interfaces.chain = interfaces::MakeChain(); + g_rpc_interfaces = &interfaces; r = CallRPC(std::string("signrawtransactionwithkey ")+notsigned+" [] "+prevout); BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == false); r = CallRPC(std::string("signrawtransactionwithkey ")+notsigned+" ["+privkey1+","+privkey2+"] "+prevout); BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == true); + g_rpc_interfaces = nullptr; } BOOST_AUTO_TEST_CASE(rpc_createraw_op_return) diff --git a/src/test/skiplist_tests.cpp b/src/test/skiplist_tests.cpp index 552bd1ab03..5c46976ace 100644 --- a/src/test/skiplist_tests.cpp +++ b/src/test/skiplist_tests.cpp @@ -170,7 +170,6 @@ BOOST_AUTO_TEST_CASE(findearliestatleast_edge_test) BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(-1)->nHeight, 0); BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(std::numeric_limits<int64_t>::min())->nHeight, 0); - BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(std::numeric_limits<unsigned int>::min())->nHeight, 0); BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(-int64_t(std::numeric_limits<unsigned int>::max()) - 1)->nHeight, 0); BOOST_CHECK(!chain.FindEarliestAtLeast(std::numeric_limits<int64_t>::max())); BOOST_CHECK(!chain.FindEarliestAtLeast(std::numeric_limits<unsigned int>::max())); diff --git a/src/threadsafety.h b/src/threadsafety.h index 47e6b2ea38..33acddc65c 100644 --- a/src/threadsafety.h +++ b/src/threadsafety.h @@ -54,4 +54,15 @@ #define ASSERT_EXCLUSIVE_LOCK(...) #endif // __GNUC__ +// Utility class for indicating to compiler thread analysis that a mutex is +// locked (when it couldn't be determined otherwise). +struct SCOPED_LOCKABLE LockAnnotation +{ + template <typename Mutex> + explicit LockAnnotation(Mutex& mutex) EXCLUSIVE_LOCK_FUNCTION(mutex) + { + } + ~LockAnnotation() UNLOCK_FUNCTION() {} +}; + #endif // BITCOIN_THREADSAFETY_H diff --git a/src/txmempool.h b/src/txmempool.h index cda78ea90c..fadb554723 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -15,6 +15,7 @@ #include <amount.h> #include <coins.h> +#include <crypto/siphash.h> #include <indirectmap.h> #include <policy/feerate.h> #include <primitives/transaction.h> diff --git a/src/undo.h b/src/undo.h index 4ed3dc4ca0..3f50f4caad 100644 --- a/src/undo.h +++ b/src/undo.h @@ -11,6 +11,7 @@ #include <consensus/consensus.h> #include <primitives/transaction.h> #include <serialize.h> +#include <version.h> /** Undo information for a CTxIn * diff --git a/src/util/bytevectorhash.cpp b/src/util/bytevectorhash.cpp new file mode 100644 index 0000000000..f87d0e04b3 --- /dev/null +++ b/src/util/bytevectorhash.cpp @@ -0,0 +1,18 @@ +// 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 <crypto/siphash.h> +#include <random.h> +#include <util/bytevectorhash.h> + +ByteVectorHash::ByteVectorHash() +{ + GetRandBytes(reinterpret_cast<unsigned char*>(&m_k0), sizeof(m_k0)); + GetRandBytes(reinterpret_cast<unsigned char*>(&m_k1), sizeof(m_k1)); +} + +size_t ByteVectorHash::operator()(const std::vector<unsigned char>& input) const +{ + return CSipHasher(m_k0, m_k1).Write(input.data(), input.size()).Finalize(); +} diff --git a/src/util/bytevectorhash.h b/src/util/bytevectorhash.h new file mode 100644 index 0000000000..b88c17460b --- /dev/null +++ b/src/util/bytevectorhash.h @@ -0,0 +1,26 @@ +// 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_UTIL_BYTEVECTORHASH_H +#define BITCOIN_UTIL_BYTEVECTORHASH_H + +#include <stdint.h> +#include <vector> + +/** + * Implementation of Hash named requirement for types that internally store a byte array. This may + * be used as the hash function in std::unordered_set or std::unordered_map over such types. + * Internally, this uses a random instance of SipHash-2-4. + */ +class ByteVectorHash final +{ +private: + uint64_t m_k0, m_k1; + +public: + ByteVectorHash(); + size_t operator()(const std::vector<unsigned char>& input) const; +}; + +#endif // BITCOIN_UTIL_BYTEVECTORHASH_H diff --git a/src/util/system.cpp b/src/util/system.cpp index 4f5dd2d6e9..f6f36c2238 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -826,8 +826,10 @@ static bool GetConfigOptions(std::istream& stream, std::string& error, std::vect std::string::size_type pos; int linenr = 1; while (std::getline(stream, str)) { + bool used_hash = false; if ((pos = str.find('#')) != std::string::npos) { str = str.substr(0, pos); + used_hash = true; } const static std::string pattern = " \t\r\n"; str = TrimString(str, pattern); @@ -840,6 +842,10 @@ static bool GetConfigOptions(std::istream& stream, std::string& error, std::vect } else if ((pos = str.find('=')) != std::string::npos) { std::string name = prefix + TrimString(str.substr(0, pos), pattern); std::string value = TrimString(str.substr(pos + 1), pattern); + if (used_hash && name == "rpcpassword") { + error = strprintf("parse error on line %i, using # in rpcpassword can be ambiguous and should be avoided", linenr); + return false; + } options.emplace_back(name, value); } else { error = strprintf("parse error on line %i: %s", linenr, str); diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index de320b4e9e..f5ac6e98b2 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -202,7 +202,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) if (keyPass && keyFail) { LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n"); - assert(false); + throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt."); } if (keyFail || !keyPass) return false; diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 5eb70cd7a5..7a71aea715 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <consensus/validation.h> +#include <interfaces/chain.h> #include <wallet/coincontrol.h> #include <wallet/feebumper.h> #include <wallet/fees.h> @@ -18,7 +19,7 @@ //! Check whether transaction has descendant in wallet or mempool, or has been //! mined, or conflicts with a mined transaction. Return a feebumper::Result. -static feebumper::Result PreconditionChecks(const CWallet* wallet, const CWalletTx& wtx, std::vector<std::string>& errors) EXCLUSIVE_LOCKS_REQUIRED(cs_main, wallet->cs_wallet) +static feebumper::Result PreconditionChecks(interfaces::Chain::Lock& locked_chain, const CWallet* wallet, const CWalletTx& wtx, std::vector<std::string>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) { if (wallet->HasWalletSpend(wtx.GetHash())) { errors.push_back("Transaction has descendants in the wallet"); @@ -34,7 +35,7 @@ static feebumper::Result PreconditionChecks(const CWallet* wallet, const CWallet } } - if (wtx.GetDepthInMainChain() != 0) { + if (wtx.GetDepthInMainChain(locked_chain) != 0) { errors.push_back("Transaction has been mined, or is conflicted with a mined transaction"); return feebumper::Result::WALLET_ERROR; } @@ -64,19 +65,21 @@ namespace feebumper { bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid) { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); const CWalletTx* wtx = wallet->GetWalletTx(txid); if (wtx == nullptr) return false; std::vector<std::string> errors_dummy; - feebumper::Result res = PreconditionChecks(wallet, *wtx, errors_dummy); + feebumper::Result res = PreconditionChecks(*locked_chain, wallet, *wtx, errors_dummy); return res == feebumper::Result::OK; } Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, std::vector<std::string>& errors, CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); errors.clear(); auto it = wallet->mapWallet.find(txid); if (it == wallet->mapWallet.end()) { @@ -85,7 +88,7 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin } const CWalletTx& wtx = it->second; - Result result = PreconditionChecks(wallet, wtx, errors); + Result result = PreconditionChecks(*locked_chain, wallet, wtx, errors); if (result != Result::OK) { return result; } @@ -212,13 +215,15 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin } bool SignTransaction(CWallet* wallet, CMutableTransaction& mtx) { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); return wallet->SignTransaction(mtx); } Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransaction&& mtx, std::vector<std::string>& errors, uint256& bumped_txid) { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); if (!errors.empty()) { return Result::MISC_ERROR; } @@ -230,7 +235,7 @@ Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransacti CWalletTx& oldWtx = it->second; // make sure the transaction still has no descendants and hasn't been mined in the meantime - Result result = PreconditionChecks(wallet, oldWtx, errors); + Result result = PreconditionChecks(*locked_chain, wallet, oldWtx, errors); if (result != Result::OK) { return result; } diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 220780c96c..14d811c6cd 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -5,6 +5,7 @@ #include <chainparams.h> #include <init.h> +#include <interfaces/chain.h> #include <net.h> #include <scheduler.h> #include <outputtype.h> @@ -28,28 +29,8 @@ public: //! Wallets parameter interaction bool ParameterInteraction() const override; - //! Register wallet RPCs. - void RegisterRPC(CRPCTable &tableRPC) const override; - - //! Responsible for reading and validating the -wallet arguments and verifying the wallet database. - // This function will perform salvage on the wallet if requested, as long as only one wallet is - // being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). - bool Verify() const override; - - //! Load wallet databases. - bool Open() const override; - - //! Complete startup of wallets. - void Start(CScheduler& scheduler) const override; - - //! Flush all wallets in preparation for shutdown. - void Flush() const override; - - //! Stop all wallets. Wallets will be flushed first. - void Stop() const override; - - //! Close all wallets. - void Close() const override; + //! Add wallets that should be opened to list of init interfaces. + void Construct(InitInterfaces& interfaces) const override; }; const WalletInitInterface& g_wallet_init_interface = WalletInit(); @@ -57,7 +38,7 @@ const WalletInitInterface& g_wallet_init_interface = WalletInit(); void WalletInit::AddWalletOptions() const { gArgs.AddArg("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")", FormatOutputType(DEFAULT_ADDRESS_TYPE)), false, OptionsCategory::WALLET); - gArgs.AddArg("-avoidpartialspends", strprintf(_("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u)"), DEFAULT_AVOIDPARTIALSPENDS), false, OptionsCategory::WALLET); + gArgs.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u)", DEFAULT_AVOIDPARTIALSPENDS), false, OptionsCategory::WALLET); gArgs.AddArg("-changetype", "What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\"). Default is same as -addresstype, except when -addresstype=p2sh-segwit a native segwit output is used when sending to a native segwit address)", false, OptionsCategory::WALLET); gArgs.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", false, OptionsCategory::WALLET); gArgs.AddArg("-discardfee=<amt>", strprintf("The fee rate (in %s/kB) that indicates your tolerance for discarding change by adding it to the fee (default: %s). " @@ -99,7 +80,6 @@ bool WalletInit::ParameterInteraction() const return true; } - gArgs.SoftSetArg("-wallet", ""); const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1; if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && gArgs.SoftSetBoolArg("-walletbroadcast", false)) { @@ -165,21 +145,8 @@ bool WalletInit::ParameterInteraction() const return true; } -void WalletInit::RegisterRPC(CRPCTable &t) const -{ - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { - return; - } - - RegisterWalletRPCCommands(t); -} - -bool WalletInit::Verify() const +bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) { - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { - return true; - } - if (gArgs.IsArgSet("-walletdir")) { fs::path wallet_dir = gArgs.GetArg("-walletdir", ""); boost::system::error_code error; @@ -200,8 +167,6 @@ bool WalletInit::Verify() const uiInterface.InitMessage(_("Verifying wallet(s)...")); - std::vector<std::string> wallet_files = gArgs.GetArgs("-wallet"); - // Parameter interaction code should have thrown an error if -salvagewallet // was enabled with more than wallet file, so the wallet_files size check // here should have no effect. @@ -219,7 +184,7 @@ bool WalletInit::Verify() const std::string error_string; std::string warning_string; - bool verify_success = CWallet::Verify(location, salvage_wallet, error_string, warning_string); + bool verify_success = CWallet::Verify(chain, location, salvage_wallet, error_string, warning_string); if (!error_string.empty()) InitError(error_string); if (!warning_string.empty()) InitWarning(warning_string); if (!verify_success) return false; @@ -228,15 +193,20 @@ bool WalletInit::Verify() const return true; } -bool WalletInit::Open() const +void WalletInit::Construct(InitInterfaces& interfaces) const { if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { LogPrintf("Wallet disabled!\n"); - return true; + return; } + gArgs.SoftSetArg("-wallet", ""); + interfaces.chain_clients.emplace_back(interfaces::MakeWalletClient(*interfaces.chain, gArgs.GetArgs("-wallet"))); +} - for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { - std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(WalletLocation(walletFile)); +bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) +{ + for (const std::string& walletFile : wallet_files) { + std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(chain, WalletLocation(walletFile)); if (!pwallet) { return false; } @@ -246,7 +216,7 @@ bool WalletInit::Open() const return true; } -void WalletInit::Start(CScheduler& scheduler) const +void StartWallets(CScheduler& scheduler) { for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { pwallet->postInitProcess(); @@ -256,21 +226,21 @@ void WalletInit::Start(CScheduler& scheduler) const scheduler.scheduleEvery(MaybeCompactWalletDB, 500); } -void WalletInit::Flush() const +void FlushWallets() { for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { pwallet->Flush(false); } } -void WalletInit::Stop() const +void StopWallets() { for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { pwallet->Flush(true); } } -void WalletInit::Close() const +void UnloadWallets() { for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { RemoveWallet(pwallet); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 2a4cf0147e..6269bc9d3c 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -3,17 +3,19 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chain.h> +#include <core_io.h> +#include <interfaces/chain.h> #include <key_io.h> +#include <merkleblock.h> #include <rpc/server.h> -#include <validation.h> +#include <rpc/util.h> #include <script/script.h> #include <script/standard.h> #include <sync.h> #include <util/system.h> #include <util/time.h> +#include <validation.h> #include <wallet/wallet.h> -#include <merkleblock.h> -#include <core_io.h> #include <wallet/rpcwallet.h> @@ -112,7 +114,7 @@ UniValue importprivkey(const JSONRPCRequest& request) "Hint: use importmulti to import more than one private key.\n" "\nArguments:\n" "1. \"privkey\" (string, required) The private key (see dumpprivkey)\n" - "2. \"label\" (string, optional, default=\"\") An optional label\n" + "2. \"label\" (string, optional, default=current label if address exists, otherwise \"\") An optional label\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" @@ -133,7 +135,8 @@ UniValue importprivkey(const JSONRPCRequest& request) WalletRescanReserver reserver(pwallet); bool fRescan = true; { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -161,9 +164,14 @@ UniValue importprivkey(const JSONRPCRequest& request) CKeyID vchAddress = pubkey.GetID(); { pwallet->MarkDirty(); - // We don't know which corresponding address will be used; label them all + + // We don't know which corresponding address will be used; + // label all new addresses, and label existing addresses if a + // label was passed. for (const auto& dest : GetAllDestinationsForKey(pubkey)) { - pwallet->SetAddressBook(dest, strLabel, "receive"); + if (!request.params[1].isNull() || pwallet->mapAddressBook.count(dest) == 0) { + pwallet->SetAddressBook(dest, strLabel, "receive"); + } } // Don't throw error in case a key is already there @@ -305,7 +313,8 @@ UniValue importaddress(const JSONRPCRequest& request) fP2SH = request.params[3].get_bool(); { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (IsValidDestination(dest)) { @@ -339,7 +348,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 2) throw std::runtime_error( - "importprunedfunds\n" + "importprunedfunds \"rawtransaction\" \"txoutproof\"\n" "\nImports funds without rescan. Corresponding address or script must previously be included in wallet. Aimed towards pruned wallets. The end-user is responsible to import additional transactions that subsequently spend the imported outputs or rescan after the point in the blockchain the transaction is included.\n" "\nArguments:\n" "1. \"rawtransaction\" (string, required) A raw transaction in hex funding an already-existing address in wallet\n" @@ -362,7 +371,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request) unsigned int txnIndex = 0; if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) { - LOCK(cs_main); + auto locked_chain = pwallet->chain().lock(); const CBlockIndex* pindex = LookupBlockIndex(merkleBlock.header.GetHash()); if (!pindex || !chainActive.Contains(pindex)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); @@ -382,7 +391,8 @@ UniValue importprunedfunds(const JSONRPCRequest& request) wtx.nIndex = txnIndex; wtx.hashBlock = merkleBlock.header.GetHash(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (pwallet->IsMine(*wtx.tx)) { pwallet->AddToWallet(wtx, false); @@ -412,7 +422,8 @@ UniValue removeprunedfunds(const JSONRPCRequest& request) + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); uint256 hash(ParseHashV(request.params[0], "txid")); std::vector<uint256> vHash; @@ -483,7 +494,8 @@ UniValue importpubkey(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); for (const auto& dest : GetAllDestinationsForKey(pubKey)) { ImportAddress(pwallet, dest, strLabel); @@ -535,7 +547,8 @@ UniValue importwallet(const JSONRPCRequest& request) int64_t nTimeBegin = 0; bool fGood = true; { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -653,7 +666,8 @@ UniValue dumpprivkey(const JSONRPCRequest& request) + HelpExampleRpc("dumpprivkey", "\"myaddress\"") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -700,7 +714,8 @@ UniValue dumpwallet(const JSONRPCRequest& request) + HelpExampleRpc("dumpwallet", "\"test\"") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -723,7 +738,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) std::map<CTxDestination, int64_t> mapKeyBirth; const std::map<CKeyID, int64_t>& mapKeyPool = pwallet->GetAllReserveKeys(); - pwallet->GetKeyBirthTimes(mapKeyBirth); + pwallet->GetKeyBirthTimes(*locked_chain, mapKeyBirth); std::set<CScriptID> scripts = pwallet->GetCScripts(); // TODO: include scripts in GetKeyBirthTimes() output instead of separate @@ -994,8 +1009,9 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con throw JSONRPCError(RPC_WALLET_ERROR, "Error adding scriptPubKey script to wallet"); } - // add to address book or update label - if (IsValidDestination(scriptpubkey_dest)) { + // if not internal add to address book or update label + if (!internal) { + assert(IsValidDestination(scriptpubkey_dest)); pwallet->SetAddressBook(scriptpubkey_dest, label, "receive"); } @@ -1070,8 +1086,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) // clang-format off if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) throw std::runtime_error( - "importmulti \"requests\" ( \"options\" )\n\n" - "Import addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options). Requires a new wallet backup.\n\n" + "importmulti \"requests\" ( \"options\" )\n" + "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options). Requires a new wallet backup.\n\n" "Arguments:\n" "1. requests (array, required) Data to be imported\n" " [ (array of json objects)\n" @@ -1087,7 +1103,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) " \"witnessscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey\n" " \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n" " \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n" - " \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be treated as not incoming payments\n" + " \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be treated as not incoming payments aka change\n" " \"watchonly\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be considered watched even when they're not spendable, only allowed if keys are empty\n" " \"label\": <label> , (string, optional, default: '') Label to assign to the address, only allowed with internal=false\n" " }\n" @@ -1134,7 +1150,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) int64_t nLowestTimestamp = 0; UniValue response(UniValue::VARR); { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); // Verify all timestamps are present before importing any keys. diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 45d4b5bceb..0e2f8864b1 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -8,6 +8,8 @@ #include <consensus/validation.h> #include <core_io.h> #include <httpserver.h> +#include <init.h> +#include <interfaces/chain.h> #include <validation.h> #include <key_io.h> #include <net.h> @@ -89,9 +91,9 @@ void EnsureWalletIsUnlocked(CWallet * const pwallet) } } -static void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx, UniValue& entry) { - int confirms = wtx.GetDepthInMainChain(); + int confirms = wtx.GetDepthInMainChain(locked_chain); entry.pushKV("confirmations", confirms); if (wtx.IsCoinBase()) entry.pushKV("generated", true); @@ -101,7 +103,7 @@ static void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) EXCLUSIVE_LOCK entry.pushKV("blockindex", wtx.nIndex); entry.pushKV("blocktime", LookupBlockIndex(wtx.hashBlock)->GetBlockTime()); } else { - entry.pushKV("trusted", wtx.IsTrusted()); + entry.pushKV("trusted", wtx.IsTrusted(locked_chain)); } uint256 hash = wtx.GetHash(); entry.pushKV("txid", hash.GetHex()); @@ -290,7 +292,7 @@ static UniValue setlabel(const JSONRPCRequest& request) } -static CTransactionRef SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue) +static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue) { CAmount curBalance = pwallet->GetBalance(); @@ -317,7 +319,7 @@ static CTransactionRef SendMoney(CWallet * const pwallet, const CTxDestination & CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; vecSend.push_back(recipient); CTransactionRef tx; - if (!pwallet->CreateTransaction(vecSend, tx, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) { + if (!pwallet->CreateTransaction(locked_chain, vecSend, tx, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) { if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance) strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired)); throw JSONRPCError(RPC_WALLET_ERROR, strError); @@ -373,7 +375,8 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (!IsValidDestination(dest)) { @@ -415,7 +418,7 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) EnsureWalletIsUnlocked(pwallet); - CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue)); + CTransactionRef tx = SendMoney(*locked_chain, pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue)); return tx->GetHash().GetHex(); } @@ -455,10 +458,11 @@ static UniValue listaddressgroupings(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); UniValue jsonGroupings(UniValue::VARR); - std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances(); + std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances(*locked_chain); for (const std::set<CTxDestination>& grouping : pwallet->GetAddressGroupings()) { UniValue jsonGrouping(UniValue::VARR); for (const CTxDestination& address : grouping) @@ -508,7 +512,8 @@ static UniValue signmessage(const JSONRPCRequest& request) + HelpExampleRpc("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -574,7 +579,9 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); // Bitcoin address CTxDestination dest = DecodeDestination(request.params[0].get_str()); @@ -600,7 +607,7 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) for (const CTxOut& txout : wtx.tx->vout) if (txout.scriptPubKey == scriptPubKey) - if (wtx.GetDepthInMainChain() >= nMinDepth) + if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) nAmount += txout.nValue; } @@ -641,7 +648,9 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); // Minimum confirmations int nMinDepth = 1; @@ -663,7 +672,7 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) { CTxDestination address; if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwallet, address) && setAddress.count(address)) { - if (wtx.GetDepthInMainChain() >= nMinDepth) + if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) nAmount += txout.nValue; } } @@ -684,7 +693,7 @@ static UniValue getbalance(const JSONRPCRequest& request) if (request.fHelp || (request.params.size() > 3 )) throw std::runtime_error( - "getbalance ( \"(dummy)\" minconf include_watchonly )\n" + "getbalance ( \"dummy\" minconf include_watchonly )\n" "\nReturns the total available balance.\n" "The available balance is what the wallet considers currently spendable, and is\n" "thus affected by options which limit spendability such as -spendzeroconfchange.\n" @@ -707,7 +716,8 @@ static UniValue getbalance(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); const UniValue& dummy_value = request.params[0]; if (!dummy_value.isNull() && dummy_value.get_str() != "*") { @@ -745,7 +755,8 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); return ValueFromAmount(pwallet->GetUnconfirmedBalance()); } @@ -806,7 +817,8 @@ static UniValue sendmany(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (pwallet->GetBroadcastTransactions() && !g_connman) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -892,7 +904,7 @@ static UniValue sendmany(const JSONRPCRequest& request) int nChangePosRet = -1; std::string strFailReason; CTransactionRef tx; - bool fCreated = pwallet->CreateTransaction(vecSend, tx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control); + bool fCreated = pwallet->CreateTransaction(*locked_chain, vecSend, tx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control); if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); CValidationState state; @@ -945,7 +957,8 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) throw std::runtime_error(msg); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); std::string label; if (!request.params[2].isNull()) @@ -996,8 +1009,10 @@ struct tallyitem } }; -static UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pwallet->cs_wallet) +static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, CWallet * const pwallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + // Minimum confirmations int nMinDepth = 1; if (!params[0].isNull()) @@ -1031,7 +1046,7 @@ static UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bo if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) continue; - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = wtx.GetDepthInMainChain(locked_chain); if (nDepth < nMinDepth) continue; @@ -1185,9 +1200,10 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); - return ListReceived(pwallet, request.params, false); + return ListReceived(*locked_chain, pwallet, request.params, false); } static UniValue listreceivedbylabel(const JSONRPCRequest& request) @@ -1229,9 +1245,10 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); - return ListReceived(pwallet, request.params, true); + return ListReceived(*locked_chain, pwallet, request.params, true); } static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) @@ -1244,25 +1261,26 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) /** * List transactions based on the given criteria. * - * @param pwallet The wallet. - * @param wtx The wallet transaction. - * @param nMinDepth The minimum confirmation depth. - * @param fLong Whether to include the JSON version of the transaction. - * @param ret The UniValue into which the result is stored. - * @param filter The "is mine" filter bool. + * @param pwallet The wallet. + * @param wtx The wallet transaction. + * @param nMinDepth The minimum confirmation depth. + * @param fLong Whether to include the JSON version of the transaction. + * @param ret The UniValue into which the result is stored. + * @param filter_ismine The "is mine" filter flags. + * @param filter_label Optional label string to filter incoming transactions. */ -static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) { CAmount nFee; std::list<COutputEntry> listReceived; std::list<COutputEntry> listSent; - wtx.GetAmounts(listReceived, listSent, nFee, filter); + wtx.GetAmounts(listReceived, listSent, nFee, filter_ismine); bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY); // Sent - if ((!listSent.empty() || nFee != 0)) + if (!filter_label) { for (const COutputEntry& s : listSent) { @@ -1279,14 +1297,14 @@ static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int n entry.pushKV("vout", s.vout); entry.pushKV("fee", ValueFromAmount(-nFee)); if (fLong) - WalletTxToJSON(wtx, entry); + WalletTxToJSON(pwallet->chain(), locked_chain, wtx, entry); entry.pushKV("abandoned", wtx.isAbandoned()); ret.push_back(entry); } } // Received - if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) + if (listReceived.size() > 0 && wtx.GetDepthInMainChain(locked_chain) >= nMinDepth) { for (const COutputEntry& r : listReceived) { @@ -1294,6 +1312,9 @@ static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int n if (pwallet->mapAddressBook.count(r.destination)) { label = pwallet->mapAddressBook[r.destination].name; } + if (filter_label && label != *filter_label) { + continue; + } UniValue entry(UniValue::VOBJ); if (involvesWatchonly || (::IsMine(*pwallet, r.destination) & ISMINE_WATCH_ONLY)) { entry.pushKV("involvesWatchonly", true); @@ -1301,9 +1322,9 @@ static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int n MaybePushAddress(entry, r.destination); if (wtx.IsCoinBase()) { - if (wtx.GetDepthInMainChain() < 1) + if (wtx.GetDepthInMainChain(locked_chain) < 1) entry.pushKV("category", "orphan"); - else if (wtx.IsImmatureCoinBase()) + else if (wtx.IsImmatureCoinBase(locked_chain)) entry.pushKV("category", "immature"); else entry.pushKV("category", "generate"); @@ -1318,7 +1339,7 @@ static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int n } entry.pushKV("vout", r.vout); if (fLong) - WalletTxToJSON(wtx, entry); + WalletTxToJSON(pwallet->chain(), locked_chain, wtx, entry); ret.push_back(entry); } } @@ -1335,10 +1356,12 @@ UniValue listtransactions(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 4) throw std::runtime_error( - "listtransactions (dummy count skip include_watchonly)\n" + "listtransactions ( \"label\" count skip include_watchonly )\n" + "\nIf a label name is provided, this will return only incoming transactions paying to addresses with the specified label.\n" "\nReturns up to 'count' most recent transactions skipping the first 'from' transactions.\n" "\nArguments:\n" - "1. \"dummy\" (string, optional) If set, should be \"*\" for backwards compatibility.\n" + "1. \"label\" (string, optional) If set, should be a valid label name to return only incoming transactions\n" + " with the specified label, or \"*\" to disable filtering and return all transactions.\n" "2. count (numeric, optional, default=10) The number of transactions to return\n" "3. skip (numeric, optional, default=0) The number of transactions to skip\n" "4. include_watchonly (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')\n" @@ -1383,8 +1406,12 @@ UniValue listtransactions(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); + const std::string* filter_label = nullptr; if (!request.params[0].isNull() && request.params[0].get_str() != "*") { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"*\""); + filter_label = &request.params[0].get_str(); + if (filter_label->empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\"."); + } } int nCount = 10; if (!request.params[1].isNull()) @@ -1405,7 +1432,8 @@ UniValue listtransactions(const JSONRPCRequest& request) UniValue ret(UniValue::VARR); { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); const CWallet::TxItems & txOrdered = pwallet->wtxOrdered; @@ -1413,7 +1441,7 @@ UniValue listtransactions(const JSONRPCRequest& request) for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { CWalletTx *const pwtx = (*it).second; - ListTransactions(pwallet, *pwtx, 0, true, ret, filter); + ListTransactions(*locked_chain, pwallet, *pwtx, 0, true, ret, filter, filter_label); if ((int)ret.size() >= (nCount+nFrom)) break; } } @@ -1505,7 +1533,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); const CBlockIndex* pindex = nullptr; // Block index of the specified block or the common ancestor, if the block provided was in a deactivated chain. const CBlockIndex* paltindex = nullptr; // Block index of the specified block, even if it's in a deactivated chain. @@ -1548,8 +1577,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request) for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { CWalletTx tx = pairWtx.second; - if (depth == -1 || tx.GetDepthInMainChain() < depth) { - ListTransactions(pwallet, tx, 0, true, transactions, filter); + if (depth == -1 || tx.GetDepthInMainChain(*locked_chain) < depth) { + ListTransactions(*locked_chain, pwallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); } } @@ -1566,7 +1595,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request) if (it != pwallet->mapWallet.end()) { // We want all transactions regardless of confirmation count to appear here, // even negative confirmation ones, hence the big negative. - ListTransactions(pwallet, it->second, -100000000, true, removed, filter); + ListTransactions(*locked_chain, pwallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */); } } paltindex = paltindex->pprev; @@ -1640,7 +1669,8 @@ static UniValue gettransaction(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); uint256 hash(ParseHashV(request.params[0], "txid")); @@ -1656,7 +1686,7 @@ static UniValue gettransaction(const JSONRPCRequest& request) } const CWalletTx& wtx = it->second; - CAmount nCredit = wtx.GetCredit(filter); + CAmount nCredit = wtx.GetCredit(*locked_chain, filter); CAmount nDebit = wtx.GetDebit(filter); CAmount nNet = nCredit - nDebit; CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0); @@ -1665,10 +1695,10 @@ static UniValue gettransaction(const JSONRPCRequest& request) if (wtx.IsFromMe(filter)) entry.pushKV("fee", ValueFromAmount(nFee)); - WalletTxToJSON(wtx, entry); + WalletTxToJSON(pwallet->chain(), *locked_chain, wtx, entry); UniValue details(UniValue::VARR); - ListTransactions(pwallet, wtx, 0, false, details, filter); + ListTransactions(*locked_chain, pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); entry.pushKV("details", details); std::string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags()); @@ -1707,14 +1737,15 @@ static UniValue abandontransaction(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); uint256 hash(ParseHashV(request.params[0], "txid")); if (!pwallet->mapWallet.count(hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); } - if (!pwallet->AbandonTransaction(hash)) { + if (!pwallet->AbandonTransaction(*locked_chain, hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); } @@ -1746,7 +1777,8 @@ static UniValue backupwallet(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); std::string strDest = request.params[0].get_str(); if (!pwallet->BackupWallet(strDest)) { @@ -1782,7 +1814,8 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool unsigned int kpSize = 0; @@ -1833,7 +1866,8 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called."); @@ -1911,7 +1945,8 @@ static UniValue walletpassphrasechange(const JSONRPCRequest& request) ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); @@ -1967,7 +2002,8 @@ static UniValue walletlock(const JSONRPCRequest& request) ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called."); @@ -2013,7 +2049,8 @@ static UniValue encryptwallet(const JSONRPCRequest& request) ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called."); @@ -2048,7 +2085,21 @@ static UniValue lockunspent(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "lockunspent unlock ([{\"txid\":\"txid\",\"vout\":n},...])\n" + RPCHelpMan{"lockunspent", + { + {"unlock", RPCArg::Type::BOOL, false}, + {"transactions", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"txid", RPCArg::Type::STR_HEX, false}, + {"vout", RPCArg::Type::NUM, false}, + }, + true}, + }, + true}, + }} + .ToString() + "\nUpdates list of temporarily unspendable outputs.\n" "Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.\n" "If no transaction outputs are specified when unlocking then all current locked transaction outputs are unlocked.\n" @@ -2087,7 +2138,8 @@ static UniValue lockunspent(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); RPCTypeCheckArgument(request.params[0], UniValue::VBOOL); @@ -2136,7 +2188,7 @@ static UniValue lockunspent(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds"); } - if (pwallet->IsSpent(outpt.hash, outpt.n)) { + if (pwallet->IsSpent(*locked_chain, outpt.hash, outpt.n)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output"); } @@ -2197,7 +2249,8 @@ static UniValue listlockunspent(const JSONRPCRequest& request) + HelpExampleRpc("listlockunspent", "") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); std::vector<COutPoint> vOutpts; pwallet->ListLockedCoins(vOutpts); @@ -2238,7 +2291,8 @@ static UniValue settxfee(const JSONRPCRequest& request) ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); CAmount nAmount = AmountFromValue(request.params[0]); CFeeRate tx_fee_rate(nAmount, 1000); @@ -2293,7 +2347,8 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); UniValue obj(UniValue::VOBJ); @@ -2420,11 +2475,11 @@ static UniValue loadwallet(const JSONRPCRequest& request) } std::string warning; - if (!CWallet::Verify(location, false, error, warning)) { + if (!CWallet::Verify(*g_rpc_interfaces->chain, location, false, error, warning)) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); } - std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(location); + std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location); if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed."); } @@ -2472,11 +2527,11 @@ static UniValue createwallet(const JSONRPCRequest& request) } // Wallet::Verify will check if we're trying to create a wallet with a duplication name. - if (!CWallet::Verify(location, false, error, warning)) { + if (!CWallet::Verify(*g_rpc_interfaces->chain, location, false, error, warning)) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); } - std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(location, (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0)); + std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location, (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0)); if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); } @@ -2562,13 +2617,14 @@ static UniValue resendwallettransactions(const JSONRPCRequest& request) if (!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (!pwallet->GetBroadcastTransactions()) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet transaction broadcasting is disabled with -walletbroadcast"); } - std::vector<uint256> txids = pwallet->ResendWalletTransactionsBefore(GetTime(), g_connman.get()); + std::vector<uint256> txids = pwallet->ResendWalletTransactionsBefore(*locked_chain, GetTime(), g_connman.get()); UniValue result(UniValue::VARR); for (const uint256& txid : txids) { @@ -2588,7 +2644,26 @@ static UniValue listunspent(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 5) throw std::runtime_error( - "listunspent ( minconf maxconf [\"addresses\",...] [include_unsafe] [query_options])\n" + RPCHelpMan{"listunspent", + { + {"minconf", RPCArg::Type::NUM, true}, + {"maxconf", RPCArg::Type::NUM, true}, + {"addresses", RPCArg::Type::ARR, + { + {"address", RPCArg::Type::STR_HEX, true}, + }, + true}, + {"include_unsafe", RPCArg::Type::BOOL, true}, + {"query_options", RPCArg::Type::OBJ, + { + {"minimumAmount", RPCArg::Type::AMOUNT, true}, + {"maximumAmount", RPCArg::Type::AMOUNT, true}, + {"maximumCount", RPCArg::Type::NUM, true}, + {"minimumSumAmount", RPCArg::Type::AMOUNT, true}, + }, + true}, + }} + .ToString() + "\nReturns array of unspent transaction outputs\n" "with between minconf and maxconf (inclusive) confirmations.\n" "Optionally filter to only include txouts paid to specified addresses.\n" @@ -2699,8 +2774,9 @@ static UniValue listunspent(const JSONRPCRequest& request) UniValue results(UniValue::VARR); std::vector<COutput> vecOutputs; { - LOCK2(cs_main, pwallet->cs_wallet); - pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); + pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); } LOCK(pwallet->cs_wallet); @@ -2962,7 +3038,25 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( - "signrawtransactionwithwallet \"hexstring\" ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] sighashtype )\n" + RPCHelpMan{"signrawtransactionwithwallet", + { + {"hexstring", RPCArg::Type::STR, false}, + {"prevtxs", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"txid", RPCArg::Type::STR_HEX, false}, + {"vout", RPCArg::Type::NUM, false}, + {"scriptPubKey", RPCArg::Type::STR_HEX, false}, + {"redeemScript", RPCArg::Type::STR_HEX, false}, + {"amount", RPCArg::Type::AMOUNT, false}, + }, + false}, + }, + true}, + {"sighashtype", RPCArg::Type::STR, true}, + }} + .ToString() + "\nSign inputs for raw transaction (serialized, hex-encoded).\n" "The second optional argument (may be null) is an array of previous transaction outputs that\n" "this transaction depends on but may not yet be in the block chain.\n" @@ -3018,10 +3112,11 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) } // Sign the transaction - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); - return SignTransaction(mtx, request.params[1], pwallet, false, request.params[2]); + return SignTransaction(pwallet->chain(), mtx, request.params[1], pwallet, false, request.params[2]); } static UniValue bumpfee(const JSONRPCRequest& request) @@ -3123,7 +3218,8 @@ static UniValue bumpfee(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -3263,7 +3359,7 @@ UniValue rescanblockchain(const JSONRPCRequest& request) CBlockIndex *pindexStop = nullptr; CBlockIndex *pChainTip = nullptr; { - LOCK(cs_main); + auto locked_chain = pwallet->chain().lock(); pindexStart = chainActive.Genesis(); pChainTip = chainActive.Tip(); @@ -3287,7 +3383,7 @@ UniValue rescanblockchain(const JSONRPCRequest& request) // We can't rescan beyond non-pruned blocks, stop and throw an error if (fPruneMode) { - LOCK(cs_main); + auto locked_chain = pwallet->chain().lock(); CBlockIndex *block = pindexStop ? pindexStop : pChainTip; while (block && block->nHeight >= pindexStart->nHeight) { if (!(block->nStatus & BLOCK_HAVE_DATA)) { @@ -3687,7 +3783,8 @@ UniValue sethdseed(const JSONRPCRequest& request) throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download"); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); // Do not do anything to non-HD wallets if (!pwallet->IsHDEnabled()) { @@ -3736,24 +3833,34 @@ void AddKeypathToMap(const CWallet* pwallet, const CKeyID& keyID, std::map<CPubK hd_keypaths.emplace(vchPubKey, std::move(info)); } -bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type, bool sign, bool bip32derivs) +bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs) { LOCK(pwallet->cs_wallet); // Get all of the previous transactions bool complete = true; - for (unsigned int i = 0; i < txConst->vin.size(); ++i) { - const CTxIn& txin = txConst->vin[i]; + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + const CTxIn& txin = psbtx.tx->vin[i]; PSBTInput& input = psbtx.inputs.at(i); - // If we don't know about this input, skip it and let someone else deal with it - const uint256& txhash = txin.prevout.hash; - const auto it = pwallet->mapWallet.find(txhash); - if (it != pwallet->mapWallet.end()) { - const CWalletTx& wtx = it->second; - CTxOut utxo = wtx.tx->vout[txin.prevout.n]; - // Update both UTXOs from the wallet. - input.non_witness_utxo = wtx.tx; - input.witness_utxo = utxo; + if (PSBTInputSigned(input)) { + continue; + } + + // Verify input looks sane. This will check that we have at most one uxto, witness or non-witness. + if (!input.IsSane()) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "PSBT input is not sane."); + } + + // If we have no utxo, grab it from the wallet. + if (!input.non_witness_utxo && input.witness_utxo.IsNull()) { + const uint256& txhash = txin.prevout.hash; + const auto it = pwallet->mapWallet.find(txhash); + if (it != pwallet->mapWallet.end()) { + const CWalletTx& wtx = it->second; + // We only need the non_witness_utxo, which is a superset of the witness_utxo. + // The signing code will switch to the smaller witness_utxo if this is ok. + input.non_witness_utxo = wtx.tx; + } } // Get the Sighash type @@ -3761,12 +3868,12 @@ bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const C throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Specified Sighash and sighash in PSBT do not match."); } - complete &= SignPSBTInput(HidingSigningProvider(pwallet, !sign, !bip32derivs), *psbtx.tx, input, i, sighash_type); + complete &= SignPSBTInput(HidingSigningProvider(pwallet, !sign, !bip32derivs), psbtx, i, sighash_type); } // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change - for (unsigned int i = 0; i < txConst->vout.size(); ++i) { - const CTxOut& out = txConst->vout.at(i); + for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { + const CTxOut& out = psbtx.tx->vout.at(i); PSBTOutput& psbt_out = psbtx.outputs.at(i); // Fill a SignatureData with output info @@ -3831,19 +3938,15 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request) // Get the sighash type int nHashType = ParseSighashString(request.params[2]); - // Use CTransaction for the constant parts of the - // transaction to avoid rehashing. - const CTransaction txConst(*psbtx.tx); - // Fill transaction with our data and also sign bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool(); - bool complete = FillPSBT(pwallet, psbtx, &txConst, nHashType, sign, bip32derivs); + bool complete = FillPSBT(pwallet, psbtx, nHashType, sign, bip32derivs); UniValue result(UniValue::VOBJ); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; - result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size())); + result.pushKV("psbt", EncodeBase64(ssTx.str())); result.pushKV("complete", complete); return result; @@ -3860,7 +3963,55 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) throw std::runtime_error( - "walletcreatefundedpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable ) ( options bip32derivs )\n" + RPCHelpMan{"walletcreatefundedpsbt", + { + {"inputs", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"txid", RPCArg::Type::STR_HEX, false}, + {"vout", RPCArg::Type::NUM, false}, + {"sequence", RPCArg::Type::NUM, false}, + }, + false}, + }, + false}, + {"outputs", RPCArg::Type::ARR, + { + {"", RPCArg::Type::OBJ, + { + {"address", RPCArg::Type::AMOUNT, true}, + }, + true}, + {"", RPCArg::Type::OBJ, + { + {"data", RPCArg::Type::STR_HEX, true}, + }, + true}, + }, + false}, + {"locktime", RPCArg::Type::NUM, true}, + {"options", RPCArg::Type::OBJ, + { + {"changeAddress", RPCArg::Type::STR_HEX, true}, + {"changePosition", RPCArg::Type::NUM, true}, + {"change_type", RPCArg::Type::STR, true}, + {"includeWatching", RPCArg::Type::BOOL, true}, + {"lockUnspents", RPCArg::Type::BOOL, true}, + {"feeRate", RPCArg::Type::NUM, true}, + {"subtractFeeFromOutputs", RPCArg::Type::ARR, + { + {"int", RPCArg::Type::NUM, true}, + }, + true}, + {"replaceable", RPCArg::Type::BOOL, true}, + {"conf_target", RPCArg::Type::NUM, true}, + {"estimate_mode", RPCArg::Type::STR, true}, + }, + true}, + {"bip32derivs", RPCArg::Type::BOOL, true}, + }} + .ToString() + "\nCreates and funds a transaction in the Partially Signed Transaction format. Inputs will be added if supplied inputs are not enough\n" "Implements the Creator and Updater roles.\n" "\nArguments:\n" @@ -3935,29 +4086,18 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]); // Make a blank psbt - PartiallySignedTransaction psbtx; - psbtx.tx = rawTx; - for (unsigned int i = 0; i < rawTx.vin.size(); ++i) { - psbtx.inputs.push_back(PSBTInput()); - } - for (unsigned int i = 0; i < rawTx.vout.size(); ++i) { - psbtx.outputs.push_back(PSBTOutput()); - } - - // Use CTransaction for the constant parts of the - // transaction to avoid rehashing. - const CTransaction txConst(*psbtx.tx); + PartiallySignedTransaction psbtx(rawTx); // Fill transaction with out data but don't sign bool bip32derivs = request.params[4].isNull() ? false : request.params[4].get_bool(); - FillPSBT(pwallet, psbtx, &txConst, 1, false, bip32derivs); + FillPSBT(pwallet, psbtx, 1, false, bip32derivs); // Serialize the PSBT CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; UniValue result(UniValue::VOBJ); - result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size())); + result.pushKV("psbt", EncodeBase64(ssTx.str())); result.pushKV("fee", ValueFromAmount(fee)); result.pushKV("changepos", change_position); return result; @@ -4013,7 +4153,7 @@ static const CRPCCommand commands[] = { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} }, { "wallet", "listreceivedbylabel", &listreceivedbylabel, {"minconf","include_empty","include_watchonly"} }, { "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} }, - { "wallet", "listtransactions", &listtransactions, {"dummy","count","skip","include_watchonly"} }, + { "wallet", "listtransactions", &listtransactions, {"label|dummy","count","skip","include_watchonly"} }, { "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, { "wallet", "listwalletdir", &listwalletdir, {} }, { "wallet", "listwallets", &listwallets, {} }, diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index 9b9a159b86..abd7750874 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -30,5 +30,5 @@ bool EnsureWalletIsAvailable(CWallet *, bool avoidException); UniValue getaddressinfo(const JSONRPCRequest& request); UniValue signrawtransactionwithwallet(const JSONRPCRequest& request); -bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type = 1, bool sign = true, bool bip32derivs = false); +bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false); #endif //BITCOIN_WALLET_RPCWALLET_H diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index a9464870ea..5c65acf601 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -28,7 +28,8 @@ std::vector<std::unique_ptr<CWalletTx>> wtxn; typedef std::set<CInputCoin> CoinSet; static std::vector<COutput> vCoins; -static CWallet testWallet(WalletLocation(), WalletDatabase::CreateDummy()); +static auto testChain = interfaces::MakeChain(); +static CWallet testWallet(*testChain, WalletLocation(), WalletDatabase::CreateDummy()); static CAmount balance = 0; CoinEligibilityFilter filter_standard(1, 6, 0); diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp index 1453029c9c..3b828d57f9 100644 --- a/src/wallet/test/init_test_fixture.cpp +++ b/src/wallet/test/init_test_fixture.cpp @@ -8,6 +8,8 @@ InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainName): BasicTestingSetup(chainName) { + m_chain_client = MakeWalletClient(*m_chain, {}); + std::string sep; sep += fs::path::preferred_separator; diff --git a/src/wallet/test/init_test_fixture.h b/src/wallet/test/init_test_fixture.h index 5684adbece..cd47b31da1 100644 --- a/src/wallet/test/init_test_fixture.h +++ b/src/wallet/test/init_test_fixture.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H #define BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H +#include <interfaces/chain.h> #include <test/test_bitcoin.h> @@ -16,6 +17,8 @@ struct InitWalletDirTestingSetup: public BasicTestingSetup { fs::path m_datadir; fs::path m_cwd; std::map<std::string, fs::path> m_walletdir_path_cases; + std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); + std::unique_ptr<interfaces::ChainClient> m_chain_client; }; #endif // BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H diff --git a/src/wallet/test/init_tests.cpp b/src/wallet/test/init_tests.cpp index 7048547b2b..5852d3ef84 100644 --- a/src/wallet/test/init_tests.cpp +++ b/src/wallet/test/init_tests.cpp @@ -17,7 +17,7 @@ BOOST_FIXTURE_TEST_SUITE(init_tests, InitWalletDirTestingSetup) BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_default) { SetWalletDir(m_walletdir_path_cases["default"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == true); fs::path walletdir = gArgs.GetArg("-walletdir", ""); fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]); @@ -27,7 +27,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_default) BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom) { SetWalletDir(m_walletdir_path_cases["custom"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == true); fs::path walletdir = gArgs.GetArg("-walletdir", ""); fs::path expected_path = fs::canonical(m_walletdir_path_cases["custom"]); @@ -37,28 +37,28 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom) BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_does_not_exist) { SetWalletDir(m_walletdir_path_cases["nonexistent"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == false); } BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_directory) { SetWalletDir(m_walletdir_path_cases["file"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == false); } BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_relative) { SetWalletDir(m_walletdir_path_cases["relative"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == false); } BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing) { SetWalletDir(m_walletdir_path_cases["trailing"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == true); fs::path walletdir = gArgs.GetArg("-walletdir", ""); fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]); @@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing) BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing2) { SetWalletDir(m_walletdir_path_cases["trailing2"]); - bool result = g_wallet_init_interface.Verify(); + bool result = m_chain_client->verify(); BOOST_CHECK(result == true); fs::path walletdir = gArgs.GetArg("-walletdir", ""); fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]); diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index cb1ad25461..9918eeb89f 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -59,12 +59,8 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test) CDataStream ssData(ParseHex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000"), SER_NETWORK, PROTOCOL_VERSION); ssData >> psbtx; - // Use CTransaction for the constant parts of the - // transaction to avoid rehashing. - const CTransaction txConst(*psbtx.tx); - // Fill transaction with our data - FillPSBT(&m_wallet, psbtx, &txConst, 1, false, true); + FillPSBT(&m_wallet, psbtx, SIGHASH_ALL, false, true); // Get the final tx CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index d42209ab15..a5fb1db86c 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -9,7 +9,7 @@ #include <wallet/rpcwallet.h> WalletTestingSetup::WalletTestingSetup(const std::string& chainName): - TestingSetup(chainName), m_wallet(WalletLocation(), WalletDatabase::CreateMock()) + TestingSetup(chainName), m_wallet(*m_chain, WalletLocation(), WalletDatabase::CreateMock()) { bool fFirstRun; m_wallet.LoadWallet(fFirstRun); diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h index ff8ec32b03..e6fe8c9473 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -7,6 +7,8 @@ #include <test/test_bitcoin.h> +#include <interfaces/chain.h> +#include <interfaces/wallet.h> #include <wallet/wallet.h> #include <memory> @@ -17,6 +19,7 @@ struct WalletTestingSetup: public TestingSetup { explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); ~WalletTestingSetup(); + std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); CWallet m_wallet; }; diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 269a916829..c6aac8aad5 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -11,6 +11,7 @@ #include <vector> #include <consensus/validation.h> +#include <interfaces/chain.h> #include <rpc/server.h> #include <test/test_bitcoin.h> #include <validation.h> @@ -34,6 +35,8 @@ static void AddKey(CWallet& wallet, const CKey& key) BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) { + auto chain = interfaces::MakeChain(); + // Cap last block file size, and mine new block in a new block file. CBlockIndex* const nullBlock = nullptr; CBlockIndex* oldTip = chainActive.Tip(); @@ -41,12 +44,12 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex* newTip = chainActive.Tip(); - LOCK(cs_main); + auto locked_chain = chain->lock(); // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { - CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); @@ -61,7 +64,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) // Verify ScanForWalletTransactions only picks transactions in the new block // file. { - CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); @@ -73,7 +76,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) // before the missing block, and success for a key whose creation time is // after. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddWallet(wallet); UniValue keys; keys.setArray(); @@ -115,6 +118,8 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) // than or equal to key birthday. BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) { + auto chain = interfaces::MakeChain(); + // Create two blocks with same timestamp to verify that importwallet rescan // will pick up both blocks, not just the first. const int64_t BLOCK_TIME = chainActive.Tip()->GetBlockTimeMax() + 5; @@ -128,13 +133,13 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) SetMockTime(KEY_TIME); m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); - LOCK(cs_main); + auto locked_chain = chain->lock(); std::string backup_file = (SetDataDir("importwallet_rescan") / "wallet.backup").string(); // Import key into wallet and call dumpwallet to create backup file. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); LOCK(wallet->cs_wallet); wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); @@ -150,7 +155,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME // were scanned, and no prior blocks were scanned. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); JSONRPCRequest request; request.params.setArray(); @@ -180,21 +185,23 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // debit functions. BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { - CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy()); + auto chain = interfaces::MakeChain(); + CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); CWalletTx wtx(&wallet, m_coinbase_txns.back()); - LOCK2(cs_main, wallet.cs_wallet); + auto locked_chain = chain->lock(); + LOCK(wallet.cs_wallet); wtx.hashBlock = chainActive.Tip()->GetBlockHash(); wtx.nIndex = 0; // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 0); + BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 0); // Invalidate the cached value, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 50*COIN); + BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 50*COIN); } static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) @@ -204,7 +211,7 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 SetMockTime(mockTime); CBlockIndex* block = nullptr; if (blockTime > 0) { - LOCK(cs_main); + auto locked_chain = wallet.chain().lock(); auto inserted = mapBlockIndex.emplace(GetRandHash(), new CBlockIndex); assert(inserted.second); const uint256& hash = inserted.first->first; @@ -273,7 +280,7 @@ public: ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - wallet = MakeUnique<CWallet>(WalletLocation(), WalletDatabase::CreateMock()); + wallet = MakeUnique<CWallet>(*m_chain, WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); @@ -295,7 +302,7 @@ public: int changePos = -1; std::string error; CCoinControl dummy; - BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, reservekey, fee, changePos, error, dummy)); + BOOST_CHECK(wallet->CreateTransaction(*m_locked_chain, {recipient}, tx, reservekey, fee, changePos, error, dummy)); CValidationState state; BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservekey, nullptr, state)); CMutableTransaction blocktx; @@ -311,6 +318,8 @@ public: return it->second; } + std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); + std::unique_ptr<interfaces::Chain::Lock> m_locked_chain = m_chain->assumeLocked(); // Temporary. Removed in upcoming lock cleanup std::unique_ptr<CWallet> wallet; }; @@ -323,7 +332,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) std::map<CTxDestination, std::vector<COutput>> list; { LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(); + list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); @@ -339,7 +348,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */}); { LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(); + list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); @@ -349,7 +358,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) { LOCK2(cs_main, wallet->cs_wallet); std::vector<COutput> available; - wallet->AvailableCoins(available); + wallet->AvailableCoins(*m_locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 2U); } for (const auto& group : list) { @@ -361,14 +370,14 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) { LOCK2(cs_main, wallet->cs_wallet); std::vector<COutput> available; - wallet->AvailableCoins(available); + wallet->AvailableCoins(*m_locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 0U); } // Confirm ListCoins still returns same result as before, despite coins // being locked. { LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(); + list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); @@ -377,7 +386,8 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy()); + auto chain = interfaces::MakeChain(); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); BOOST_CHECK(!wallet->TopUpKeyPool(1000)); CPubKey pubkey; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 2ea9f45462..8ea4c5c495 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -11,6 +11,7 @@ #include <consensus/consensus.h> #include <consensus/validation.h> #include <fs.h> +#include <interfaces/chain.h> #include <key.h> #include <key_io.h> #include <keystore.h> @@ -593,7 +594,7 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran * Outpoint is spent if any non-conflicted transaction * spends it: */ -bool CWallet::IsSpent(const uint256& hash, unsigned int n) const +bool CWallet::IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash, unsigned int n) const { const COutPoint outpoint(hash, n); std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range; @@ -604,7 +605,7 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const const uint256& wtxid = it->second; std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); if (mit != mapWallet.end()) { - int depth = mit->second.GetDepthInMainChain(); + int depth = mit->second.GetDepthInMainChain(locked_chain); if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) return true; // Spent } @@ -1005,9 +1006,10 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); const CWalletTx* wtx = GetWalletTx(hashTx); - return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool(); + return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain(*locked_chain) == 0 && !wtx->InMempool(); } void CWallet::MarkInputsDirty(const CTransactionRef& tx) @@ -1020,9 +1022,10 @@ void CWallet::MarkInputsDirty(const CTransactionRef& tx) } } -bool CWallet::AbandonTransaction(const uint256& hashTx) +bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const uint256& hashTx) { - LOCK2(cs_main, cs_wallet); + auto locked_chain_recursive = chain().lock(); // Temporary. Removed in upcoming lock cleanup + LOCK(cs_wallet); WalletBatch batch(*database, "r+"); @@ -1033,7 +1036,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) auto it = mapWallet.find(hashTx); assert(it != mapWallet.end()); CWalletTx& origtx = it->second; - if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) { + if (origtx.GetDepthInMainChain(locked_chain) != 0 || origtx.InMempool()) { return false; } @@ -1046,7 +1049,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx& wtx = it->second; - int currentconfirm = wtx.GetDepthInMainChain(); + int currentconfirm = wtx.GetDepthInMainChain(locked_chain); // If the orig tx was not in block, none of its spends can be assert(currentconfirm <= 0); // if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon} @@ -1077,7 +1080,8 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); int conflictconfirms = 0; CBlockIndex* pindex = LookupBlockIndex(hashBlock); @@ -1106,7 +1110,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx& wtx = it->second; - int currentconfirm = wtx.GetDepthInMainChain(); + int currentconfirm = wtx.GetDepthInMainChain(*locked_chain); if (conflictconfirms < currentconfirm) { // Block is 'more conflicted' than current confirm; update. // Mark transaction as conflicted with this block. @@ -1140,7 +1144,8 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pin } void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); SyncTransaction(ptx); auto it = mapWallet.find(ptx->GetHash()); @@ -1158,7 +1163,8 @@ void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) { } void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); // TODO: Temporarily ensure that mempool removals are notified before // connected transactions. This shouldn't matter, but the abandoned // state of transactions in our wallet is currently cleared when we @@ -1180,7 +1186,8 @@ void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const } void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const CTransactionRef& ptx : pblock->vtx) { SyncTransaction(ptx); @@ -1199,7 +1206,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() { // We could also take cs_wallet here, and call m_last_block_processed // protected by cs_wallet instead of cs_main, but as long as we need // cs_main here anyway, it's easier to just call it cs_main-protected. - LOCK(cs_main); + auto locked_chain = chain().lock(); const CBlockIndex* initialChainTip = chainActive.Tip(); if (m_last_block_processed && m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) { @@ -1600,7 +1607,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r // to be scanned. CBlockIndex* startBlock = nullptr; { - LOCK(cs_main); + auto locked_chain = chain().lock(); startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0); } @@ -1652,7 +1659,7 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock double progress_begin; double progress_end; { - LOCK(cs_main); + auto locked_chain = chain().lock(); progress_begin = GuessVerificationProgress(chainParams.TxData(), pindex); if (pindexStop == nullptr) { tip = chainActive.Tip(); @@ -1674,7 +1681,8 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock CBlock block; if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); if (pindex && !chainActive.Contains(pindex)) { // Abort scan if current block is no longer active, to prevent // marking transactions as coming from the wrong block. @@ -1691,7 +1699,7 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock break; } { - LOCK(cs_main); + auto locked_chain = chain().lock(); pindex = chainActive.Next(pindex); progress_current = GuessVerificationProgress(chainParams.TxData(), pindex); if (pindexStop == nullptr && tip != chainActive.Tip()) { @@ -1716,7 +1724,8 @@ void CWallet::ReacceptWalletTransactions() // If transactions aren't being broadcasted, don't let them into local mempool either if (!fBroadcastTransactions) return; - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); std::map<int64_t, CWalletTx*> mapSorted; // Sort pending wallet transactions based on their initial wallet insertion order @@ -1726,7 +1735,7 @@ void CWallet::ReacceptWalletTransactions() CWalletTx& wtx = item.second; assert(wtx.GetHash() == wtxid); - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = wtx.GetDepthInMainChain(*locked_chain); if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); @@ -1737,18 +1746,18 @@ void CWallet::ReacceptWalletTransactions() for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { CWalletTx& wtx = *(item.second); CValidationState state; - wtx.AcceptToMemoryPool(maxTxFee, state); + wtx.AcceptToMemoryPool(*locked_chain, maxTxFee, state); } } -bool CWalletTx::RelayWalletTransaction(CConnman* connman) +bool CWalletTx::RelayWalletTransaction(interfaces::Chain::Lock& locked_chain, CConnman* connman) { assert(pwallet->GetBroadcastTransactions()); - if (!IsCoinBase() && !isAbandoned() && GetDepthInMainChain() == 0) + if (!IsCoinBase() && !isAbandoned() && GetDepthInMainChain(locked_chain) == 0) { CValidationState state; /* GetDepthInMainChain already catches known conflicts. */ - if (InMempool() || AcceptToMemoryPool(maxTxFee, state)) { + if (InMempool() || AcceptToMemoryPool(locked_chain, maxTxFee, state)) { pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString()); if (connman) { CInv inv(MSG_TX, GetHash()); @@ -1806,10 +1815,10 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const return debit; } -CAmount CWalletTx::GetCredit(const isminefilter& filter) const +CAmount CWalletTx::GetCredit(interfaces::Chain::Lock& locked_chain, const isminefilter& filter) const { // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsImmatureCoinBase()) + if (IsImmatureCoinBase(locked_chain)) return 0; CAmount credit = 0; @@ -1839,9 +1848,9 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const return credit; } -CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const +CAmount CWalletTx::GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache) const { - if (IsImmatureCoinBase() && IsInMainChain()) { + if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { if (fUseCache && fImmatureCreditCached) return nImmatureCreditCached; nImmatureCreditCached = pwallet->GetCredit(*tx, ISMINE_SPENDABLE); @@ -1852,13 +1861,13 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const return 0; } -CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const +CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache, const isminefilter& filter) const { if (pwallet == nullptr) return 0; // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsImmatureCoinBase()) + if (IsImmatureCoinBase(locked_chain)) return 0; CAmount* cache = nullptr; @@ -1880,7 +1889,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter uint256 hashTx = GetHash(); for (unsigned int i = 0; i < tx->vout.size(); i++) { - if (!pwallet->IsSpent(hashTx, i)) + if (!pwallet->IsSpent(locked_chain, hashTx, i)) { const CTxOut &txout = tx->vout[i]; nCredit += pwallet->GetCredit(txout, filter); @@ -1897,9 +1906,9 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter return nCredit; } -CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const +CAmount CWalletTx::GetImmatureWatchOnlyCredit(interfaces::Chain::Lock& locked_chain, const bool fUseCache) const { - if (IsImmatureCoinBase() && IsInMainChain()) { + if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { if (fUseCache && fImmatureWatchCreditCached) return nImmatureWatchCreditCached; nImmatureWatchCreditCached = pwallet->GetCredit(*tx, ISMINE_WATCH_ONLY); @@ -1924,12 +1933,14 @@ bool CWalletTx::InMempool() const return fInMempool; } -bool CWalletTx::IsTrusted() const +bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain) const { + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + // Quick answer in most cases if (!CheckFinalTx(*tx)) return false; - int nDepth = GetDepthInMainChain(); + int nDepth = GetDepthInMainChain(locked_chain); if (nDepth >= 1) return true; if (nDepth < 0) @@ -1964,7 +1975,7 @@ bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const return CTransaction(tx1) == CTransaction(tx2); } -std::vector<uint256> CWallet::ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman) +std::vector<uint256> CWallet::ResendWalletTransactionsBefore(interfaces::Chain::Lock& locked_chain, int64_t nTime, CConnman* connman) { std::vector<uint256> result; @@ -1983,7 +1994,7 @@ std::vector<uint256> CWallet::ResendWalletTransactionsBefore(int64_t nTime, CCon for (const std::pair<const unsigned int, CWalletTx*>& item : mapSorted) { CWalletTx& wtx = *item.second; - if (wtx.RelayWalletTransaction(connman)) + if (wtx.RelayWalletTransaction(locked_chain, connman)) result.push_back(wtx.GetHash()); } return result; @@ -2007,7 +2018,8 @@ void CWallet::ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman // Rebroadcast unconfirmed txes older than 5 minutes before the last // block was found: - std::vector<uint256> relayed = ResendWalletTransactionsBefore(nBestBlockTime-5*60, connman); + auto locked_chain = chain().assumeLocked(); // Temporary. Removed in upcoming lock cleanup + std::vector<uint256> relayed = ResendWalletTransactionsBefore(*locked_chain, nBestBlockTime-5*60, connman); if (!relayed.empty()) WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size()); } @@ -2027,12 +2039,13 @@ CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) con { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - if (pcoin->IsTrusted() && pcoin->GetDepthInMainChain() >= min_depth) { - nTotal += pcoin->GetAvailableCredit(true, filter); + if (pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) >= min_depth) { + nTotal += pcoin->GetAvailableCredit(*locked_chain, true, filter); } } } @@ -2044,12 +2057,13 @@ CAmount CWallet::GetUnconfirmedBalance() const { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) - nTotal += pcoin->GetAvailableCredit(); + if (!pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) == 0 && pcoin->InMempool()) + nTotal += pcoin->GetAvailableCredit(*locked_chain); } } return nTotal; @@ -2059,11 +2073,12 @@ CAmount CWallet::GetImmatureBalance() const { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - nTotal += pcoin->GetImmatureCredit(); + nTotal += pcoin->GetImmatureCredit(*locked_chain); } } return nTotal; @@ -2073,12 +2088,13 @@ CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) - nTotal += pcoin->GetAvailableCredit(true, ISMINE_WATCH_ONLY); + if (!pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) == 0 && pcoin->InMempool()) + nTotal += pcoin->GetAvailableCredit(*locked_chain, true, ISMINE_WATCH_ONLY); } } return nTotal; @@ -2088,11 +2104,12 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - nTotal += pcoin->GetImmatureWatchOnlyCredit(); + nTotal += pcoin->GetImmatureWatchOnlyCredit(*locked_chain); } } return nTotal; @@ -2106,13 +2123,15 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const // trusted. CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth) const { - LOCK2(cs_main, cs_wallet); + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + auto locked_chain = chain().lock(); + LOCK(cs_wallet); CAmount balance = 0; for (const auto& entry : mapWallet) { const CWalletTx& wtx = entry.second; - const int depth = wtx.GetDepthInMainChain(); - if (depth < 0 || !CheckFinalTx(*wtx.tx) || wtx.IsImmatureCoinBase()) { + const int depth = wtx.GetDepthInMainChain(*locked_chain); + if (depth < 0 || !CheckFinalTx(*wtx.tx) || wtx.IsImmatureCoinBase(*locked_chain)) { continue; } @@ -2139,11 +2158,12 @@ CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth) cons CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); CAmount balance = 0; std::vector<COutput> vCoins; - AvailableCoins(vCoins, true, coinControl); + AvailableCoins(*locked_chain, vCoins, true, coinControl); for (const COutput& out : vCoins) { if (out.fSpendable) { balance += out.tx->tx->vout[out.i].nValue; @@ -2152,7 +2172,7 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const return balance; } -void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const +void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const { AssertLockHeld(cs_main); AssertLockHeld(cs_wallet); @@ -2168,10 +2188,10 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const if (!CheckFinalTx(*pcoin->tx)) continue; - if (pcoin->IsImmatureCoinBase()) + if (pcoin->IsImmatureCoinBase(locked_chain)) continue; - int nDepth = pcoin->GetDepthInMainChain(); + int nDepth = pcoin->GetDepthInMainChain(locked_chain); if (nDepth < 0) continue; @@ -2180,7 +2200,7 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const if (nDepth == 0 && !pcoin->InMempool()) continue; - bool safeTx = pcoin->IsTrusted(); + bool safeTx = pcoin->IsTrusted(locked_chain); // We should not consider coins from transactions that are replacing // other transactions. @@ -2230,7 +2250,7 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const if (IsLockedCoin(entry.first, i)) continue; - if (IsSpent(wtxid, i)) + if (IsSpent(locked_chain, wtxid, i)) continue; isminetype mine = IsMine(pcoin->tx->vout[i]); @@ -2261,7 +2281,7 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const } } -std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const +std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Chain::Lock& locked_chain) const { AssertLockHeld(cs_main); AssertLockHeld(cs_wallet); @@ -2269,7 +2289,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const std::map<CTxDestination, std::vector<COutput>> result; std::vector<COutput> availableCoins; - AvailableCoins(availableCoins); + AvailableCoins(locked_chain, availableCoins); for (const COutput& coin : availableCoins) { CTxDestination address; @@ -2284,7 +2304,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const for (const COutPoint& output : lockedCoins) { auto it = mapWallet.find(output.hash); if (it != mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(); + int depth = it->second.GetDepthInMainChain(locked_chain); if (depth >= 0 && output.n < it->second.tx->vout.size() && IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) { CTxDestination address; @@ -2499,11 +2519,12 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC // Acquire the locks to prevent races to the new locked unspents between the // CreateTransaction call and LockCoin calls (when lockUnspents is true). - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); CReserveKey reservekey(this); CTransactionRef tx_new; - if (!CreateTransaction(vecSend, tx_new, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { + if (!CreateTransaction(*locked_chain, vecSend, tx_new, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { return false; } @@ -2562,7 +2583,7 @@ OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vec return m_default_address_type; } -bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, +bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) { CAmount nValue = 0; @@ -2624,10 +2645,11 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac int nBytes; { std::set<CInputCoin> setCoins; - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); { std::vector<COutput> vAvailableCoins; - AvailableCoins(vAvailableCoins, true, &coin_control); + AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control); CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy // Create change script that will be used if we need change @@ -2962,7 +2984,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CReserveKey& reservekey, CConnman* connman, CValidationState& state) { { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); CWalletTx wtxNew(this, std::move(tx)); wtxNew.mapValue = std::move(mapValue); @@ -2995,11 +3018,11 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve if (fBroadcastTransactions) { // Broadcast - if (!wtx.AcceptToMemoryPool(maxTxFee, state)) { + if (!wtx.AcceptToMemoryPool(*locked_chain, maxTxFee, state)) { WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state)); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. } else { - wtx.RelayWalletTransaction(connman); + wtx.RelayWalletTransaction(*locked_chain, connman); } } } @@ -3008,7 +3031,8 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); fFirstRunRet = false; DBErrors nLoadWalletRet = WalletBatch(*database,"cr+").LoadWallet(this); @@ -3392,7 +3416,7 @@ int64_t CWallet::GetOldestKeyPoolTime() return oldestKey; } -std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() +std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain::Lock& locked_chain) { std::map<CTxDestination, CAmount> balances; @@ -3402,13 +3426,13 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() { const CWalletTx *pcoin = &walletEntry.second; - if (!pcoin->IsTrusted()) + if (!pcoin->IsTrusted(locked_chain)) continue; - if (pcoin->IsImmatureCoinBase()) + if (pcoin->IsImmatureCoinBase(locked_chain)) continue; - int nDepth = pcoin->GetDepthInMainChain(); + int nDepth = pcoin->GetDepthInMainChain(locked_chain); if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1)) continue; @@ -3420,7 +3444,7 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() if(!ExtractDestination(pcoin->tx->vout[i].scriptPubKey, addr)) continue; - CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->tx->vout[i].nValue; + CAmount n = IsSpent(locked_chain, walletEntry.first, i) ? 0 : pcoin->tx->vout[i].nValue; if (!balances.count(addr)) balances[addr] = 0; @@ -3645,7 +3669,7 @@ void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const /** @} */ // end of Actions -void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const { +void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<CTxDestination, int64_t> &mapKeyBirth) const { AssertLockHeld(cs_wallet); // mapKeyMetadata mapKeyBirth.clear(); @@ -3825,7 +3849,7 @@ void CWallet::MarkPreSplitKeys() } } -bool CWallet::Verify(const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::string& warning_string) +bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::string& warning_string) { // Do some checking on wallet path. It should be either a: // @@ -3866,7 +3890,7 @@ bool CWallet::Verify(const WalletLocation& location, bool salvage_wallet, std::s if (salvage_wallet) { // Recover readable keypairs: - CWallet dummyWallet(WalletLocation(), WalletDatabase::CreateDummy()); + CWallet dummyWallet(chain, WalletLocation(), WalletDatabase::CreateDummy()); std::string backup_filename; if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) { return false; @@ -3876,7 +3900,7 @@ bool CWallet::Verify(const WalletLocation& location, bool salvage_wallet, std::s return WalletBatch::VerifyDatabaseFile(wallet_path, warning_string, error_string); } -std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const WalletLocation& location, uint64_t wallet_creation_flags) +std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, uint64_t wallet_creation_flags) { const std::string& walletFile = location.GetName(); @@ -3886,7 +3910,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const WalletLocation& loc if (gArgs.GetBoolArg("-zapwallettxes", false)) { uiInterface.InitMessage(_("Zapping all transactions from wallet...")); - std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(location, WalletDatabase::Create(location.GetPath())); + std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(chain, location, WalletDatabase::Create(location.GetPath())); DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); if (nZapWalletRet != DBErrors::LOAD_OK) { InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); @@ -3900,7 +3924,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const WalletLocation& loc bool fFirstRun = true; // TODO: Can't use std::make_shared because we need a custom deleter but // should be possible to use std::allocate_shared. - std::shared_ptr<CWallet> walletInstance(new CWallet(location, WalletDatabase::Create(location.GetPath())), ReleaseWallet); + std::shared_ptr<CWallet> walletInstance(new CWallet(chain, location, WalletDatabase::Create(location.GetPath())), ReleaseWallet); DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DBErrors::LOAD_OK) { @@ -4010,6 +4034,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const WalletLocation& loc return nullptr; } + auto locked_chain = chain.assumeLocked(); // Temporary. Removed in upcoming lock cleanup walletInstance->ChainStateFlushed(chainActive.GetLocator()); } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { // Make it impossible to disable private keys after creation @@ -4097,7 +4122,9 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const WalletLocation& loc // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); - LOCK2(cs_main, walletInstance->cs_wallet); + LockAnnotation lock(::cs_main); // Temporary, for FindForkInGlobalIndex below. Removed in upcoming commit. + auto locked_chain = chain.lock(); + LOCK(walletInstance->cs_wallet); CBlockIndex *pindexRescan = chainActive.Genesis(); if (!gArgs.GetBoolArg("-rescan", false)) @@ -4232,7 +4259,7 @@ void CMerkleTx::SetMerkleBranch(const CBlockIndex* pindex, int posInBlock) nIndex = posInBlock; } -int CMerkleTx::GetDepthInMainChain() const +int CMerkleTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const { if (hashUnset()) return 0; @@ -4247,23 +4274,25 @@ int CMerkleTx::GetDepthInMainChain() const return ((nIndex == -1) ? (-1) : 1) * (chainActive.Height() - pindex->nHeight + 1); } -int CMerkleTx::GetBlocksToMaturity() const +int CMerkleTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const { if (!IsCoinBase()) return 0; - int chain_depth = GetDepthInMainChain(); + int chain_depth = GetDepthInMainChain(locked_chain); assert(chain_depth >= 0); // coinbase tx should not be conflicted return std::max(0, (COINBASE_MATURITY+1) - chain_depth); } -bool CMerkleTx::IsImmatureCoinBase() const +bool CMerkleTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const { // note GetBlocksToMaturity is 0 for non-coinbase tx - return GetBlocksToMaturity() > 0; + return GetBlocksToMaturity(locked_chain) > 0; } -bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state) +bool CWalletTx::AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, const CAmount& nAbsurdFee, CValidationState& state) { + LockAnnotation lock(::cs_main); // Temporary, for AcceptToMemoryPool below. Removed in upcoming commit. + // We must set fInMempool here - while it will be re-set to true by the // entered-mempool callback, if we did not there would be a race where a // user could call sendmoney in a loop and hit spurious out of funds errors diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 74eaa09506..f96798201f 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -7,6 +7,7 @@ #define BITCOIN_WALLET_WALLET_H #include <amount.h> +#include <interfaces/chain.h> #include <outputtype.h> #include <policy/feerate.h> #include <streams.h> @@ -33,6 +34,26 @@ #include <utility> #include <vector> +//! Responsible for reading and validating the -wallet arguments and verifying the wallet database. +// This function will perform salvage on the wallet if requested, as long as only one wallet is +// being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). +bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); + +//! Load wallet databases. +bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); + +//! Complete startup of wallets. +void StartWallets(CScheduler& scheduler); + +//! Flush all wallets in preparation for shutdown. +void FlushWallets(); + +//! Stop all wallets. Wallets will be flushed first. +void StopWallets(); + +//! Close all wallets. +void UnloadWallets(); + bool AddWallet(const std::shared_ptr<CWallet>& wallet); bool RemoveWallet(const std::shared_ptr<CWallet>& wallet); bool HasWallets(); @@ -264,22 +285,22 @@ public: * 0 : in memory pool, waiting to be included in a block * >=1 : this many blocks deep in the main chain */ - int GetDepthInMainChain() const EXCLUSIVE_LOCKS_REQUIRED(cs_main); - bool IsInMainChain() const EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return GetDepthInMainChain() > 0; } + int GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const; + bool IsInMainChain(interfaces::Chain::Lock& locked_chain) const { return GetDepthInMainChain(locked_chain) > 0; } /** * @return number of blocks to maturity for this transaction: * 0 : is not a coinbase transaction, or is a mature coinbase transaction * >0 : is a coinbase transaction which matures in this many blocks */ - int GetBlocksToMaturity() const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + int GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const; bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } void setAbandoned() { hashBlock = ABANDON_HASH; } const uint256& GetHash() const { return tx->GetHash(); } bool IsCoinBase() const { return tx->IsCoinBase(); } - bool IsImmatureCoinBase() const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const; }; //Get the marginal bytes of spending the specified output @@ -458,14 +479,14 @@ public: //! filter decides which addresses will count towards the debit CAmount GetDebit(const isminefilter& filter) const; - CAmount GetCredit(const isminefilter& filter) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); - CAmount GetImmatureCredit(bool fUseCache=true) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + CAmount GetCredit(interfaces::Chain::Lock& locked_chain, const isminefilter& filter) const; + CAmount GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true) const; // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(cs_main, pwallet->cs_wallet)". The // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid // having to resolve the issue of member access into incomplete type CWallet. - CAmount GetAvailableCredit(bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; - CAmount GetImmatureWatchOnlyCredit(const bool fUseCache=true) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + CAmount GetAvailableCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; + CAmount GetImmatureWatchOnlyCredit(interfaces::Chain::Lock& locked_chain, const bool fUseCache=true) const; CAmount GetChange() const; // Get the marginal bytes if spending the specified output from this transaction @@ -486,15 +507,15 @@ public: bool IsEquivalentTo(const CWalletTx& tx) const; bool InMempool() const; - bool IsTrusted() const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool IsTrusted(interfaces::Chain::Lock& locked_chain) const; int64_t GetTxTime() const; // RelayWalletTransaction may only be called if fBroadcastTransactions! - bool RelayWalletTransaction(CConnman* connman) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool RelayWalletTransaction(interfaces::Chain::Lock& locked_chain, CConnman* connman); /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */ - bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, const CAmount& nAbsurdFee, CValidationState& state); // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation @@ -676,6 +697,9 @@ private: */ bool AddWatchOnly(const CScript& dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + /** Interface for accessing chain state. */ + interfaces::Chain& m_chain; + /** Wallet location which includes wallet name (see WalletLocation). */ WalletLocation m_location; @@ -737,7 +761,7 @@ public: unsigned int nMasterKeyMaxID = 0; /** Construct wallet with specified name and database implementation. */ - CWallet(const WalletLocation& location, std::unique_ptr<WalletDatabase> database) : m_location(location), database(std::move(database)) + CWallet(interfaces::Chain& chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database) : m_chain(chain), m_location(location), database(std::move(database)) { } @@ -759,6 +783,9 @@ public: std::set<COutPoint> setLockedCoins GUARDED_BY(cs_wallet); + /** Interface for accessing chain state. */ + interfaces::Chain& chain() const { return m_chain; } + const CWalletTx* GetWalletTx(const uint256& hash) const; //! check whether we are allowed to upgrade (or already support) to the named feature @@ -767,12 +794,12 @@ public: /** * populate vCoins with vector of available COutputs. */ - void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0, const int nMinDepth = 0, const int nMaxDepth = 9999999) const EXCLUSIVE_LOCKS_REQUIRED(cs_main, cs_wallet); + void AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0, const int nMinDepth = 0, const int nMaxDepth = 9999999) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Return list of available coins and locked coins grouped by non-change output address. */ - std::map<CTxDestination, std::vector<COutput>> ListCoins() const EXCLUSIVE_LOCKS_REQUIRED(cs_main, cs_wallet); + std::map<CTxDestination, std::vector<COutput>> ListCoins(interfaces::Chain::Lock& locked_chain) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Find non-change parent output. @@ -788,7 +815,7 @@ public: bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const; - bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_main, cs_wallet); + bool IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const; bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -852,7 +879,7 @@ public: bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); - void GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<CTxDestination, int64_t> &mapKeyBirth) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); unsigned int ComputeTimeSmart(const CWalletTx& wtx) const; /** @@ -874,7 +901,7 @@ public: void ReacceptWalletTransactions(); void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override EXCLUSIVE_LOCKS_REQUIRED(cs_main); // ResendWalletTransactionsBefore may only be called if fBroadcastTransactions! - std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + std::vector<uint256> ResendWalletTransactionsBefore(interfaces::Chain::Lock& locked_chain, int64_t nTime, CConnman* connman); CAmount GetBalance(const isminefilter& filter=ISMINE_SPENDABLE, const int min_depth=0) const; CAmount GetUnconfirmedBalance() const; CAmount GetImmatureBalance() const; @@ -897,7 +924,7 @@ public: * selected by SelectCoins(); Also create the change output, when needed * @note passing nChangePosInOut as -1 will result in setting a random position */ - bool CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, + bool CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign = true); bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CReserveKey& reservekey, CConnman* connman, CValidationState& state); @@ -956,7 +983,7 @@ public: const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; } std::set<std::set<CTxDestination>> GetAddressGroupings() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - std::map<CTxDestination, CAmount> GetAddressBalances() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + std::map<CTxDestination, CAmount> GetAddressBalances(interfaces::Chain::Lock& locked_chain); std::set<CTxDestination> GetLabelAddresses(const std::string& label) const; @@ -1051,16 +1078,16 @@ public: bool TransactionCanBeAbandoned(const uint256& hashTx) const; /* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */ - bool AbandonTransaction(const uint256& hashTx); + bool AbandonTransaction(interfaces::Chain::Lock& locked_chain, const uint256& hashTx); /** Mark a transaction as replaced by another transaction (e.g., BIP 125). */ bool MarkReplaced(const uint256& originalHash, const uint256& newHash); //! Verify wallet naming and perform salvage on the wallet if required - static bool Verify(const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::string& warning_string); + static bool Verify(interfaces::Chain& chain, const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::string& warning_string); /* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */ - static std::shared_ptr<CWallet> CreateWalletFromFile(const WalletLocation& location, uint64_t wallet_creation_flags = 0); + static std::shared_ptr<CWallet> CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, uint64_t wallet_creation_flags = 0); /** * Wallet post-init setup diff --git a/src/walletinitinterface.h b/src/walletinitinterface.h index 6f12551273..22aca65990 100644 --- a/src/walletinitinterface.h +++ b/src/walletinitinterface.h @@ -9,6 +9,7 @@ class CScheduler; class CRPCTable; +struct InitInterfaces; class WalletInitInterface { public: @@ -18,20 +19,8 @@ public: virtual void AddWalletOptions() const = 0; /** Check wallet parameter interaction */ virtual bool ParameterInteraction() const = 0; - /** Register wallet RPC*/ - virtual void RegisterRPC(CRPCTable &) const = 0; - /** Verify wallets */ - virtual bool Verify() const = 0; - /** Open wallets*/ - virtual bool Open() const = 0; - /** Start wallets*/ - virtual void Start(CScheduler& scheduler) const = 0; - /** Flush Wallets*/ - virtual void Flush() const = 0; - /** Stop Wallets*/ - virtual void Stop() const = 0; - /** Close wallets */ - virtual void Close() const = 0; + /** Add wallets that should be opened to list of init interfaces. */ + virtual void Construct(InitInterfaces& interfaces) const = 0; virtual ~WalletInitInterface() {} }; diff --git a/src/zmq/zmqrpc.cpp b/src/zmq/zmqrpc.cpp index 0e0b745375..66b491427d 100644 --- a/src/zmq/zmqrpc.cpp +++ b/src/zmq/zmqrpc.cpp @@ -5,6 +5,7 @@ #include <zmq/zmqrpc.h> #include <rpc/server.h> +#include <rpc/util.h> #include <zmq/zmqabstractnotifier.h> #include <zmq/zmqnotificationinterface.h> |