diff options
Diffstat (limited to 'src')
259 files changed, 7063 insertions, 4574 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 39e8d3d689..8fc7f61d4b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -143,7 +143,6 @@ BITCOIN_CORE_H = \ interfaces/wallet.h \ key.h \ key_io.h \ - keystore.h \ dbwrapper.h \ limitedmap.h \ logging.h \ @@ -151,11 +150,13 @@ BITCOIN_CORE_H = \ merkleblock.h \ miner.h \ net.h \ + net_permissions.h \ net_processing.h \ netaddress.h \ netbase.h \ netmessagemaker.h \ node/coin.h \ + node/coinstats.h \ node/psbt.h \ node/transaction.h \ noui.h \ @@ -175,14 +176,17 @@ BITCOIN_CORE_H = \ rpc/blockchain.h \ rpc/client.h \ rpc/protocol.h \ - rpc/server.h \ rpc/rawtransaction_util.h \ rpc/register.h \ + rpc/request.h \ + rpc/server.h \ rpc/util.h \ scheduler.h \ script/descriptor.h \ + script/keyorigin.h \ script/sigcache.h \ script/sign.h \ + script/signingprovider.h \ script/standard.h \ shutdown.h \ streams.h \ @@ -208,8 +212,10 @@ BITCOIN_CORE_H = \ util/memory.h \ util/moneystr.h \ util/rbf.h \ + util/string.h \ util/threadnames.h \ util/time.h \ + util/translation.h \ util/url.h \ util/validation.h \ validation.h \ @@ -273,6 +279,7 @@ libbitcoin_server_a_SOURCES = \ net.cpp \ net_processing.cpp \ node/coin.cpp \ + node/coinstats.cpp \ node/psbt.cpp \ node/transaction.cpp \ noui.cpp \ @@ -307,7 +314,7 @@ libbitcoin_server_a_SOURCES += dummywallet.cpp endif if ENABLE_ZMQ -libbitcoin_zmq_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(ZMQ_CFLAGS) +libbitcoin_zmq_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(ZMQ_CFLAGS) libbitcoin_zmq_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_zmq_a_SOURCES = \ zmq/zmqabstractnotifier.cpp \ @@ -351,6 +358,8 @@ crypto_libbitcoin_crypto_base_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) crypto_libbitcoin_crypto_base_a_SOURCES = \ crypto/aes.cpp \ crypto/aes.h \ + crypto/chacha_poly_aead.h \ + crypto/chacha_poly_aead.cpp \ crypto/chacha20.h \ crypto/chacha20.cpp \ crypto/common.h \ @@ -446,10 +455,10 @@ libbitcoin_common_a_SOURCES = \ core_write.cpp \ key.cpp \ key_io.cpp \ - keystore.cpp \ merkleblock.cpp \ netaddress.cpp \ netbase.cpp \ + net_permissions.cpp \ outputtype.cpp \ policy/feerate.cpp \ policy/policy.cpp \ @@ -460,6 +469,7 @@ libbitcoin_common_a_SOURCES = \ scheduler.cpp \ script/descriptor.cpp \ script/sign.cpp \ + script/signingprovider.cpp \ script/standard.cpp \ versionbitsinfo.cpp \ warnings.cpp \ @@ -481,7 +491,7 @@ libbitcoin_util_a_SOURCES = \ interfaces/handler.cpp \ logging.cpp \ random.cpp \ - rpc/protocol.cpp \ + rpc/request.cpp \ support/cleanse.cpp \ sync.cpp \ threadinterrupt.cpp \ @@ -494,6 +504,7 @@ libbitcoin_util_a_SOURCES = \ util/rbf.cpp \ util/threadnames.cpp \ util/strencodings.cpp \ + util/string.cpp \ util/time.cpp \ util/url.cpp \ util/validation.cpp \ @@ -613,7 +624,7 @@ bitcoin_wallet_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(CRYPTO_LIBS) $(EVENT_PTHREAD # bitcoinconsensus library # if BUILD_BITCOIN_LIBS include_HEADERS = script/bitcoinconsensus.h -libbitcoinconsensus_la_SOURCES = $(crypto_libbitcoin_crypto_base_a_SOURCES) $(libbitcoin_consensus_a_SOURCES) +libbitcoinconsensus_la_SOURCES = support/cleanse.cpp $(crypto_libbitcoin_crypto_base_a_SOURCES) $(libbitcoin_consensus_a_SOURCES) if GLIBC_BACK_COMPAT libbitcoinconsensus_la_SOURCES += compat/glibc_compat.cpp diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index c6162b5caa..e421b377a0 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -18,15 +18,19 @@ bench_bench_bitcoin_SOURCES = \ bench/block_assemble.cpp \ bench/checkblock.cpp \ bench/checkqueue.cpp \ + bench/data.h \ + bench/data.cpp \ bench/duplicate_inputs.cpp \ bench/examples.cpp \ bench/rollingbloom.cpp \ bench/chacha20.cpp \ + bench/chacha_poly_aead.cpp \ bench/crypto_hash.cpp \ bench/ccoins_caching.cpp \ bench/gcs_filter.cpp \ bench/merkle_root.cpp \ bench/mempool_eviction.cpp \ + bench/rpc_blockchain.cpp \ bench/rpc_mempool.cpp \ bench/util_time.cpp \ bench/verify_script.cpp \ @@ -76,7 +80,7 @@ CLEAN_BITCOIN_BENCH = bench/*.gcda bench/*.gcno $(GENERATED_BENCH_FILES) CLEANFILES += $(CLEAN_BITCOIN_BENCH) -bench/checkblock.cpp: bench/data/block413567.raw.h +bench/data.cpp: bench/data/block413567.raw.h bitcoin_bench: $(BENCH_BINARY) @@ -89,7 +93,7 @@ bitcoin_bench_clean : FORCE %.raw.h: %.raw @$(MKDIR_P) $(@D) @{ \ - echo "static unsigned const char $(*F)[] = {" && \ + echo "static unsigned const char $(*F)_raw[] = {" && \ $(HEXDUMP) -v -e '8/1 "0x%02x, "' -e '"\n"' $< | $(SED) -e 's/0x ,//g' && \ echo "};"; \ } > "$@.new" && mv -f "$@.new" "$@" diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index c4c08487f3..7540122418 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -98,6 +98,7 @@ QT_FORMS_UI = \ qt/forms/addressbookpage.ui \ qt/forms/askpassphrasedialog.ui \ qt/forms/coincontroldialog.ui \ + qt/forms/createwalletdialog.ui \ qt/forms/editaddressdialog.ui \ qt/forms/helpmessagedialog.ui \ qt/forms/intro.ui \ @@ -117,6 +118,7 @@ QT_MOC_CPP = \ qt/moc_addressbookpage.cpp \ qt/moc_addresstablemodel.cpp \ qt/moc_askpassphrasedialog.cpp \ + qt/moc_createwalletdialog.cpp \ qt/moc_bantablemodel.cpp \ qt/moc_bitcoinaddressvalidator.cpp \ qt/moc_bitcoinamountfield.cpp \ @@ -202,6 +204,7 @@ BITCOIN_QT_H = \ qt/clientmodel.h \ qt/coincontroldialog.h \ qt/coincontroltreewidget.h \ + qt/createwalletdialog.h \ qt/csvmodelwriter.h \ qt/editaddressdialog.h \ qt/guiconstants.h \ @@ -250,8 +253,6 @@ BITCOIN_QT_H = \ RES_ICONS = \ qt/res/icons/add.png \ qt/res/icons/address-book.png \ - qt/res/icons/about.png \ - qt/res/icons/about_qt.png \ qt/res/icons/bitcoin.ico \ qt/res/icons/bitcoin_testnet.ico \ qt/res/icons/bitcoin.png \ @@ -261,13 +262,11 @@ RES_ICONS = \ qt/res/icons/clock3.png \ qt/res/icons/clock4.png \ qt/res/icons/clock5.png \ - qt/res/icons/configure.png \ qt/res/icons/connect0.png \ qt/res/icons/connect1.png \ qt/res/icons/connect2.png \ qt/res/icons/connect3.png \ qt/res/icons/connect4.png \ - qt/res/icons/debugwindow.png \ qt/res/icons/edit.png \ qt/res/icons/editcopy.png \ qt/res/icons/editpaste.png \ @@ -275,21 +274,16 @@ RES_ICONS = \ qt/res/icons/eye.png \ qt/res/icons/eye_minus.png \ qt/res/icons/eye_plus.png \ - qt/res/icons/filesave.png \ qt/res/icons/fontbigger.png \ qt/res/icons/fontsmaller.png \ qt/res/icons/hd_disabled.png \ qt/res/icons/hd_enabled.png \ qt/res/icons/history.png \ - qt/res/icons/info.png \ - qt/res/icons/key.png \ qt/res/icons/lock_closed.png \ qt/res/icons/lock_open.png \ qt/res/icons/network_disabled.png \ - qt/res/icons/open.png \ qt/res/icons/overview.png \ qt/res/icons/proxy.png \ - qt/res/icons/quit.png \ qt/res/icons/receive.png \ qt/res/icons/remove.png \ qt/res/icons/send.png \ @@ -302,8 +296,7 @@ RES_ICONS = \ qt/res/icons/tx_input.png \ qt/res/icons/tx_output.png \ qt/res/icons/tx_mined.png \ - qt/res/icons/warning.png \ - qt/res/icons/verify.png + qt/res/icons/warning.png BITCOIN_QT_BASE_CPP = \ qt/bantablemodel.cpp \ @@ -338,6 +331,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/askpassphrasedialog.cpp \ qt/coincontroldialog.cpp \ qt/coincontroltreewidget.cpp \ + qt/createwalletdialog.cpp \ qt/editaddressdialog.cpp \ qt/openuridialog.cpp \ qt/overviewpage.cpp \ @@ -447,7 +441,7 @@ SECONDARY: $(QT_QM) $(srcdir)/qt/bitcoinstrings.cpp: $(libbitcoin_server_a_SOURCES) $(libbitcoin_wallet_a_SOURCES) $(libbitcoin_common_a_SOURCES) $(libbitcoin_zmq_a_SOURCES) $(libbitcoin_consensus_a_SOURCES) $(libbitcoin_util_a_SOURCES) @test -n $(XGETTEXT) || echo "xgettext is required for updating translations" - $(AM_V_GEN) cd $(srcdir); XGETTEXT=$(XGETTEXT) PACKAGE_NAME="$(PACKAGE_NAME)" COPYRIGHT_HOLDERS="$(COPYRIGHT_HOLDERS)" COPYRIGHT_HOLDERS_SUBSTITUTION="$(COPYRIGHT_HOLDERS_SUBSTITUTION)" $(PYTHON) ../share/qt/extract_strings_qt.py $^ + $(AM_V_GEN) cd $(srcdir); XGETTEXT=$(XGETTEXT) COPYRIGHT_HOLDERS="$(COPYRIGHT_HOLDERS)" $(PYTHON) ../share/qt/extract_strings_qt.py $^ translate: $(srcdir)/qt/bitcoinstrings.cpp $(QT_FORMS_UI) $(QT_FORMS_UI) $(BITCOIN_QT_BASE_CPP) qt/bitcoin.cpp $(BITCOIN_QT_WINDOWS_CPP) $(BITCOIN_QT_WALLET_CPP) $(BITCOIN_QT_H) $(BITCOIN_MM) @test -n $(LUPDATE) || echo "lupdate is required for updating translations" diff --git a/src/addrdb.cpp b/src/addrdb.cpp index c6083f5554..db936486b6 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -44,18 +44,30 @@ bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data fs::path pathTmp = GetDataDir() / tmpfn; FILE *file = fsbridge::fopen(pathTmp, "wb"); CAutoFile fileout(file, SER_DISK, CLIENT_VERSION); - if (fileout.IsNull()) + if (fileout.IsNull()) { + fileout.fclose(); + remove(pathTmp); return error("%s: Failed to open file %s", __func__, pathTmp.string()); + } // Serialize - if (!SerializeDB(fileout, data)) return false; - if (!FileCommit(fileout.Get())) + if (!SerializeDB(fileout, data)) { + fileout.fclose(); + remove(pathTmp); + return false; + } + if (!FileCommit(fileout.Get())) { + fileout.fclose(); + remove(pathTmp); return error("%s: Failed to flush file %s", __func__, pathTmp.string()); + } fileout.fclose(); // replace existing file, if any, with new file - if (!RenameOver(pathTmp, path)) + if (!RenameOver(pathTmp, path)) { + remove(pathTmp); return error("%s: Rename-into-place failed", __func__); + } return true; } diff --git a/src/banman.cpp b/src/banman.cpp index 47d64a8f31..37fca7dd82 100644 --- a/src/banman.cpp +++ b/src/banman.cpp @@ -9,12 +9,13 @@ #include <ui_interface.h> #include <util/system.h> #include <util/time.h> +#include <util/translation.h> BanMan::BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t default_ban_time) : m_client_interface(client_interface), m_ban_db(std::move(ban_file)), m_default_ban_time(default_ban_time) { - if (m_client_interface) m_client_interface->InitMessage(_("Loading banlist...")); + if (m_client_interface) m_client_interface->InitMessage(_("Loading banlist...").translated); int64_t n_start = GetTimeMillis(); m_is_dirty = false; diff --git a/src/bech32.cpp b/src/bech32.cpp index d6b29391a9..4c966350b4 100644 --- a/src/bech32.cpp +++ b/src/bech32.cpp @@ -4,6 +4,8 @@ #include <bech32.h> +#include <assert.h> + namespace { @@ -58,7 +60,7 @@ uint32_t PolyMod(const data& v) // During the course of the loop below, `c` contains the bitpacked coefficients of the // polynomial constructed from just the values of v that were processed so far, mod g(x). In - // the above example, `c` initially corresponds to 1 mod (x), and after processing 2 inputs of + // the above example, `c` initially corresponds to 1 mod g(x), and after processing 2 inputs of // v, it corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the starting value // for `c`. uint32_t c = 1; @@ -145,6 +147,10 @@ namespace bech32 /** Encode a Bech32 string. */ std::string Encode(const std::string& hrp, const data& values) { + // First ensure that the HRP is all lowercase. BIP-173 requires an encoder + // to return a lowercase Bech32 string, but if given an uppercase HRP, the + // result will always be invalid. + for (const char& c : hrp) assert(c < 'A' || c > 'Z'); data checksum = CreateChecksum(hrp, values); data combined = Cat(values, checksum); std::string ret = hrp + '1'; diff --git a/src/bech32.h b/src/bech32.h index 2e2823e974..fb39cd352b 100644 --- a/src/bech32.h +++ b/src/bech32.h @@ -19,7 +19,7 @@ namespace bech32 { -/** Encode a Bech32 string. Returns the empty string in case of failure. */ +/** Encode a Bech32 string. If hrp contains uppercase characters, this will cause an assertion error. */ std::string Encode(const std::string& hrp, const std::vector<uint8_t>& values); /** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */ diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index 8eea96d930..d0d7c03ee1 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -21,14 +21,14 @@ static void SetupBenchArgs() { SetupHelpOptions(gArgs); - gArgs.AddArg("-list", "List benchmarks without executing them. Can be combined with -scaling and -filter", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-evals=<n>", strprintf("Number of measurement evaluations to perform. (default: %u)", DEFAULT_BENCH_EVALUATIONS), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-filter=<regex>", strprintf("Regular expression filter to select benchmark by name (default: %s)", DEFAULT_BENCH_FILTER), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-scaling=<n>", strprintf("Scaling factor for benchmark's runtime (default: %u)", DEFAULT_BENCH_SCALING), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-printer=(console|plot)", strprintf("Choose printer format. console: print data to console. plot: Print results as HTML graph (default: %s)", DEFAULT_BENCH_PRINTER), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-plot-plotlyurl=<uri>", strprintf("URL to use for plotly.js (default: %s)", DEFAULT_PLOT_PLOTLYURL), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-plot-width=<x>", strprintf("Plot width in pixel (default: %u)", DEFAULT_PLOT_WIDTH), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-plot-height=<x>", strprintf("Plot height in pixel (default: %u)", DEFAULT_PLOT_HEIGHT), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-list", "List benchmarks without executing them. Can be combined with -scaling and -filter", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-evals=<n>", strprintf("Number of measurement evaluations to perform. (default: %u)", DEFAULT_BENCH_EVALUATIONS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-filter=<regex>", strprintf("Regular expression filter to select benchmark by name (default: %s)", DEFAULT_BENCH_FILTER), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-scaling=<n>", strprintf("Scaling factor for benchmark's runtime (default: %u)", DEFAULT_BENCH_SCALING), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-printer=(console|plot)", strprintf("Choose printer format. console: print data to console. plot: Print results as HTML graph (default: %s)", DEFAULT_BENCH_PRINTER), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-plot-plotlyurl=<uri>", strprintf("URL to use for plotly.js (default: %s)", DEFAULT_PLOT_PLOTLYURL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-plot-width=<x>", strprintf("Plot width in pixel (default: %u)", DEFAULT_PLOT_WIDTH), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-plot-height=<x>", strprintf("Plot height in pixel (default: %u)", DEFAULT_PLOT_HEIGHT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); } int main(int argc, char** argv) diff --git a/src/bench/ccoins_caching.cpp b/src/bench/ccoins_caching.cpp index 1041a22303..39cab092cf 100644 --- a/src/bench/ccoins_caching.cpp +++ b/src/bench/ccoins_caching.cpp @@ -5,7 +5,7 @@ #include <bench/bench.h> #include <coins.h> #include <policy/policy.h> -#include <wallet/crypter.h> +#include <script/signingprovider.h> #include <vector> @@ -17,7 +17,7 @@ // paid to a TX_PUBKEYHASH. // static std::vector<CMutableTransaction> -SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) +SetupDummyInputs(FillableSigningProvider& keystoreRet, CCoinsViewCache& coinsRet) { std::vector<CMutableTransaction> dummyTransactions; dummyTransactions.resize(2); @@ -55,7 +55,7 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) // (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484) static void CCoinsCaching(benchmark::State& state) { - CBasicKeyStore keystore; + FillableSigningProvider keystore; CCoinsView coinsDummy; CCoinsViewCache coins(&coinsDummy); std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins); diff --git a/src/bench/chacha_poly_aead.cpp b/src/bench/chacha_poly_aead.cpp new file mode 100644 index 0000000000..f5f7297490 --- /dev/null +++ b/src/bench/chacha_poly_aead.cpp @@ -0,0 +1,123 @@ +// Copyright (c) 2019 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 <iostream> + +#include <bench/bench.h> +#include <crypto/chacha_poly_aead.h> +#include <crypto/poly1305.h> // for the POLY1305_TAGLEN constant +#include <hash.h> + +#include <limits> +#include <assert.h> + +/* Number of bytes to process per iteration */ +static constexpr uint64_t BUFFER_SIZE_TINY = 64; +static constexpr uint64_t BUFFER_SIZE_SMALL = 256; +static constexpr uint64_t BUFFER_SIZE_LARGE = 1024 * 1024; + +static const unsigned char k1[32] = {0}; +static const unsigned char k2[32] = {0}; + +static ChaCha20Poly1305AEAD aead(k1, 32, k2, 32); + +static void CHACHA20_POLY1305_AEAD(benchmark::State& state, size_t buffersize, bool include_decryption) +{ + std::vector<unsigned char> in(buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); + std::vector<unsigned char> out(buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); + uint64_t seqnr_payload = 0; + uint64_t seqnr_aad = 0; + int aad_pos = 0; + uint32_t len = 0; + while (state.KeepRunning()) { + // encrypt or decrypt the buffer with a static key + assert(aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true)); + + if (include_decryption) { + // if we decrypt, include the GetLength + assert(aead.GetLength(&len, seqnr_aad, aad_pos, in.data())); + assert(aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true)); + } + + // increase main sequence number + seqnr_payload++; + // increase aad position (position in AAD keystream) + aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN; + if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) { + aad_pos = 0; + seqnr_aad++; + } + if (seqnr_payload + 1 == std::numeric_limits<uint64_t>::max()) { + // reuse of nonce+key is okay while benchmarking. + seqnr_payload = 0; + seqnr_aad = 0; + aad_pos = 0; + } + } +} + +static void CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT(benchmark::State& state) +{ + CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_TINY, false); +} + +static void CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT(benchmark::State& state) +{ + CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_SMALL, false); +} + +static void CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT(benchmark::State& state) +{ + CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_LARGE, false); +} + +static void CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT(benchmark::State& state) +{ + CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_TINY, true); +} + +static void CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT(benchmark::State& state) +{ + CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_SMALL, true); +} + +static void CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT(benchmark::State& state) +{ + CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_LARGE, true); +} + +// Add Hash() (dbl-sha256) bench for comparison + +static void HASH(benchmark::State& state, size_t buffersize) +{ + uint8_t hash[CHash256::OUTPUT_SIZE]; + std::vector<uint8_t> in(buffersize,0); + while (state.KeepRunning()) + CHash256().Write(in.data(), in.size()).Finalize(hash); +} + +static void HASH_64BYTES(benchmark::State& state) +{ + HASH(state, BUFFER_SIZE_TINY); +} + +static void HASH_256BYTES(benchmark::State& state) +{ + HASH(state, BUFFER_SIZE_SMALL); +} + +static void HASH_1MB(benchmark::State& state) +{ + HASH(state, BUFFER_SIZE_LARGE); +} + +BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT, 500000); +BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT, 250000); +BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT, 340); +BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT, 500000); +BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT, 250000); +BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT, 340); +BENCHMARK(HASH_64BYTES, 500000); +BENCHMARK(HASH_256BYTES, 250000); +BENCHMARK(HASH_1MB, 340); diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp index e325333c01..4b13381e16 100644 --- a/src/bench/checkblock.cpp +++ b/src/bench/checkblock.cpp @@ -3,41 +3,34 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <bench/bench.h> +#include <bench/data.h> #include <chainparams.h> #include <validation.h> #include <streams.h> #include <consensus/validation.h> -namespace block_bench { -#include <bench/data/block413567.raw.h> -} // namespace block_bench - // These are the two major time-sinks which happen after we have fully received // a block off the wire, but before we can relay the block on to peers using // compact block relay. static void DeserializeBlockTest(benchmark::State& state) { - CDataStream stream((const char*)block_bench::block413567, - (const char*)block_bench::block413567 + sizeof(block_bench::block413567), - SER_NETWORK, PROTOCOL_VERSION); + CDataStream stream(benchmark::data::block413567, SER_NETWORK, PROTOCOL_VERSION); char a = '\0'; stream.write(&a, 1); // Prevent compaction while (state.KeepRunning()) { CBlock block; stream >> block; - bool rewound = stream.Rewind(sizeof(block_bench::block413567)); + bool rewound = stream.Rewind(benchmark::data::block413567.size()); assert(rewound); } } static void DeserializeAndCheckBlockTest(benchmark::State& state) { - CDataStream stream((const char*)block_bench::block413567, - (const char*)block_bench::block413567 + sizeof(block_bench::block413567), - SER_NETWORK, PROTOCOL_VERSION); + CDataStream stream(benchmark::data::block413567, SER_NETWORK, PROTOCOL_VERSION); char a = '\0'; stream.write(&a, 1); // Prevent compaction @@ -46,7 +39,7 @@ static void DeserializeAndCheckBlockTest(benchmark::State& state) while (state.KeepRunning()) { CBlock block; // Note that CBlock caches its checked state, so we need to recreate it here stream >> block; - bool rewound = stream.Rewind(sizeof(block_bench::block413567)); + bool rewound = stream.Rewind(benchmark::data::block413567.size()); assert(rewound); CValidationState validationState; diff --git a/src/bench/data.cpp b/src/bench/data.cpp new file mode 100644 index 0000000000..0ae4c7cad4 --- /dev/null +++ b/src/bench/data.cpp @@ -0,0 +1,14 @@ +// Copyright (c) 2019 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 <bench/data.h> + +namespace benchmark { +namespace data { + +#include <bench/data/block413567.raw.h> +const std::vector<uint8_t> block413567{block413567_raw, block413567_raw + sizeof(block413567_raw) / sizeof(block413567_raw[0])}; + +} // namespace data +} // namespace benchmark diff --git a/src/bench/data.h b/src/bench/data.h new file mode 100644 index 0000000000..5f13d766ea --- /dev/null +++ b/src/bench/data.h @@ -0,0 +1,19 @@ +// Copyright (c) 2019 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_BENCH_DATA_H +#define BITCOIN_BENCH_DATA_H + +#include <cstdint> +#include <vector> + +namespace benchmark { +namespace data { + +extern const std::vector<uint8_t> block413567; + +} // namespace data +} // namespace benchmark + +#endif // BITCOIN_BENCH_DATA_H diff --git a/src/bench/rpc_blockchain.cpp b/src/bench/rpc_blockchain.cpp new file mode 100644 index 0000000000..29e448fc43 --- /dev/null +++ b/src/bench/rpc_blockchain.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2016-2019 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 <bench/bench.h> +#include <bench/data.h> + +#include <validation.h> +#include <streams.h> +#include <consensus/validation.h> +#include <rpc/blockchain.h> + +#include <univalue.h> + +static void BlockToJsonVerbose(benchmark::State& state) { + CDataStream stream(benchmark::data::block413567, SER_NETWORK, PROTOCOL_VERSION); + char a = '\0'; + stream.write(&a, 1); // Prevent compaction + + CBlock block; + stream >> block; + + CBlockIndex blockindex; + const uint256 blockHash = block.GetHash(); + blockindex.phashBlock = &blockHash; + blockindex.nBits = 403014710; + + while (state.KeepRunning()) { + (void)blockToJSON(block, &blockindex, &blockindex, /*verbose*/ true); + } +} + +BENCHMARK(BlockToJsonVerbose, 10); diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 38010c461e..cde624ce74 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -12,9 +12,12 @@ #include <fs.h> #include <rpc/client.h> #include <rpc/protocol.h> -#include <util/system.h> +#include <rpc/request.h> #include <util/strencodings.h> +#include <util/system.h> +#include <util/translation.h> +#include <functional> #include <memory> #include <stdio.h> #include <tuple> @@ -40,22 +43,22 @@ static void SetupCliArgs() const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET); const auto regtestBaseParams = CreateBaseChainParams(CBaseChainParams::REGTEST); - gArgs.AddArg("-version", "Print version and exit", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-datadir=<dir>", "Specify data directory", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-getinfo", "Get general information from the remote server. Note that unlike server-side RPC calls, the results of -getinfo is the result of multiple non-atomic requests. Some entries in the result may represent results from different states (e.g. wallet balance may be as of a different block from the chain state reported)", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-getinfo", "Get general information from the remote server. Note that unlike server-side RPC calls, the results of -getinfo is the result of multiple non-atomic requests. Some entries in the result may represent results from different states (e.g. wallet balance may be as of a different block from the chain state reported)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); SetupChainParamsBaseOptions(); - gArgs.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-rpcclienttimeout=<n>", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-rpcport=<port>", strprintf("Connect to JSON-RPC on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-rpcwait", "Wait for RPC server to start", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind). This changes the RPC endpoint used, e.g. http://127.0.0.1:8332/wallet/<walletname>", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-stdin", "Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). When combined with -stdinrpcpass, the first line from standard input is used for the RPC password.", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-stdinrpcpass", "Read RPC password from standard input as a single line. When combined with -stdin, the first line from standard input is used for the RPC password.", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcclienttimeout=<n>", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcport=<port>", strprintf("Connect to JSON-RPC on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcwait", "Wait for RPC server to start", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind). This changes the RPC endpoint used, e.g. http://127.0.0.1:8332/wallet/<walletname>", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-stdin", "Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). When combined with -stdinrpcpass, the first line from standard input is used for the RPC password.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-stdinrpcpass", "Read RPC password from standard input as a single line. When combined with -stdin, the first line from standard input is used for the RPC password.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); } /** libevent event log callback */ @@ -122,7 +125,7 @@ static int AppInitRPC(int argc, char* argv[]) } return EXIT_SUCCESS; } - if (!fs::is_directory(GetDataDir(false))) { + if (!CheckDataDirOption()) { tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return EXIT_FAILURE; } diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 933b34744d..f4972c3cd4 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -11,18 +11,20 @@ #include <consensus/consensus.h> #include <core_io.h> #include <key_io.h> -#include <keystore.h> #include <policy/policy.h> #include <policy/rbf.h> #include <primitives/transaction.h> #include <script/script.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <univalue.h> -#include <util/rbf.h> -#include <util/system.h> #include <util/moneystr.h> +#include <util/rbf.h> #include <util/strencodings.h> +#include <util/system.h> +#include <util/translation.h> +#include <functional> #include <memory> #include <stdio.h> @@ -38,36 +40,36 @@ static void SetupBitcoinTxArgs() { SetupHelpOptions(gArgs); - gArgs.AddArg("-create", "Create new, empty TX.", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-json", "Select JSON output", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-txid", "Output only the hex-encoded transaction id of the resultant transaction.", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-create", "Create new, empty TX.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-json", "Select JSON output", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-txid", "Output only the hex-encoded transaction id of the resultant transaction.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); SetupChainParamsBaseOptions(); - gArgs.AddArg("delin=N", "Delete input N from TX", false, OptionsCategory::COMMANDS); - gArgs.AddArg("delout=N", "Delete output N from TX", false, OptionsCategory::COMMANDS); - gArgs.AddArg("in=TXID:VOUT(:SEQUENCE_NUMBER)", "Add input to TX", false, OptionsCategory::COMMANDS); - gArgs.AddArg("locktime=N", "Set TX lock time to N", false, OptionsCategory::COMMANDS); - gArgs.AddArg("nversion=N", "Set TX version to N", false, OptionsCategory::COMMANDS); - gArgs.AddArg("outaddr=VALUE:ADDRESS", "Add address-based output to TX", false, OptionsCategory::COMMANDS); - gArgs.AddArg("outdata=[VALUE:]DATA", "Add data-based output to TX", false, OptionsCategory::COMMANDS); + gArgs.AddArg("delin=N", "Delete input N from TX", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("delout=N", "Delete output N from TX", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("in=TXID:VOUT(:SEQUENCE_NUMBER)", "Add input to TX", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("locktime=N", "Set TX lock time to N", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("nversion=N", "Set TX version to N", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("outaddr=VALUE:ADDRESS", "Add address-based output to TX", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("outdata=[VALUE:]DATA", "Add data-based output to TX", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); gArgs.AddArg("outmultisig=VALUE:REQUIRED:PUBKEYS:PUBKEY1:PUBKEY2:....[:FLAGS]", "Add Pay To n-of-m Multi-sig output to TX. n = REQUIRED, m = PUBKEYS. " "Optionally add the \"W\" flag to produce a pay-to-witness-script-hash output. " - "Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.", false, OptionsCategory::COMMANDS); + "Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); gArgs.AddArg("outpubkey=VALUE:PUBKEY[:FLAGS]", "Add pay-to-pubkey output to TX. " "Optionally add the \"W\" flag to produce a pay-to-witness-pubkey-hash output. " - "Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.", false, OptionsCategory::COMMANDS); + "Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); gArgs.AddArg("outscript=VALUE:SCRIPT[:FLAGS]", "Add raw script output to TX. " "Optionally add the \"W\" flag to produce a pay-to-witness-script-hash output. " - "Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.", false, OptionsCategory::COMMANDS); - gArgs.AddArg("replaceable(=N)", "Set RBF opt-in sequence number for input N (if not provided, opt-in all available inputs)", false, OptionsCategory::COMMANDS); + "Optionally add the \"S\" flag to wrap the output in a pay-to-script-hash.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("replaceable(=N)", "Set RBF opt-in sequence number for input N (if not provided, opt-in all available inputs)", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); gArgs.AddArg("sign=SIGHASH-FLAGS", "Add zero or more signatures to transaction. " "This command requires JSON registers:" "prevtxs=JSON object, " "privatekeys=JSON object. " - "See signrawtransactionwithkey docs for format of sighash flags, JSON objects.", false, OptionsCategory::COMMANDS); + "See signrawtransactionwithkey docs for format of sighash flags, JSON objects.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); - gArgs.AddArg("load=NAME:FILENAME", "Load JSON file FILENAME into register NAME", false, OptionsCategory::REGISTER_COMMANDS); - gArgs.AddArg("set=NAME:JSON-STRING", "Set register NAME to given JSON-STRING", false, OptionsCategory::REGISTER_COMMANDS); + gArgs.AddArg("load=NAME:FILENAME", "Load JSON file FILENAME into register NAME", ArgsManager::ALLOW_ANY, OptionsCategory::REGISTER_COMMANDS); + gArgs.AddArg("set=NAME:JSON-STRING", "Set register NAME to given JSON-STRING", ArgsManager::ALLOW_ANY, OptionsCategory::REGISTER_COMMANDS); } // @@ -557,7 +559,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) if (!registers.count("privatekeys")) throw std::runtime_error("privatekeys register variable must be set."); - CBasicKeyStore tempKeystore; + FillableSigningProvider tempKeystore; UniValue keysObj = registers["privatekeys"]; for (unsigned int kidx = 0; kidx < keysObj.size(); kidx++) { @@ -631,7 +633,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) } } - const CKeyStore& keystore = tempKeystore; + const FillableSigningProvider& keystore = tempKeystore; bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index cbb4ea750c..361fedf35a 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -9,10 +9,12 @@ #include <chainparams.h> #include <chainparamsbase.h> #include <logging.h> -#include <util/system.h> #include <util/strencodings.h> +#include <util/system.h> +#include <util/translation.h> #include <wallet/wallettool.h> +#include <functional> #include <stdio.h> const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; @@ -22,13 +24,13 @@ static void SetupWalletToolArgs() SetupHelpOptions(gArgs); SetupChainParamsBaseOptions(); - gArgs.AddArg("-datadir=<dir>", "Specify data directory", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-wallet=<wallet-name>", "Specify wallet name", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-debug=<category>", "Output debugging information (default: 0).", false, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise.", false, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-wallet=<wallet-name>", "Specify wallet name", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS); + gArgs.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("info", "Get wallet info", false, OptionsCategory::COMMANDS); - gArgs.AddArg("create", "Create new wallet file", false, OptionsCategory::COMMANDS); + gArgs.AddArg("info", "Get wallet info", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); + gArgs.AddArg("create", "Create new wallet file", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); } static bool WalletAppInit(int argc, char* argv[]) @@ -55,7 +57,7 @@ static bool WalletAppInit(int argc, char* argv[]) // check for printtoconsole, allow -debug LogInstance().m_print_to_console = gArgs.GetBoolArg("-printtoconsole", gArgs.GetBoolArg("-debug", false)); - if (!fs::is_directory(GetDataDir(false))) { + if (!CheckDataDirOption()) { tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return false; } diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index ba6de702e0..cb3c4f70b4 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -11,15 +11,17 @@ #include <clientversion.h> #include <compat.h> #include <fs.h> -#include <interfaces/chain.h> #include <init.h> +#include <interfaces/chain.h> #include <noui.h> #include <shutdown.h> +#include <ui_interface.h> +#include <util/strencodings.h> #include <util/system.h> #include <util/threadnames.h> -#include <util/strencodings.h> +#include <util/translation.h> -#include <stdio.h> +#include <functional> const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; @@ -70,8 +72,7 @@ static bool AppInit(int argc, char* argv[]) SetupServerArgs(); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { - tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error.c_str()); - return false; + return InitError(strprintf("Error parsing command line arguments: %s\n", error)); } // Process help and version before taking care about datadir @@ -94,28 +95,23 @@ static bool AppInit(int argc, char* argv[]) try { - if (!fs::is_directory(GetDataDir(false))) - { - tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); - return false; + if (!CheckDataDirOption()) { + return InitError(strprintf("Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", ""))); } if (!gArgs.ReadConfigFiles(error, true)) { - tfm::format(std::cerr, "Error reading configuration file: %s\n", error.c_str()); - return false; + return InitError(strprintf("Error reading configuration file: %s\n", error)); } // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause) try { SelectParams(gArgs.GetChainName()); } catch (const std::exception& e) { - tfm::format(std::cerr, "Error: %s\n", e.what()); - return false; + return InitError(strprintf("%s\n", e.what())); } // Error out when loose non-argument tokens are encountered on command line for (int i = 1; i < argc; i++) { if (!IsSwitchChar(argv[i][0])) { - tfm::format(std::cerr, "Error: Command line contains unexpected token '%s', see bitcoind -h for a list of options.\n", argv[i]); - return false; + return InitError(strprintf("Command line contains unexpected token '%s', see bitcoind -h for a list of options.\n", argv[i])); } } @@ -146,19 +142,17 @@ static bool AppInit(int argc, char* argv[]) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif - tfm::format(std::cout, "Bitcoin server starting\n"); + tfm::format(std::cout, PACKAGE_NAME " daemon starting\n"); // Daemonize if (daemon(1, 0)) { // don't chdir (1), do close FDs (0) - tfm::format(std::cerr, "Error: daemon() failed: %s\n", strerror(errno)); - return false; + return InitError(strprintf("daemon() failed: %s\n", strerror(errno))); } #if defined(MAC_OSX) #pragma GCC diagnostic pop #endif #else - tfm::format(std::cerr, "Error: -daemon is not supported on this operating system\n"); - return false; + return InitError("-daemon is not supported on this operating system\n"); #endif // HAVE_DECL_DAEMON } // Lock data directory after daemonization diff --git a/src/chain.h b/src/chain.h index dd9cc2a598..1b67ebbe41 100644 --- a/src/chain.h +++ b/src/chain.h @@ -95,8 +95,8 @@ enum BlockStatus: uint32_t { //! Unused. BLOCK_VALID_UNKNOWN = 0, - //! Parsed, version ok, hash satisfies claimed PoW, 1 <= vtx count <= max, timestamp not in future - BLOCK_VALID_HEADER = 1, + //! Reserved (was BLOCK_VALID_HEADER). + BLOCK_VALID_RESERVED = 1, //! All parent headers found, difficulty matches, timestamp >= median previous, checkpoint. Implies all parents //! are also at least TREE. @@ -117,7 +117,7 @@ enum BlockStatus: uint32_t { BLOCK_VALID_SCRIPTS = 5, //! All validity bits. - BLOCK_VALID_MASK = BLOCK_VALID_HEADER | BLOCK_VALID_TREE | BLOCK_VALID_TRANSACTIONS | + BLOCK_VALID_MASK = BLOCK_VALID_RESERVED | BLOCK_VALID_TREE | BLOCK_VALID_TRANSACTIONS | BLOCK_VALID_CHAIN | BLOCK_VALID_SCRIPTS, BLOCK_HAVE_DATA = 8, //!< full block available in blk*.dat diff --git a/src/chainparams.cpp b/src/chainparams.cpp index b8e0ea23dd..ad766471dc 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -69,6 +69,8 @@ public: consensus.BIP34Hash = uint256S("0x000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8"); consensus.BIP65Height = 388381; // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0 consensus.BIP66Height = 363725; // 00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931 + consensus.CSVHeight = 419328; // 000000000000000004a1b34462cb8aeebd5799177f7a29cf28f2d1961716b5b5 + consensus.SegwitHeight = 481824; // 0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893 consensus.powLimit = uint256S("00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks consensus.nPowTargetSpacing = 10 * 60; @@ -80,16 +82,6 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008 consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008 - // Deployment of BIP68, BIP112, and BIP113. - consensus.vDeployments[Consensus::DEPLOYMENT_CSV].bit = 0; - consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nStartTime = 1462060800; // May 1st, 2016 - consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nTimeout = 1493596800; // May 1st, 2017 - - // Deployment of SegWit (BIP141, BIP143, and BIP147) - consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].bit = 1; - consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nStartTime = 1479168000; // November 15th, 2016. - consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nTimeout = 1510704000; // November 15th, 2017. - // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x0000000000000000000000000000000000000000051dc8b82f450202ecb3d471"); @@ -141,7 +133,7 @@ public: fDefaultConsistencyChecks = false; fRequireStandard = true; - fMineBlocksOnDemand = false; + m_is_test_chain = false; checkpointData = { { @@ -167,9 +159,6 @@ public: /* nTxCount */ 383732546, /* dTxRate */ 3.685496590998308 }; - - /* disable fallback fee on mainnet */ - m_fallback_fee_enabled = false; } }; @@ -186,6 +175,8 @@ public: consensus.BIP34Hash = uint256S("0x0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8"); consensus.BIP65Height = 581885; // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6 consensus.BIP66Height = 330776; // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182 + consensus.CSVHeight = 770112; // 00000000025e930139bac5c6c31a403776da130831ab85be56578f3fa75369bb + consensus.SegwitHeight = 834624; // 00000000002b980fcd729daaa248fd9316a5200e9b367f4ff2c42453e84201ca consensus.powLimit = uint256S("00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks consensus.nPowTargetSpacing = 10 * 60; @@ -197,16 +188,6 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008 consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008 - // Deployment of BIP68, BIP112, and BIP113. - consensus.vDeployments[Consensus::DEPLOYMENT_CSV].bit = 0; - consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nStartTime = 1456790400; // March 1st, 2016 - consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nTimeout = 1493596800; // May 1st, 2017 - - // Deployment of SegWit (BIP141, BIP143, and BIP147) - consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].bit = 1; - consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nStartTime = 1462060800; // May 1st 2016 - consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nTimeout = 1493596800; // May 1st 2017 - // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000007dbe94253893cbd463"); @@ -247,7 +228,7 @@ public: fDefaultConsistencyChecks = false; fRequireStandard = false; - fMineBlocksOnDemand = false; + m_is_test_chain = true; checkpointData = { @@ -262,9 +243,6 @@ public: /* nTxCount */ 19438708, /* dTxRate */ 0.626 }; - - /* enable fallback fee on testnet */ - m_fallback_fee_enabled = true; } }; @@ -281,6 +259,8 @@ public: consensus.BIP34Hash = uint256(); consensus.BIP65Height = 1351; // BIP65 activated on regtest (Used in functional tests) consensus.BIP66Height = 1251; // BIP66 activated on regtest (Used in functional tests) + consensus.CSVHeight = 432; // CSV activated on regtest (Used in rpc activation tests) + consensus.SegwitHeight = 0; // SEGWIT is always activated on regtest unless overridden consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks consensus.nPowTargetSpacing = 10 * 60; @@ -291,12 +271,6 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; - consensus.vDeployments[Consensus::DEPLOYMENT_CSV].bit = 0; - consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nStartTime = 0; - consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; - consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].bit = 1; - consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; - consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00"); @@ -313,7 +287,7 @@ public: m_assumed_blockchain_size = 0; m_assumed_chain_state_size = 0; - UpdateVersionBitsParametersFromArgs(args); + UpdateActivationParametersFromArgs(args); genesis = CreateGenesisBlock(1296688602, 2, 0x207fffff, 1, 50 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); @@ -324,8 +298,8 @@ public: vSeeds.clear(); //!< Regtest mode doesn't have any DNS seeds. fDefaultConsistencyChecks = true; - fRequireStandard = false; - fMineBlocksOnDemand = true; + fRequireStandard = true; + m_is_test_chain = true; checkpointData = { { @@ -346,9 +320,6 @@ public: base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; bech32_hrp = "bcrt"; - - /* enable fallback fee on regtest */ - m_fallback_fee_enabled = true; } /** @@ -359,11 +330,22 @@ public: consensus.vDeployments[d].nStartTime = nStartTime; consensus.vDeployments[d].nTimeout = nTimeout; } - void UpdateVersionBitsParametersFromArgs(const ArgsManager& args); + void UpdateActivationParametersFromArgs(const ArgsManager& args); }; -void CRegTestParams::UpdateVersionBitsParametersFromArgs(const ArgsManager& args) +void CRegTestParams::UpdateActivationParametersFromArgs(const ArgsManager& args) { + if (gArgs.IsArgSet("-segwitheight")) { + int64_t height = gArgs.GetArg("-segwitheight", consensus.SegwitHeight); + if (height < -1 || height >= std::numeric_limits<int>::max()) { + throw std::runtime_error(strprintf("Activation height %ld for segwit is out of valid range. Use -1 to disable segwit.", height)); + } else if (height == -1) { + LogPrintf("Segwit disabled for testing\n"); + height = std::numeric_limits<int>::max(); + } + consensus.SegwitHeight = static_cast<int>(height); + } + if (!args.IsArgSet("-vbparams")) return; for (const std::string& strDeployment : args.GetArgs("-vbparams")) { diff --git a/src/chainparams.h b/src/chainparams.h index 6ff3dbb7e5..8f1d27e03c 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -66,17 +66,17 @@ public: bool DefaultConsistencyChecks() const { return fDefaultConsistencyChecks; } /** Policy: Filter transactions that do not match well-defined patterns */ bool RequireStandard() const { return fRequireStandard; } + /** If this chain is exclusively used for testing */ + bool IsTestChain() const { return m_is_test_chain; } uint64_t PruneAfterHeight() const { return nPruneAfterHeight; } /** Minimum free space (in GB) needed for data directory */ uint64_t AssumedBlockchainSize() const { return m_assumed_blockchain_size; } /** Minimum free space (in GB) needed for data directory when pruned; Does not include prune target*/ uint64_t AssumedChainStateSize() const { return m_assumed_chain_state_size; } - /** Make miner stop after a block is found. In RPC, don't return until nGenProcLimit blocks are generated */ - bool MineBlocksOnDemand() const { return fMineBlocksOnDemand; } + /** Whether it is possible to mine blocks on demand (no retargeting) */ + bool MineBlocksOnDemand() const { return consensus.fPowNoRetargeting; } /** Return the BIP70 network string (main, test or regtest) */ std::string NetworkIDString() const { return strNetworkID; } - /** Return true if the fallback fee is by default enabled for this network */ - bool IsFallbackFeeEnabled() const { return m_fallback_fee_enabled; } /** Return the list of hostnames to look up for DNS seeds */ const std::vector<std::string>& DNSSeeds() const { return vSeeds; } const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } @@ -101,10 +101,9 @@ protected: std::vector<SeedSpec6> vFixedSeeds; bool fDefaultConsistencyChecks; bool fRequireStandard; - bool fMineBlocksOnDemand; + bool m_is_test_chain; CCheckpointData checkpointData; ChainTxData chainTxData; - bool m_fallback_fee_enabled; }; /** diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index f0559a319a..9b98dff3ca 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -18,9 +18,10 @@ const std::string CBaseChainParams::REGTEST = "regtest"; void SetupChainParamsBaseOptions() { gArgs.AddArg("-regtest", "Enter regression test mode, which uses a special chain in which blocks can be solved instantly. " - "This is intended for regression testing tools and app development.", true, OptionsCategory::CHAINPARAMS); - gArgs.AddArg("-testnet", "Use the test chain", false, OptionsCategory::CHAINPARAMS); - gArgs.AddArg("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest-only)", true, OptionsCategory::CHAINPARAMS); + "This is intended for regression testing tools and app development.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-segwitheight=<n>", "Set the activation height of segwit. -1 to disable. (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-testnet", "Use the test chain", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); } static std::unique_ptr<CBaseChainParams> globalChainBaseParams; diff --git a/src/chainparamsbase.h b/src/chainparamsbase.h index 355df043d3..f34646f7ac 100644 --- a/src/chainparamsbase.h +++ b/src/chainparamsbase.h @@ -7,7 +7,6 @@ #include <memory> #include <string> -#include <vector> /** * CBaseChainParams defines the base parameters (shared between bitcoin-cli and bitcoind) diff --git a/src/coins.cpp b/src/coins.cpp index 3ef9e0463c..6b85edd01a 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -5,6 +5,7 @@ #include <coins.h> #include <consensus/consensus.h> +#include <logging.h> #include <random.h> #include <version.h> @@ -258,3 +259,19 @@ const Coin& AccessByTxid(const CCoinsViewCache& view, const uint256& txid) } return coinEmpty; } + +bool CCoinsViewErrorCatcher::GetCoin(const COutPoint &outpoint, Coin &coin) const { + try { + return CCoinsViewBacked::GetCoin(outpoint, coin); + } catch(const std::runtime_error& e) { + for (auto f : m_err_callbacks) { + f(); + } + LogPrintf("Error reading from database: %s\n", e.what()); + // Starting the shutdown sequence and returning false to the caller would be + // interpreted as 'entry not found' (as opposed to unable to read data), and + // could lead to invalid interpretation. Just exit immediately, as we can't + // continue anyway, and all writes should be atomic. + std::abort(); + } +} diff --git a/src/coins.h b/src/coins.h index 482e233e8c..dca1beabb6 100644 --- a/src/coins.h +++ b/src/coins.h @@ -17,6 +17,7 @@ #include <assert.h> #include <stdint.h> +#include <functional> #include <unordered_map> /** @@ -315,4 +316,28 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction& tx, int nHeight, bool //! lookups to database, so it should be used with care. const Coin& AccessByTxid(const CCoinsViewCache& cache, const uint256& txid); +/** + * This is a minimally invasive approach to shutdown on LevelDB read errors from the + * chainstate, while keeping user interface out of the common library, which is shared + * between bitcoind, and bitcoin-qt and non-server tools. + * + * Writes do not need similar protection, as failure to write is handled by the caller. +*/ +class CCoinsViewErrorCatcher final : public CCoinsViewBacked +{ +public: + explicit CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {} + + void AddReadErrCallback(std::function<void()> f) { + m_err_callbacks.emplace_back(std::move(f)); + } + + bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; + +private: + /** A list of callbacks to execute upon leveldb read error. */ + std::vector<std::function<void()>> m_err_callbacks; + +}; + #endif // BITCOIN_COINS_H diff --git a/src/consensus/params.h b/src/consensus/params.h index 6c3a201f4f..8263b0fef4 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -16,8 +16,6 @@ namespace Consensus { enum DeploymentPos { DEPLOYMENT_TESTDUMMY, - DEPLOYMENT_CSV, // Deployment of BIP68, BIP112, and BIP113. - DEPLOYMENT_SEGWIT, // Deployment of BIP141, BIP143, and BIP147. // NOTE: Also add new deployments to VersionBitsDeploymentInfo in versionbits.cpp MAX_VERSION_BITS_DEPLOYMENTS }; @@ -58,6 +56,12 @@ struct Params { int BIP65Height; /** Block height at which BIP66 becomes active */ int BIP66Height; + /** Block height at which CSV (BIP68, BIP112 and BIP113) becomes active */ + int CSVHeight; + /** Block height at which Segwit (BIP141, BIP143 and BIP147) becomes active. + * Note that segwit v0 script rules are enforced on all blocks except the + * BIP 16 exception blocks. */ + int SegwitHeight; /** * Minimum blocks including miner confirmation of the total of 2016 blocks in a retargeting period, * (nPowTargetTimespan / nPowTargetSpacing) which is also used for BIP9 deployments. diff --git a/src/crypto/chacha_poly_aead.cpp b/src/crypto/chacha_poly_aead.cpp new file mode 100644 index 0000000000..6a3d43deb1 --- /dev/null +++ b/src/crypto/chacha_poly_aead.cpp @@ -0,0 +1,126 @@ +// Copyright (c) 2019 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/chacha_poly_aead.h> + +#include <crypto/common.h> +#include <crypto/poly1305.h> +#include <support/cleanse.h> + +#include <assert.h> +#include <string.h> + +#include <cstdio> +#include <limits> + +#ifndef HAVE_TIMINGSAFE_BCMP + +int timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n) +{ + const unsigned char *p1 = b1, *p2 = b2; + int ret = 0; + + for (; n > 0; n--) + ret |= *p1++ ^ *p2++; + return (ret != 0); +} + +#endif // TIMINGSAFE_BCMP + +ChaCha20Poly1305AEAD::ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_len, const unsigned char* K_2, size_t K_2_len) +{ + assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN); + assert(K_2_len == CHACHA20_POLY1305_AEAD_KEY_LEN); + m_chacha_main.SetKey(K_1, CHACHA20_POLY1305_AEAD_KEY_LEN); + m_chacha_header.SetKey(K_2, CHACHA20_POLY1305_AEAD_KEY_LEN); + + // set the cached sequence number to uint64 max which hints for an unset cache. + // we can't hit uint64 max since the rekey rule (which resets the sequence number) is 1GB + m_cached_aad_seqnr = std::numeric_limits<uint64_t>::max(); +} + +bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int aad_pos, unsigned char* dest, size_t dest_len /* length of the output buffer for sanity checks */, const unsigned char* src, size_t src_len, bool is_encrypt) +{ + // check buffer boundaries + if ( + // if we encrypt, make sure the source contains at least the expected AAD and the destination has at least space for the source + MAC + (is_encrypt && (src_len < CHACHA20_POLY1305_AEAD_AAD_LEN || dest_len < src_len + POLY1305_TAGLEN)) || + // if we decrypt, make sure the source contains at least the expected AAD+MAC and the destination has at least space for the source - MAC + (!is_encrypt && (src_len < CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN || dest_len < src_len - POLY1305_TAGLEN))) { + return false; + } + + unsigned char expected_tag[POLY1305_TAGLEN], poly_key[POLY1305_KEYLEN]; + memset(poly_key, 0, sizeof(poly_key)); + m_chacha_main.SetIV(seqnr_payload); + + // block counter 0 for the poly1305 key + // use lower 32bytes for the poly1305 key + // (throws away 32 unused bytes (upper 32) from this ChaCha20 round) + m_chacha_main.Seek(0); + m_chacha_main.Crypt(poly_key, poly_key, sizeof(poly_key)); + + // if decrypting, verify the tag prior to decryption + if (!is_encrypt) { + const unsigned char* tag = src + src_len - POLY1305_TAGLEN; + poly1305_auth(expected_tag, src, src_len - POLY1305_TAGLEN, poly_key); + + // constant time compare the calculated MAC with the provided MAC + if (timingsafe_bcmp(expected_tag, tag, POLY1305_TAGLEN) != 0) { + memory_cleanse(expected_tag, sizeof(expected_tag)); + memory_cleanse(poly_key, sizeof(poly_key)); + return false; + } + memory_cleanse(expected_tag, sizeof(expected_tag)); + // MAC has been successfully verified, make sure we don't covert it in decryption + src_len -= POLY1305_TAGLEN; + } + + // calculate and cache the next 64byte keystream block if requested sequence number is not yet the cache + if (m_cached_aad_seqnr != seqnr_aad) { + m_cached_aad_seqnr = seqnr_aad; + m_chacha_header.SetIV(seqnr_aad); + m_chacha_header.Seek(0); + m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); + } + // crypt the AAD (3 bytes message length) with given position in AAD cipher instance keystream + dest[0] = src[0] ^ m_aad_keystream_buffer[aad_pos]; + dest[1] = src[1] ^ m_aad_keystream_buffer[aad_pos + 1]; + dest[2] = src[2] ^ m_aad_keystream_buffer[aad_pos + 2]; + + // Set the playload ChaCha instance block counter to 1 and crypt the payload + m_chacha_main.Seek(1); + m_chacha_main.Crypt(src + CHACHA20_POLY1305_AEAD_AAD_LEN, dest + CHACHA20_POLY1305_AEAD_AAD_LEN, src_len - CHACHA20_POLY1305_AEAD_AAD_LEN); + + // If encrypting, calculate and append tag + if (is_encrypt) { + // the poly1305 tag expands over the AAD (3 bytes length) & encrypted payload + poly1305_auth(dest + src_len, dest, src_len, poly_key); + } + + // cleanse no longer required MAC and polykey + memory_cleanse(poly_key, sizeof(poly_key)); + return true; +} + +bool ChaCha20Poly1305AEAD::GetLength(uint32_t* len24_out, uint64_t seqnr_aad, int aad_pos, const uint8_t* ciphertext) +{ + // enforce valid aad position to avoid accessing outside of the 64byte keystream cache + // (there is space for 21 times 3 bytes) + assert(aad_pos >= 0 && aad_pos < CHACHA20_ROUND_OUTPUT - CHACHA20_POLY1305_AEAD_AAD_LEN); + if (m_cached_aad_seqnr != seqnr_aad) { + // we need to calculate the 64 keystream bytes since we reached a new aad sequence number + m_cached_aad_seqnr = seqnr_aad; + m_chacha_header.SetIV(seqnr_aad); // use LE for the nonce + m_chacha_header.Seek(0); // block counter 0 + m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); // write keystream to the cache + } + + // decrypt the ciphertext length by XORing the right position of the 64byte keystream cache with the ciphertext + *len24_out = (ciphertext[0] ^ m_aad_keystream_buffer[aad_pos + 0]) | + (ciphertext[1] ^ m_aad_keystream_buffer[aad_pos + 1]) << 8 | + (ciphertext[2] ^ m_aad_keystream_buffer[aad_pos + 2]) << 16; + + return true; +} diff --git a/src/crypto/chacha_poly_aead.h b/src/crypto/chacha_poly_aead.h new file mode 100644 index 0000000000..b3ba781cdd --- /dev/null +++ b/src/crypto/chacha_poly_aead.h @@ -0,0 +1,146 @@ +// Copyright (c) 2019 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_CHACHA_POLY_AEAD_H +#define BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H + +#include <crypto/chacha20.h> + +#include <cmath> + +static constexpr int CHACHA20_POLY1305_AEAD_KEY_LEN = 32; +static constexpr int CHACHA20_POLY1305_AEAD_AAD_LEN = 3; /* 3 bytes length */ +static constexpr int CHACHA20_ROUND_OUTPUT = 64; /* 64 bytes per round */ +static constexpr int AAD_PACKAGES_PER_ROUND = 21; /* 64 / 3 round down*/ + +/* A AEAD class for ChaCha20-Poly1305@bitcoin. + * + * ChaCha20 is a stream cipher designed by Daniel Bernstein and described in + * <ref>[http://cr.yp.to/chacha/chacha-20080128.pdf ChaCha20]</ref>. It operates + * by permuting 128 fixed bits, 128 or 256 bits of key, a 64 bit nonce and a 64 + * bit counter into 64 bytes of output. This output is used as a keystream, with + * any unused bytes simply discarded. + * + * Poly1305 <ref>[http://cr.yp.to/mac/poly1305-20050329.pdf Poly1305]</ref>, also + * by Daniel Bernstein, is a one-time Carter-Wegman MAC that computes a 128 bit + * integrity tag given a message and a single-use 256 bit secret key. + * + * The chacha20-poly1305@bitcoin combines these two primitives into an + * authenticated encryption mode. The construction used is based on that proposed + * for TLS by Adam Langley in + * <ref>[http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-03 "ChaCha20 + * and Poly1305 based Cipher Suites for TLS", Adam Langley]</ref>, but differs in + * the layout of data passed to the MAC and in the addition of encryption of the + * packet lengths. + * + * ==== Detailed Construction ==== + * + * The chacha20-poly1305@bitcoin cipher requires two 256 bits of key material as + * output from the key exchange. Each key (K_1 and K_2) are used by two separate + * instances of chacha20. + * + * The instance keyed by K_1 is a stream cipher that is used only to encrypt the 3 + * byte packet length field and has its own sequence number. The second instance, + * keyed by K_2, is used in conjunction with poly1305 to build an AEAD + * (Authenticated Encryption with Associated Data) that is used to encrypt and + * authenticate the entire packet. + * + * Two separate cipher instances are used here so as to keep the packet lengths + * confidential but not create an oracle for the packet payload cipher by + * decrypting and using the packet length prior to checking the MAC. By using an + * independently-keyed cipher instance to encrypt the length, an active attacker + * seeking to exploit the packet input handling as a decryption oracle can learn + * nothing about the payload contents or its MAC (assuming key derivation, + * ChaCha20 and Poly1305 are secure). + * + * The AEAD is constructed as follows: for each packet, generate a Poly1305 key by + * taking the first 256 bits of ChaCha20 stream output generated using K_2, an IV + * consisting of the packet sequence number encoded as an LE uint64 and a ChaCha20 + * block counter of zero. The K_2 ChaCha20 block counter is then set to the + * little-endian encoding of 1 (i.e. {1, 0, 0, 0, 0, 0, 0, 0}) and this instance + * is used for encryption of the packet payload. + * + * ==== Packet Handling ==== + * + * When receiving a packet, the length must be decrypted first. When 3 bytes of + * ciphertext length have been received, they may be decrypted. + * + * A ChaCha20 round always calculates 64bytes which is sufficient to crypt 21 + * times a 3 bytes length field (21*3 = 63). The length field sequence number can + * thus be used 21 times (keystream caching). + * + * The length field must be enc-/decrypted with the ChaCha20 keystream keyed with + * K_1 defined by block counter 0, the length field sequence number in little + * endian and a keystream position from 0 to 60. + * + * Once the entire packet has been received, the MAC MUST be checked before + * decryption. A per-packet Poly1305 key is generated as described above and the + * MAC tag calculated using Poly1305 with this key over the ciphertext of the + * packet length and the payload together. The calculated MAC is then compared in + * constant time with the one appended to the packet and the packet decrypted + * using ChaCha20 as described above (with K_2, the packet sequence number as + * nonce and a starting block counter of 1). + * + * Detection of an invalid MAC MUST lead to immediate connection termination. + * + * To send a packet, first encode the 3 byte length and encrypt it using K_1 as + * described above. Encrypt the packet payload (using K_2) and append it to the + * encrypted length. Finally, calculate a MAC tag and append it. + * + * The initiating peer MUST use <code>K_1_A, K_2_A</code> to encrypt messages on + * the send channel, <code>K_1_B, K_2_B</code> MUST be used to decrypt messages on + * the receive channel. + * + * The responding peer MUST use <code>K_1_A, K_2_A</code> to decrypt messages on + * the receive channel, <code>K_1_B, K_2_B</code> MUST be used to encrypt messages + * on the send channel. + * + * Optimized implementations of ChaCha20-Poly1305@bitcoin are relatively fast in + * general, therefore it is very likely that encrypted messages require not more + * CPU cycles per bytes then the current unencrypted p2p message format + * (ChaCha20/Poly1305 versus double SHA256). + * + * The initial packet sequence numbers are 0. + * + * K_2 ChaCha20 cipher instance (payload) must never reuse a {key, nonce} for + * encryption nor may it be used to encrypt more than 2^70 bytes under the same + * {key, nonce}. + * + * K_1 ChaCha20 cipher instance (length field/AAD) must never reuse a {key, nonce, + * position-in-keystream} for encryption nor may it be used to encrypt more than + * 2^70 bytes under the same {key, nonce}. + * + * We use message sequence numbers for both communication directions. + */ + +class ChaCha20Poly1305AEAD +{ +private: + ChaCha20 m_chacha_main; // payload and poly1305 key-derivation cipher instance + ChaCha20 m_chacha_header; // AAD cipher instance (encrypted length) + unsigned char m_aad_keystream_buffer[CHACHA20_ROUND_OUTPUT]; // aad keystream cache + uint64_t m_cached_aad_seqnr; // aad keystream cache hint + +public: + ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_len, const unsigned char* K_2, size_t K_2_len); + + explicit ChaCha20Poly1305AEAD(const ChaCha20Poly1305AEAD&) = delete; + + /** Encrypts/decrypts a packet + seqnr_payload, the message sequence number + seqnr_aad, the messages AAD sequence number which allows reuse of the AAD keystream + aad_pos, position to use in the AAD keystream to encrypt the AAD + dest, output buffer, must be of a size equal or larger then CHACHA20_POLY1305_AEAD_AAD_LEN + payload (+ POLY1305_TAG_LEN in encryption) bytes + destlen, length of the destination buffer + src, the AAD+payload to encrypt or the AAD+payload+MAC to decrypt + src_len, the length of the source buffer + is_encrypt, set to true if we encrypt (creates and appends the MAC instead of verifying it) + */ + bool Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int aad_pos, unsigned char* dest, size_t dest_len, const unsigned char* src, size_t src_len, bool is_encrypt); + + /** decrypts the 3 bytes AAD data and decodes it into a uint32_t field */ + bool GetLength(uint32_t* len24_out, uint64_t seqnr_aad, int aad_pos, const uint8_t* ciphertext); +}; + +#endif // BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index eeec6dec25..126e3479f3 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -5,8 +5,10 @@ #include <stdio.h> #include <util/system.h> #include <walletinitinterface.h> +#include <support/allocators/secure.h> class CWallet; +enum class WalletCreationStatus; namespace interfaces { class Chain; @@ -74,6 +76,11 @@ std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& throw std::logic_error("Wallet function called in non-wallet build."); } +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::shared_ptr<CWallet>& result) +{ + throw std::logic_error("Wallet function called in non-wallet build."); +} + namespace interfaces { class Wallet; diff --git a/src/httprpc.cpp b/src/httprpc.cpp index c7a119440b..306d718574 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -5,19 +5,20 @@ #include <httprpc.h> #include <chainparams.h> +#include <crypto/hmac_sha256.h> #include <httpserver.h> #include <key_io.h> #include <rpc/protocol.h> #include <rpc/server.h> #include <sync.h> -#include <util/system.h> -#include <util/strencodings.h> #include <ui_interface.h> +#include <util/strencodings.h> +#include <util/system.h> +#include <util/translation.h> #include <walletinitinterface.h> -#include <crypto/hmac_sha256.h> -#include <stdio.h> #include <memory> +#include <stdio.h> #include <boost/algorithm/string.hpp> // boost::trim @@ -218,7 +219,7 @@ static bool InitRPCAuthentication() LogPrintf("No rpcpassword set - using random cookie authentication.\n"); if (!GenerateAuthCookie(&strRPCUserColonPass)) { uiInterface.ThreadSafeMessageBox( - _("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode + _("Error: A fatal internal error occurred, see debug.log for details").translated, // Same message as AbortNode "", CClientUIInterface::MSG_ERROR); return false; } diff --git a/src/index/blockfilterindex.cpp b/src/index/blockfilterindex.cpp index 20f33baf2c..c3ce8d7af0 100644 --- a/src/index/blockfilterindex.cpp +++ b/src/index/blockfilterindex.cpp @@ -53,7 +53,7 @@ struct DBHeightKey { int height; DBHeightKey() : height(0) {} - DBHeightKey(int height_in) : height(height_in) {} + explicit DBHeightKey(int height_in) : height(height_in) {} template<typename Stream> void Serialize(Stream& s) const @@ -76,7 +76,7 @@ struct DBHeightKey { struct DBHashKey { uint256 hash; - DBHashKey(const uint256& hash_in) : hash(hash_in) {} + explicit DBHashKey(const uint256& hash_in) : hash(hash_in) {} ADD_SERIALIZE_METHODS; diff --git a/src/index/txindex.cpp b/src/index/txindex.cpp index 929b85bfb5..62db38f894 100644 --- a/src/index/txindex.cpp +++ b/src/index/txindex.cpp @@ -6,6 +6,7 @@ #include <shutdown.h> #include <ui_interface.h> #include <util/system.h> +#include <util/translation.h> #include <validation.h> #include <boost/thread.hpp> @@ -137,7 +138,7 @@ bool TxIndex::DB::MigrateData(CBlockTreeDB& block_tree_db, const CBlockLocator& int64_t count = 0; LogPrintf("Upgrading txindex database... [0%%]\n"); - uiInterface.ShowProgress(_("Upgrading txindex database"), 0, true); + uiInterface.ShowProgress(_("Upgrading txindex database").translated, 0, true); int report_done = 0; const size_t batch_size = 1 << 24; // 16 MiB @@ -174,7 +175,7 @@ bool TxIndex::DB::MigrateData(CBlockTreeDB& block_tree_db, const CBlockLocator& (static_cast<uint32_t>(*(txid.begin() + 1)) << 0); int percentage_done = (int)(high_nibble * 100.0 / 65536.0 + 0.5); - uiInterface.ShowProgress(_("Upgrading txindex database"), percentage_done, true); + uiInterface.ShowProgress(_("Upgrading txindex database").translated, percentage_done, true); if (report_done < percentage_done/10) { LogPrintf("Upgrading txindex database... [%d%%]\n", percentage_done); report_done = percentage_done/10; diff --git a/src/init.cpp b/src/init.cpp index 6625080c6e..ee49ac1914 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -18,40 +18,43 @@ #include <compat/sanity.h> #include <consensus/validation.h> #include <fs.h> -#include <httpserver.h> #include <httprpc.h> +#include <httpserver.h> #include <index/blockfilterindex.h> -#include <interfaces/chain.h> #include <index/txindex.h> +#include <interfaces/chain.h> #include <key.h> -#include <validation.h> #include <miner.h> -#include <netbase.h> #include <net.h> +#include <net_permissions.h> #include <net_processing.h> +#include <netbase.h> #include <policy/feerate.h> #include <policy/fees.h> #include <policy/policy.h> #include <policy/settings.h> -#include <rpc/server.h> -#include <rpc/register.h> #include <rpc/blockchain.h> +#include <rpc/register.h> +#include <rpc/server.h> #include <rpc/util.h> -#include <script/standard.h> -#include <script/sigcache.h> #include <scheduler.h> +#include <script/sigcache.h> +#include <script/standard.h> #include <shutdown.h> -#include <util/threadnames.h> #include <timedata.h> +#include <torcontrol.h> #include <txdb.h> #include <txmempool.h> -#include <torcontrol.h> #include <ui_interface.h> -#include <util/system.h> #include <util/moneystr.h> +#include <util/system.h> +#include <util/threadnames.h> +#include <util/translation.h> #include <util/validation.h> +#include <validation.h> #include <validationinterface.h> #include <walletinitinterface.h> + #include <stdint.h> #include <stdio.h> @@ -117,7 +120,7 @@ NODISCARD static bool CreatePidFile() #endif return true; } else { - return InitError(strprintf(_("Unable to create the PID file '%s': %s"), GetPidFile().string(), std::strerror(errno))); + return InitError(strprintf(_("Unable to create the PID file '%s': %s").translated, GetPidFile().string(), std::strerror(errno))); } } @@ -146,32 +149,6 @@ NODISCARD static bool CreatePidFile() // shutdown thing. // -/** - * This is a minimally invasive approach to shutdown on LevelDB read errors from the - * chainstate, while keeping user interface out of the common library, which is shared - * between bitcoind, and bitcoin-qt and non-server tools. -*/ -class CCoinsViewErrorCatcher final : public CCoinsViewBacked -{ -public: - explicit CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {} - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override { - try { - return CCoinsViewBacked::GetCoin(outpoint, coin); - } catch(const std::runtime_error& e) { - uiInterface.ThreadSafeMessageBox(_("Error reading from database, shutting down."), "", CClientUIInterface::MSG_ERROR); - LogPrintf("Error reading from database: %s\n", e.what()); - // Starting the shutdown sequence and returning false to the caller would be - // interpreted as 'entry not found' (as opposed to unable to read data), and - // could lead to invalid interpretation. Just exit immediately, as we can't - // continue anyway, and all writes should be atomic. - abort(); - } - } - // Writes do not need similar protection, as failure to write is handled by the caller. -}; - -static std::unique_ptr<CCoinsViewErrorCatcher> pcoinscatcher; static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle; static boost::thread_group threadGroup; @@ -256,8 +233,14 @@ void Shutdown(InitInterfaces& interfaces) } // FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing - if (pcoinsTip != nullptr) { - ::ChainstateActive().ForceFlushStateToDisk(); + // + // g_chainstate is referenced here directly (instead of ::ChainstateActive()) because it + // may not have been initialized yet. + { + LOCK(cs_main); + if (g_chainstate && g_chainstate->CanFlushToDisk()) { + g_chainstate->ForceFlushStateToDisk(); + } } // After there are no more peers/RPC left to give us new data which may generate @@ -272,12 +255,10 @@ void Shutdown(InitInterfaces& interfaces) { LOCK(cs_main); - if (pcoinsTip != nullptr) { - ::ChainstateActive().ForceFlushStateToDisk(); + if (g_chainstate && g_chainstate->CanFlushToDisk()) { + g_chainstate->ForceFlushStateToDisk(); + g_chainstate->ResetCoinsViews(); } - pcoinsTip.reset(); - pcoinscatcher.reset(); - pcoinsdbview.reset(); pblocktree.reset(); } for (const auto& client : interfaces.chain_clients) { @@ -360,7 +341,7 @@ static void OnRPCStopped() void SetupServerArgs() { SetupHelpOptions(gArgs); - gArgs.AddArg("-help-debug", "Print help message with debugging options and exit", false, OptionsCategory::DEBUG_TEST); // server-only for now + gArgs.AddArg("-help-debug", "Print help message with debugging options and exit", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); // server-only for now const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN); const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET); @@ -375,99 +356,111 @@ void SetupServerArgs() // GUI args. These will be overwritten by SetupUIArgs for the GUI "-allowselfsignedrootcertificates", "-choosedatadir", "-lang=<lang>", "-min", "-resetguisettings", "-rootcertificates=<file>", "-splash", "-uiplatform"}; - gArgs.AddArg("-version", "Print version and exit", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-alertnotify=<cmd>", "Execute command when a relevant alert is received or we see a really long fork (%s in cmd is replaced by message)", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-assumevalid=<hex>", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex()), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-blocksdir=<dir>", "Specify directory to hold blocks subdirectory for *.dat files (default: <datadir>)", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-blocknotify=<cmd>", "Execute command when the best block changes (%s in cmd is replaced by block hash)", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-blockreconstructionextratxn=<n>", strprintf("Extra transactions to keep in memory for compact block reconstructions (default: %u)", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Transactions from the wallet or RPC are not affected. (default: %u)", DEFAULT_BLOCKSONLY), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-datadir=<dir>", "Specify data directory", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), true, OptionsCategory::OPTIONS); - gArgs.AddArg("-dbcache=<n>", strprintf("Maximum database cache size <n> MiB (%d to %d, default: %d). In addition, unused mempool memory is shared for this cache (see -maxmempool).", nMinDbCache, nMaxDbCache, nDefaultDbCache), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-debuglogfile=<file>", strprintf("Specify location of debug log file. Relative paths will be prefixed by a net-specific datadir location. (-nodebuglogfile to disable; default: %s)", DEFAULT_DEBUGLOGFILE), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-feefilter", strprintf("Tell other nodes to filter invs to us by our mempool min fee (default: %u)", DEFAULT_FEEFILTER), true, OptionsCategory::OPTIONS); - gArgs.AddArg("-includeconf=<file>", "Specify additional configuration file, relative to the -datadir path (only useable from configuration file, not command line)", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-loadblock=<file>", "Imports blocks from external blk000??.dat file on startup", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-maxmempool=<n>", strprintf("Keep the transaction memory pool below <n> megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-maxorphantx=<n>", strprintf("Keep at most <n> unconnectable transactions in memory (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-mempoolexpiry=<n>", strprintf("Do not keep transactions in the mempool longer than <n> hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-minimumchainwork=<hex>", strprintf("Minimum work assumed to exist on a valid chain in hex (default: %s, testnet: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex()), true, OptionsCategory::OPTIONS); + gArgs.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); +#if HAVE_SYSTEM + gArgs.AddArg("-alertnotify=<cmd>", "Execute command when a relevant alert is received or we see a really long fork (%s in cmd is replaced by message)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); +#endif + gArgs.AddArg("-assumevalid=<hex>", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-blocksdir=<dir>", "Specify directory to hold blocks subdirectory for *.dat files (default: <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); +#if HAVE_SYSTEM + gArgs.AddArg("-blocknotify=<cmd>", "Execute command when the best block changes (%s in cmd is replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); +#endif + gArgs.AddArg("-blockreconstructionextratxn=<n>", strprintf("Extra transactions to keep in memory for compact block reconstructions (default: %u)", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Transactions from the wallet, RPC and relay whitelisted inbound peers are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); + gArgs.AddArg("-dbcache=<n>", strprintf("Maximum database cache size <n> MiB (%d to %d, default: %d). In addition, unused mempool memory is shared for this cache (see -maxmempool).", nMinDbCache, nMaxDbCache, nDefaultDbCache), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-debuglogfile=<file>", strprintf("Specify location of debug log file. Relative paths will be prefixed by a net-specific datadir location. (-nodebuglogfile to disable; default: %s)", DEFAULT_DEBUGLOGFILE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-feefilter", strprintf("Tell other nodes to filter invs to us by our mempool min fee (default: %u)", DEFAULT_FEEFILTER), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); + gArgs.AddArg("-includeconf=<file>", "Specify additional configuration file, relative to the -datadir path (only useable from configuration file, not command line)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-loadblock=<file>", "Imports blocks from external blk000??.dat file on startup", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-maxmempool=<n>", strprintf("Keep the transaction memory pool below <n> megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-maxorphantx=<n>", strprintf("Keep at most <n> unconnectable transactions in memory (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-mempoolexpiry=<n>", strprintf("Do not keep transactions in the mempool longer than <n> hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-minimumchainwork=<hex>", strprintf("Minimum work assumed to exist on a valid chain in hex (default: %s, testnet: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); gArgs.AddArg("-par=<n>", strprintf("Set the number of script verification threads (%u to %d, 0 = auto, <0 = leave that many cores free, default: %d)", - -GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-pid=<file>", strprintf("Specify pid file. Relative paths will be prefixed by a net-specific datadir location. (default: %s)", BITCOIN_PID_FILENAME), false, OptionsCategory::OPTIONS); + -GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-pid=<file>", strprintf("Specify pid file. Relative paths will be prefixed by a net-specific datadir location. (default: %s)", BITCOIN_PID_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-prune=<n>", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex and -rescan. " "Warning: Reverting this setting requires re-downloading the entire blockchain. " - "(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >=%u = automatically prune block files to stay under the specified target size in MiB)", MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead.", false, OptionsCategory::OPTIONS); + "(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >=%u = automatically prune block files to stay under the specified target size in MiB)", MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #ifndef WIN32 - gArgs.AddArg("-sysperms", "Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-sysperms", "Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #else hidden_args.emplace_back("-sysperms"); #endif - gArgs.AddArg("-txindex", strprintf("Maintain a full transaction index, used by the getrawtransaction rpc call (default: %u)", DEFAULT_TXINDEX), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-txindex", strprintf("Maintain a full transaction index, used by the getrawtransaction rpc call (default: %u)", DEFAULT_TXINDEX), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-blockfilterindex=<type>", strprintf("Maintain an index of compact filters by block (default: %s, values: %s).", DEFAULT_BLOCKFILTERINDEX, ListBlockFilterTypes()) + " If <type> is not supplied or if <type> = 1, indexes for all known types are enabled.", - false, OptionsCategory::OPTIONS); - - gArgs.AddArg("-addnode=<ip>", "Add a node to connect to and attempt to keep the connection open (see the `addnode` RPC command help for more info). This option can be specified multiple times to add multiple nodes.", false, OptionsCategory::CONNECTION); - gArgs.AddArg("-banscore=<n>", strprintf("Threshold for disconnecting misbehaving peers (default: %u)", DEFAULT_BANSCORE_THRESHOLD), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-bantime=<n>", strprintf("Number of seconds to keep misbehaving peers from reconnecting (default: %u)", DEFAULT_MISBEHAVING_BANTIME), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-bind=<addr>", "Bind to given address and always listen on it. Use [host]:port notation for IPv6", false, OptionsCategory::CONNECTION); - gArgs.AddArg("-connect=<ip>", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", false, OptionsCategory::CONNECTION); - gArgs.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", false, OptionsCategory::CONNECTION); - gArgs.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and -connect (default: %u)", DEFAULT_NAME_LOOKUP), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-dnsseed", "Query for peer addresses via DNS lookup, if low on addresses (default: 1 unless -connect used)", false, OptionsCategory::CONNECTION); - gArgs.AddArg("-enablebip61", strprintf("Send reject messages per BIP61 (default: %u)", DEFAULT_ENABLE_BIP61), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-externalip=<ip>", "Specify your own public address", false, OptionsCategory::CONNECTION); - gArgs.AddArg("-forcednsseed", strprintf("Always query for peer addresses via DNS lookup (default: %u)", DEFAULT_FORCEDNSSEED), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-listen", "Accept connections from outside (default: 1 if no -proxy or -connect)", false, OptionsCategory::CONNECTION); - gArgs.AddArg("-listenonion", strprintf("Automatically create Tor hidden service (default: %d)", DEFAULT_LISTEN_ONION), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-maxconnections=<n>", strprintf("Maintain at most <n> connections to peers (default: %u)", DEFAULT_MAX_PEER_CONNECTIONS), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-maxreceivebuffer=<n>", strprintf("Maximum per-connection receive buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-maxsendbuffer=<n>", strprintf("Maximum per-connection send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)", DEFAULT_MAX_TIME_ADJUSTMENT), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target (in MiB per 24h), 0 = no limit (default: %d)", DEFAULT_MAX_UPLOAD_TARGET), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor hidden services, set -noonion to disable (default: -proxy)", false, OptionsCategory::CONNECTION); - gArgs.AddArg("-onlynet=<net>", "Make outgoing connections only through network <net> (ipv4, ipv6 or onion). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks.", false, OptionsCategory::CONNECTION); - gArgs.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-port=<port>", strprintf("Listen for connections on <port> (default: %u, testnet: %u, regtest: %u)", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", false, OptionsCategory::CONNECTION); - gArgs.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes.", false, OptionsCategory::CONNECTION); - gArgs.AddArg("-timeout=<n>", strprintf("Specify connection timeout in milliseconds (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-peertimeout=<n>", strprintf("Specify p2p connection timeout in seconds. This option determines the amount of time a peer may be inactive before the connection to it is dropped. (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), true, OptionsCategory::CONNECTION); - gArgs.AddArg("-torcontrol=<ip>:<port>", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), false, OptionsCategory::CONNECTION); - gArgs.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", false, OptionsCategory::CONNECTION); + ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + + gArgs.AddArg("-addnode=<ip>", "Add a node to connect to and attempt to keep the connection open (see the `addnode` RPC command help for more info). This option can be specified multiple times to add multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + gArgs.AddArg("-banscore=<n>", strprintf("Threshold for disconnecting misbehaving peers (default: %u)", DEFAULT_BANSCORE_THRESHOLD), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-bantime=<n>", strprintf("Number of seconds to keep misbehaving peers from reconnecting (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-bind=<addr>", "Bind to given address and always listen on it. Use [host]:port notation for IPv6", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + gArgs.AddArg("-connect=<ip>", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + gArgs.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and -connect (default: %u)", DEFAULT_NAME_LOOKUP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-dnsseed", "Query for peer addresses via DNS lookup, if low on addresses (default: 1 unless -connect used)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-enablebip61", strprintf("Send reject messages per BIP61 (default: %u)", DEFAULT_ENABLE_BIP61), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-externalip=<ip>", "Specify your own public address", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-forcednsseed", strprintf("Always query for peer addresses via DNS lookup (default: %u)", DEFAULT_FORCEDNSSEED), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-listen", "Accept connections from outside (default: 1 if no -proxy or -connect)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-listenonion", strprintf("Automatically create Tor hidden service (default: %d)", DEFAULT_LISTEN_ONION), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-maxconnections=<n>", strprintf("Maintain at most <n> connections to peers (default: %u)", DEFAULT_MAX_PEER_CONNECTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-maxreceivebuffer=<n>", strprintf("Maximum per-connection receive buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-maxsendbuffer=<n>", strprintf("Maximum per-connection send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target (in MiB per 24h), 0 = no limit (default: %d)", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor hidden services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-onlynet=<net>", "Make outgoing connections only through network <net> (ipv4, ipv6 or onion). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-port=<port>", strprintf("Listen for connections on <port> (default: %u, testnet: %u, regtest: %u)", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + gArgs.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-timeout=<n>", strprintf("Specify connection timeout in milliseconds (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-peertimeout=<n>", strprintf("Specify p2p connection timeout in seconds. This option determines the amount of time a peer may be inactive before the connection to it is dropped. (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION); + gArgs.AddArg("-torcontrol=<ip>:<port>", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + gArgs.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); #ifdef USE_UPNP #if USE_UPNP - gArgs.AddArg("-upnp", "Use UPnP to map the listening port (default: 1 when listening and no -proxy)", false, OptionsCategory::CONNECTION); + gArgs.AddArg("-upnp", "Use UPnP to map the listening port (default: 1 when listening and no -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); #else - gArgs.AddArg("-upnp", strprintf("Use UPnP to map the listening port (default: %u)", 0), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-upnp", strprintf("Use UPnP to map the listening port (default: %u)", 0), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); #endif #else hidden_args.emplace_back("-upnp"); #endif - gArgs.AddArg("-whitebind=<addr>", "Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6", false, OptionsCategory::CONNECTION); - gArgs.AddArg("-whitelist=<IP address or network>", "Whitelist peers connecting from the given IP address (e.g. 1.2.3.4) or CIDR notated network (e.g. 1.2.3.0/24). Can be specified multiple times." - " Whitelisted peers cannot be DoS banned", false, OptionsCategory::CONNECTION); + gArgs.AddArg("-whitebind=<[permissions@]addr>", "Bind to given address and whitelist peers connecting to it. " + "Use [host]:port notation for IPv6. Allowed permissions are bloomfilter (allow requesting BIP37 filtered blocks and transactions), " + "noban (do not ban for misbehavior), " + "forcerelay (relay even non-standard transactions), " + "relay (relay even in -blocksonly mode), " + "and mempool (allow requesting BIP35 mempool contents). " + "Specify multiple permissions separated by commas (default: noban,mempool,relay). Can be specified multiple times.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + + gArgs.AddArg("-whitelist=<[permissions@]IP address or network>", "Whitelist peers connecting from the given IP address (e.g. 1.2.3.4) or " + "CIDR notated network(e.g. 1.2.3.0/24). Uses same permissions as " + "-whitebind. Can be specified multiple times." , ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); g_wallet_init_interface.AddWalletOptions(); #if ENABLE_ZMQ - gArgs.AddArg("-zmqpubhashblock=<address>", "Enable publish hash block in <address>", false, OptionsCategory::ZMQ); - gArgs.AddArg("-zmqpubhashtx=<address>", "Enable publish hash transaction in <address>", false, OptionsCategory::ZMQ); - gArgs.AddArg("-zmqpubrawblock=<address>", "Enable publish raw block in <address>", false, OptionsCategory::ZMQ); - gArgs.AddArg("-zmqpubrawtx=<address>", "Enable publish raw transaction in <address>", false, OptionsCategory::ZMQ); - gArgs.AddArg("-zmqpubhashblockhwm=<n>", strprintf("Set publish hash block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), false, OptionsCategory::ZMQ); - gArgs.AddArg("-zmqpubhashtxhwm=<n>", strprintf("Set publish hash transaction outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), false, OptionsCategory::ZMQ); - gArgs.AddArg("-zmqpubrawblockhwm=<n>", strprintf("Set publish raw block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), false, OptionsCategory::ZMQ); - gArgs.AddArg("-zmqpubrawtxhwm=<n>", strprintf("Set publish raw transaction outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), false, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubhashblock=<address>", "Enable publish hash block in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubhashtx=<address>", "Enable publish hash transaction in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawblock=<address>", "Enable publish raw block in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawtx=<address>", "Enable publish raw transaction in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubhashblockhwm=<n>", strprintf("Set publish hash block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubhashtxhwm=<n>", strprintf("Set publish hash transaction outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawblockhwm=<n>", strprintf("Set publish raw block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawtxhwm=<n>", strprintf("Set publish raw transaction outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); #else hidden_args.emplace_back("-zmqpubhashblock=<address>"); hidden_args.emplace_back("-zmqpubhashtx=<address>"); @@ -479,7 +472,7 @@ void SetupServerArgs() hidden_args.emplace_back("-zmqpubrawtxhwm=<n>"); #endif - gArgs.AddArg("-checkblocks=<n>", strprintf("How many blocks to check at startup (default: %u, 0 = all)", DEFAULT_CHECKBLOCKS), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkblocks=<n>", strprintf("How many blocks to check at startup (default: %u, 0 = all)", DEFAULT_CHECKBLOCKS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-checklevel=<n>", strprintf("How thorough the block verification of -checkblocks is: " "level 0 reads the blocks from disk, " "level 1 verifies block validity, " @@ -487,68 +480,68 @@ void SetupServerArgs() "level 3 checks disconnection of tip blocks, " "and level 4 tries to reconnect the blocks, " "each level includes the checks of the previous levels " - "(0-4, default: %u)", DEFAULT_CHECKLEVEL), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-checkblockindex", strprintf("Do a full consistency check for mapBlockIndex, setBlockIndexCandidates, ::ChainActive() and mapBlocksUnlinked occasionally. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-checkmempool=<n>", strprintf("Run checks every <n> transactions (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-checkpoints", strprintf("Disable expensive verification for known chain history (default: %u)", DEFAULT_CHECKPOINTS_ENABLED), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-deprecatedrpc=<method>", "Allows deprecated RPC method(s) to be used", true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-dropmessagestest=<n>", "Randomly drop 1 of every <n> network messages", true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", DEFAULT_STOPAFTERBLOCKIMPORT), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-stopatheight", strprintf("Stop running after reaching the given height in the main chain (default: %u)", DEFAULT_STOPATHEIGHT), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-limitancestorcount=<n>", strprintf("Do not accept transactions if number of in-mempool ancestors is <n> or more (default: %u)", DEFAULT_ANCESTOR_LIMIT), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-limitancestorsize=<n>", strprintf("Do not accept transactions whose size with all in-mempool ancestors exceeds <n> kilobytes (default: %u)", DEFAULT_ANCESTOR_SIZE_LIMIT), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-limitdescendantcount=<n>", strprintf("Do not accept transactions if any ancestor would have <n> or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-limitdescendantsize=<n>", strprintf("Do not accept transactions if any ancestor would have more than <n> kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-addrmantest", "Allows to test address relay on localhost", true, OptionsCategory::DEBUG_TEST); + "(0-4, default: %u)", DEFAULT_CHECKLEVEL), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkblockindex", strprintf("Do a full consistency check for the block tree, setBlockIndexCandidates, ::ChainActive() and mapBlocksUnlinked occasionally. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkmempool=<n>", strprintf("Run checks every <n> transactions (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkpoints", strprintf("Disable expensive verification for known chain history (default: %u)", DEFAULT_CHECKPOINTS_ENABLED), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-deprecatedrpc=<method>", "Allows deprecated RPC method(s) to be used", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-dropmessagestest=<n>", "Randomly drop 1 of every <n> network messages", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", DEFAULT_STOPAFTERBLOCKIMPORT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-stopatheight", strprintf("Stop running after reaching the given height in the main chain (default: %u)", DEFAULT_STOPATHEIGHT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-limitancestorcount=<n>", strprintf("Do not accept transactions if number of in-mempool ancestors is <n> or more (default: %u)", DEFAULT_ANCESTOR_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-limitancestorsize=<n>", strprintf("Do not accept transactions whose size with all in-mempool ancestors exceeds <n> kilobytes (default: %u)", DEFAULT_ANCESTOR_SIZE_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-limitdescendantcount=<n>", strprintf("Do not accept transactions if any ancestor would have <n> or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-limitdescendantsize=<n>", strprintf("Do not accept transactions if any ancestor would have more than <n> kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-addrmantest", "Allows to test address relay on localhost", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-debug=<category>", "Output debugging information (default: -nodebug, supplying <category> is optional). " - "If <category> is not supplied or if <category> = 1, output all debugging information. <category> can be: " + ListLogCategories() + ".", false, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-debugexclude=<category>", strprintf("Exclude debugging information for a category. Can be used in conjunction with -debug=1 to output debug logs for all categories except one or more specified categories."), false, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-logips", strprintf("Include IP addresses in debug output (default: %u)", DEFAULT_LOGIPS), false, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-logtimestamps", strprintf("Prepend debug output with timestamp (default: %u)", DEFAULT_LOGTIMESTAMPS), false, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-logthreadnames", strprintf("Prepend debug output with name of the originating thread (only available on platforms supporting thread_local) (default: %u)", DEFAULT_LOGTHREADNAMES), false, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-logtimemicros", strprintf("Add microsecond precision to debug timestamps (default: %u)", DEFAULT_LOGTIMEMICROS), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-mocktime=<n>", "Replace actual time with <n> seconds since epoch (default: 0)", true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-printpriority", strprintf("Log transaction fee per kB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -daemon. To disable logging to file, set -nodebuglogfile)", false, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-shrinkdebugfile", "Shrink debug.log file on client startup (default: 1 when no -debug)", false, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-uacomment=<cmt>", "Append comment to the user agent string", false, OptionsCategory::DEBUG_TEST); + "If <category> is not supplied or if <category> = 1, output all debugging information. <category> can be: " + ListLogCategories() + ".", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-debugexclude=<category>", strprintf("Exclude debugging information for a category. Can be used in conjunction with -debug=1 to output debug logs for all categories except one or more specified categories."), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-logips", strprintf("Include IP addresses in debug output (default: %u)", DEFAULT_LOGIPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-logtimestamps", strprintf("Prepend debug output with timestamp (default: %u)", DEFAULT_LOGTIMESTAMPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-logthreadnames", strprintf("Prepend debug output with name of the originating thread (only available on platforms supporting thread_local) (default: %u)", DEFAULT_LOGTHREADNAMES), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-logtimemicros", strprintf("Add microsecond precision to debug timestamps (default: %u)", DEFAULT_LOGTIMEMICROS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-mocktime=<n>", "Replace actual time with <n> seconds since epoch (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-printpriority", strprintf("Log transaction fee per kB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -daemon. To disable logging to file, set -nodebuglogfile)", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-shrinkdebugfile", "Shrink debug.log file on client startup (default: 1 when no -debug)", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-uacomment=<cmt>", "Append comment to the user agent string", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); SetupChainParamsBaseOptions(); - gArgs.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", !testnetChainParams->RequireStandard()), true, OptionsCategory::NODE_RELAY); - gArgs.AddArg("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to define cost of relay, used for mempool limiting and BIP 125 replacement. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), true, OptionsCategory::NODE_RELAY); - gArgs.AddArg("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to define dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)), true, OptionsCategory::NODE_RELAY); - gArgs.AddArg("-bytespersigop", strprintf("Equivalent bytes per sigop in transactions for relay and mining (default: %u)", DEFAULT_BYTES_PER_SIGOP), false, OptionsCategory::NODE_RELAY); - gArgs.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), false, OptionsCategory::NODE_RELAY); - gArgs.AddArg("-datacarriersize", strprintf("Maximum size of data in data carrier transactions we relay and mine (default: %u)", MAX_OP_RETURN_RELAY), false, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", !testnetChainParams->RequireStandard()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to define cost of relay, used for mempool limiting and BIP 125 replacement. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to define dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-bytespersigop", strprintf("Equivalent bytes per sigop in transactions for relay and mining (default: %u)", DEFAULT_BYTES_PER_SIGOP), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-datacarriersize", strprintf("Maximum size of data in data carrier transactions we relay and mine (default: %u)", MAX_OP_RETURN_RELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); gArgs.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)", - CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), false, OptionsCategory::NODE_RELAY); - gArgs.AddArg("-whitelistforcerelay", strprintf("Force relay of transactions from whitelisted peers even if the transactions were already in the mempool or violate local relay policy (default: %d)", DEFAULT_WHITELISTFORCERELAY), false, OptionsCategory::NODE_RELAY); - gArgs.AddArg("-whitelistrelay", strprintf("Accept relayed transactions received from whitelisted peers even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), false, OptionsCategory::NODE_RELAY); - - - gArgs.AddArg("-blockmaxweight=<n>", strprintf("Set maximum BIP141 block weight (default: %d)", DEFAULT_BLOCK_MAX_WEIGHT), false, OptionsCategory::BLOCK_CREATION); - gArgs.AddArg("-blockmintxfee=<amt>", strprintf("Set lowest fee rate (in %s/kB) for transactions to be included in block creation. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE)), false, OptionsCategory::BLOCK_CREATION); - gArgs.AddArg("-blockversion=<n>", "Override block version to test forking scenarios", true, OptionsCategory::BLOCK_CREATION); - - gArgs.AddArg("-rest", strprintf("Accept public REST requests (default: %u)", DEFAULT_REST_ENABLE), false, OptionsCategory::RPC); - gArgs.AddArg("-rpcallowip=<ip>", "Allow JSON-RPC connections from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times", false, OptionsCategory::RPC); - gArgs.AddArg("-rpcauth=<userpw>", "Username and HMAC-SHA-256 hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcauth. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", false, OptionsCategory::RPC); - gArgs.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost)", false, OptionsCategory::RPC); - gArgs.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", false, OptionsCategory::RPC); - gArgs.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", false, OptionsCategory::RPC); - gArgs.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), false, OptionsCategory::RPC); - gArgs.AddArg("-rpcserialversion", strprintf("Sets the serialization of raw transaction or block hex returned in non-verbose mode, non-segwit(0) or segwit(1) (default: %d)", DEFAULT_RPC_SERIALIZE_VERSION), false, OptionsCategory::RPC); - gArgs.AddArg("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), true, OptionsCategory::RPC); - gArgs.AddArg("-rpcthreads=<n>", strprintf("Set the number of threads to service RPC calls (default: %d)", DEFAULT_HTTP_THREADS), false, OptionsCategory::RPC); - gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", false, OptionsCategory::RPC); - gArgs.AddArg("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), true, OptionsCategory::RPC); - gArgs.AddArg("-server", "Accept command line and JSON-RPC commands", false, OptionsCategory::RPC); + CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-whitelistforcerelay", strprintf("Add 'forcerelay' permission to whitelisted inbound peers with default permissions. This will relay transactions even if the transactions were already in the mempool or violate local relay policy. (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-whitelistrelay", strprintf("Add 'relay' permission to whitelisted inbound peers with default permissions. The will accept relayed transactions even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + + + gArgs.AddArg("-blockmaxweight=<n>", strprintf("Set maximum BIP141 block weight (default: %d)", DEFAULT_BLOCK_MAX_WEIGHT), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); + gArgs.AddArg("-blockmintxfee=<amt>", strprintf("Set lowest fee rate (in %s/kB) for transactions to be included in block creation. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); + gArgs.AddArg("-blockversion=<n>", "Override block version to test forking scenarios", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::BLOCK_CREATION); + + gArgs.AddArg("-rest", strprintf("Accept public REST requests (default: %u)", DEFAULT_REST_ENABLE), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcallowip=<ip>", "Allow JSON-RPC connections from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcauth=<userpw>", "Username and HMAC-SHA-256 hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcauth. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC); + gArgs.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC); + gArgs.AddArg("-rpcserialversion", strprintf("Sets the serialization of raw transaction or block hex returned in non-verbose mode, non-segwit(0) or segwit(1) (default: %d)", DEFAULT_RPC_SERIALIZE_VERSION), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); + gArgs.AddArg("-rpcthreads=<n>", strprintf("Set the number of threads to service RPC calls (default: %d)", DEFAULT_HTTP_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + gArgs.AddArg("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); + gArgs.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); #if HAVE_DECL_DAEMON - gArgs.AddArg("-daemon", "Run in the background as a daemon and accept commands", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-daemon", "Run in the background as a daemon and accept commands", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #else hidden_args.emplace_back("-daemon"); #endif @@ -562,23 +555,24 @@ std::string LicenseInfo() const std::string URL_SOURCE_CODE = "<https://github.com/bitcoin/bitcoin>"; const std::string URL_WEBSITE = "<https://bitcoincore.org>"; - return CopyrightHolders(strprintf(_("Copyright (C) %i-%i"), 2009, COPYRIGHT_YEAR) + " ") + "\n" + + return CopyrightHolders(strprintf(_("Copyright (C) %i-%i").translated, 2009, COPYRIGHT_YEAR) + " ") + "\n" + "\n" + strprintf(_("Please contribute if you find %s useful. " - "Visit %s for further information about the software."), + "Visit %s for further information about the software.").translated, PACKAGE_NAME, URL_WEBSITE) + "\n" + - strprintf(_("The source code is available from %s."), + strprintf(_("The source code is available from %s.").translated, URL_SOURCE_CODE) + "\n" + "\n" + - _("This is experimental software.") + "\n" + - strprintf(_("Distributed under the MIT software license, see the accompanying file %s or %s"), "COPYING", "<https://opensource.org/licenses/MIT>") + "\n" + + _("This is experimental software.").translated + "\n" + + strprintf(_("Distributed under the MIT software license, see the accompanying file %s or %s").translated, "COPYING", "<https://opensource.org/licenses/MIT>") + "\n" + "\n" + - strprintf(_("This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit %s and cryptographic software written by Eric Young and UPnP software written by Thomas Bernard."), "<https://www.openssl.org>") + + strprintf(_("This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit %s and cryptographic software written by Eric Young and UPnP software written by Thomas Bernard.").translated, "<https://www.openssl.org>") + "\n"; } +#if HAVE_SYSTEM static void BlockNotifyCallback(bool initialSync, const CBlockIndex *pBlockIndex) { if (initialSync || !pBlockIndex) @@ -591,6 +585,7 @@ static void BlockNotifyCallback(bool initialSync, const CBlockIndex *pBlockIndex t.detach(); // thread runs free } } +#endif static bool fHaveGenesis = false; static Mutex g_genesis_wait_mutex; @@ -837,11 +832,6 @@ void InitParameterInteraction() } } -static std::string ResolveErrMsg(const char * const optname, const std::string& strBind) -{ - return strprintf(_("Cannot resolve -%s address: '%s'"), optname, strBind); -} - /** * Initialize global loggers. * @@ -945,16 +935,16 @@ bool AppInitParameterInteraction() // on the command line or in this network's section of the config file. std::string network = gArgs.GetChainName(); for (const auto& arg : gArgs.GetUnsuitableSectionOnlyArgs()) { - return InitError(strprintf(_("Config setting for %s only applied on %s network when in [%s] section."), arg, network, network)); + return InitError(strprintf(_("Config setting for %s only applied on %s network when in [%s] section.").translated, arg, network, network)); } // Warn if unrecognized section name are present in the config file. for (const auto& section : gArgs.GetUnrecognizedSections()) { - InitWarning(strprintf("%s:%i " + _("Section [%s] is not recognized."), section.m_file, section.m_line, section.m_name)); + InitWarning(strprintf("%s:%i " + _("Section [%s] is not recognized.").translated, section.m_file, section.m_line, section.m_name)); } if (!fs::is_directory(GetBlocksDir())) { - return InitError(strprintf(_("Specified blocks directory \"%s\" does not exist."), gArgs.GetArg("-blocksdir", "").c_str())); + return InitError(strprintf(_("Specified blocks directory \"%s\" does not exist.").translated, gArgs.GetArg("-blocksdir", "").c_str())); } // parse and validate enabled filter types @@ -967,7 +957,7 @@ bool AppInitParameterInteraction() for (const auto& name : names) { BlockFilterType filter_type; if (!BlockFilterTypeByName(name, filter_type)) { - return InitError(strprintf(_("Unknown -blockfilterindex value %s."), name)); + return InitError(strprintf(_("Unknown -blockfilterindex value %s.").translated, name)); } g_enabled_filter_types.push_back(filter_type); } @@ -976,9 +966,9 @@ bool AppInitParameterInteraction() // if using block pruning, then disallow txindex if (gArgs.GetArg("-prune", 0)) { if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) - return InitError(_("Prune mode is incompatible with -txindex.")); + return InitError(_("Prune mode is incompatible with -txindex.").translated); if (!g_enabled_filter_types.empty()) { - return InitError(_("Prune mode is incompatible with -blockfilterindex.")); + return InitError(_("Prune mode is incompatible with -blockfilterindex.").translated); } } @@ -1003,11 +993,11 @@ bool AppInitParameterInteraction() #endif nMaxConnections = std::max(std::min<int>(nMaxConnections, fd_max - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS), 0); if (nFD < MIN_CORE_FILEDESCRIPTORS) - return InitError(_("Not enough file descriptors available.")); + return InitError(_("Not enough file descriptors available.").translated); nMaxConnections = std::min(nFD - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS, nMaxConnections); if (nMaxConnections < nUserMaxConnections) - InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, because of system limitations."), nUserMaxConnections, nMaxConnections)); + InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, because of system limitations.").translated, nUserMaxConnections, nMaxConnections)); // ********************************************************* Step 3: parameter-to-internal-flags if (gArgs.IsArgSet("-debug")) { @@ -1018,7 +1008,7 @@ bool AppInitParameterInteraction() [](std::string cat){return cat == "0" || cat == "none";})) { for (const auto& cat : categories) { if (!LogInstance().EnableCategory(cat)) { - InitWarning(strprintf(_("Unsupported logging category %s=%s."), "-debug", cat)); + InitWarning(strprintf(_("Unsupported logging category %s=%s.").translated, "-debug", cat)); } } } @@ -1027,7 +1017,7 @@ bool AppInitParameterInteraction() // Now remove the logging categories which were explicitly excluded for (const std::string& cat : gArgs.GetArgs("-debugexclude")) { if (!LogInstance().DisableCategory(cat)) { - InitWarning(strprintf(_("Unsupported logging category %s=%s."), "-debugexclude", cat)); + InitWarning(strprintf(_("Unsupported logging category %s=%s.").translated, "-debugexclude", cat)); } } @@ -1063,14 +1053,14 @@ bool AppInitParameterInteraction() int64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; int64_t nMempoolSizeMin = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000 * 40; if (nMempoolSizeMax < 0 || nMempoolSizeMax < nMempoolSizeMin) - return InitError(strprintf(_("-maxmempool must be at least %d MB"), std::ceil(nMempoolSizeMin / 1000000.0))); + return InitError(strprintf(_("-maxmempool must be at least %d MB").translated, std::ceil(nMempoolSizeMin / 1000000.0))); // incremental relay fee sets the minimum feerate increase necessary for BIP 125 replacement in the mempool // and the amount the mempool min fee increases above the feerate of txs evicted due to mempool limiting. if (gArgs.IsArgSet("-incrementalrelayfee")) { CAmount n = 0; if (!ParseMoney(gArgs.GetArg("-incrementalrelayfee", ""), n)) - return InitError(AmountErrMsg("incrementalrelayfee", gArgs.GetArg("-incrementalrelayfee", ""))); + return InitError(AmountErrMsg("incrementalrelayfee", gArgs.GetArg("-incrementalrelayfee", "")).translated); incrementalRelayFee = CFeeRate(n); } @@ -1086,7 +1076,7 @@ bool AppInitParameterInteraction() // block pruning; get the amount of disk space (in MiB) to allot for block & undo files int64_t nPruneArg = gArgs.GetArg("-prune", 0); if (nPruneArg < 0) { - return InitError(_("Prune cannot be configured with a negative value.")); + return InitError(_("Prune cannot be configured with a negative value.").translated); } nPruneTarget = (uint64_t) nPruneArg * 1024 * 1024; if (nPruneArg == 1) { // manual pruning: -prune=1 @@ -1095,7 +1085,7 @@ bool AppInitParameterInteraction() fPruneMode = true; } else if (nPruneTarget) { if (nPruneTarget < MIN_DISK_SPACE_FOR_BLOCK_FILES) { - return InitError(strprintf(_("Prune configured below the minimum of %d MiB. Please use a higher number."), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024)); + return InitError(strprintf(_("Prune configured below the minimum of %d MiB. Please use a higher number.").translated, MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024)); } LogPrintf("Prune configured to target %u MiB on disk for block and undo files.\n", nPruneTarget / 1024 / 1024); fPruneMode = true; @@ -1114,7 +1104,7 @@ bool AppInitParameterInteraction() if (gArgs.IsArgSet("-minrelaytxfee")) { CAmount n = 0; if (!ParseMoney(gArgs.GetArg("-minrelaytxfee", ""), n)) { - return InitError(AmountErrMsg("minrelaytxfee", gArgs.GetArg("-minrelaytxfee", ""))); + return InitError(AmountErrMsg("minrelaytxfee", gArgs.GetArg("-minrelaytxfee", "")).translated); } // High fee check is done afterward in WalletParameterInteraction() ::minRelayTxFee = CFeeRate(n); @@ -1130,7 +1120,7 @@ bool AppInitParameterInteraction() { CAmount n = 0; if (!ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n)) - return InitError(AmountErrMsg("blockmintxfee", gArgs.GetArg("-blockmintxfee", ""))); + return InitError(AmountErrMsg("blockmintxfee", gArgs.GetArg("-blockmintxfee", "")).translated); } // Feerate used to define dust. Shouldn't be changed lightly as old @@ -1139,13 +1129,14 @@ bool AppInitParameterInteraction() { CAmount n = 0; if (!ParseMoney(gArgs.GetArg("-dustrelayfee", ""), n)) - return InitError(AmountErrMsg("dustrelayfee", gArgs.GetArg("-dustrelayfee", ""))); + return InitError(AmountErrMsg("dustrelayfee", gArgs.GetArg("-dustrelayfee", "")).translated); dustRelayFee = CFeeRate(n); } fRequireStandard = !gArgs.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard()); - if (chainparams.RequireStandard() && !fRequireStandard) + if (!chainparams.IsTestChain() && !fRequireStandard) { return InitError(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString())); + } nBytesPerSigOp = gArgs.GetArg("-bytespersigop", nBytesPerSigOp); if (!g_wallet_init_interface.ParameterInteraction()) return false; @@ -1176,10 +1167,10 @@ static bool LockDataDirectory(bool probeOnly) // Make sure only a single Bitcoin process is using the data directory. fs::path datadir = GetDataDir(); if (!DirIsWritable(datadir)) { - return InitError(strprintf(_("Cannot write to data directory '%s'; check permissions."), datadir.string())); + return InitError(strprintf(_("Cannot write to data directory '%s'; check permissions.").translated, datadir.string())); } if (!LockDirectory(datadir, ".lock", probeOnly)) { - return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running."), datadir.string(), _(PACKAGE_NAME))); + return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running.").translated, datadir.string(), PACKAGE_NAME)); } return true; } @@ -1197,7 +1188,7 @@ bool AppInitSanityChecks() // Sanity check if (!InitSanityCheck()) - return InitError(strprintf(_("Initialization sanity check failed. %s is shutting down."), _(PACKAGE_NAME))); + return InitError(strprintf(_("Initialization sanity check failed. %s is shutting down.").translated, PACKAGE_NAME)); // Probe the data directory lock to give an early error message, if possible // We cannot hold the data directory lock here, as the forking for daemon() hasn't yet happened, @@ -1248,7 +1239,7 @@ bool AppInitMain(InitInterfaces& interfaces) LogPrintf("Config file: %s\n", config_file_path.string()); } else if (gArgs.IsArgSet("-conf")) { // Warn if no conf file exists at path provided by user - InitWarning(strprintf(_("The specified config file %s does not exist\n"), config_file_path.string())); + InitWarning(strprintf(_("The specified config file %s does not exist\n").translated, config_file_path.string())); } else { // Not categorizing as "Warning" because it's the default behavior LogPrintf("Config file: %s (not found, skipping)\n", config_file_path.string()); @@ -1308,7 +1299,7 @@ bool AppInitMain(InitInterfaces& interfaces) { uiInterface.InitMessage_connect(SetRPCWarmupStatus); if (!AppInitServers()) - return InitError(_("Unable to start HTTP server. See debug log for details.")); + return InitError(_("Unable to start HTTP server. See debug log for details.").translated); } // ********************************************************* Step 5: verify wallet database integrity @@ -1336,12 +1327,12 @@ bool AppInitMain(InitInterfaces& interfaces) std::vector<std::string> uacomments; for (const std::string& cmt : gArgs.GetArgs("-uacomment")) { if (cmt != SanitizeString(cmt, SAFE_CHARS_UA_COMMENT)) - return InitError(strprintf(_("User Agent comment (%s) contains unsafe characters."), cmt)); + return InitError(strprintf(_("User Agent comment (%s) contains unsafe characters.").translated, cmt)); uacomments.push_back(cmt); } strSubVersion = FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, uacomments); if (strSubVersion.size() > MAX_SUBVERSION_LENGTH) { - return InitError(strprintf(_("Total length of network version string (%i) exceeds maximum length (%i). Reduce the number or size of uacomments."), + return InitError(strprintf(_("Total length of network version string (%i) exceeds maximum length (%i). Reduce the number or size of uacomments.").translated, strSubVersion.size(), MAX_SUBVERSION_LENGTH)); } @@ -1350,7 +1341,7 @@ bool AppInitMain(InitInterfaces& interfaces) for (const std::string& snet : gArgs.GetArgs("-onlynet")) { enum Network net = ParseNetwork(snet); if (net == NET_UNROUTABLE) - return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'"), snet)); + return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'").translated, snet)); nets.insert(net); } for (int n = 0; n < NET_MAX; n++) { @@ -1371,12 +1362,12 @@ bool AppInitMain(InitInterfaces& interfaces) if (proxyArg != "" && proxyArg != "0") { CService proxyAddr; if (!Lookup(proxyArg.c_str(), proxyAddr, 9050, fNameLookup)) { - return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg)); + return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'").translated, proxyArg)); } proxyType addrProxy = proxyType(proxyAddr, proxyRandomize); if (!addrProxy.IsValid()) - return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg)); + return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'").translated, proxyArg)); SetProxy(NET_IPV4, addrProxy); SetProxy(NET_IPV6, addrProxy); @@ -1395,11 +1386,11 @@ bool AppInitMain(InitInterfaces& interfaces) } else { CService onionProxy; if (!Lookup(onionArg.c_str(), onionProxy, 9050, fNameLookup)) { - return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg)); + return InitError(strprintf(_("Invalid -onion address or hostname: '%s'").translated, onionArg)); } proxyType addrOnion = proxyType(onionProxy, proxyRandomize); if (!addrOnion.IsValid()) - return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg)); + return InitError(strprintf(_("Invalid -onion address or hostname: '%s'").translated, onionArg)); SetProxy(NET_ONION, addrOnion); SetReachable(NET_ONION, true); } @@ -1474,17 +1465,17 @@ bool AppInitMain(InitInterfaces& interfaces) bool fReset = fReindex; std::string strLoadError; - uiInterface.InitMessage(_("Loading block index...")); + uiInterface.InitMessage(_("Loading block index...").translated); do { const int64_t load_block_index_start_time = GetTimeMillis(); bool is_coinsview_empty; try { LOCK(cs_main); + // This statement makes ::ChainstateActive() usable. + g_chainstate = MakeUnique<CChainState>(); UnloadBlockIndex(); - pcoinsTip.reset(); - pcoinsdbview.reset(); - pcoinscatcher.reset(); + // new CBlockTreeDB tries to delete the existing file, which // fails if it's still open from the previous loop. Close it first: pblocktree.reset(); @@ -1505,20 +1496,21 @@ bool AppInitMain(InitInterfaces& interfaces) // From here on out fReindex and fReset mean something different! if (!LoadBlockIndex(chainparams)) { if (ShutdownRequested()) break; - strLoadError = _("Error loading block database"); + strLoadError = _("Error loading block database").translated; break; } // If the loaded chain has a wrong genesis, bail out immediately // (we're likely using a testnet datadir, or the other way around). - if (!mapBlockIndex.empty() && !LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { - return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); + if (!::BlockIndex().empty() && + !LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { + return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?").translated); } // Check for changed -prune state. What we are concerned about is a user who has pruned blocks // in the past, but is now trying to run unpruned. if (fHavePruned && !fPruneMode) { - strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain"); + strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain").translated; break; } @@ -1527,54 +1519,64 @@ bool AppInitMain(InitInterfaces& interfaces) // (otherwise we use the one already on disk). // This is called again in ThreadImport after the reindex completes. if (!fReindex && !LoadGenesisBlock(chainparams)) { - strLoadError = _("Error initializing block database"); + strLoadError = _("Error initializing block database").translated; break; } // At this point we're either in reindex or we've loaded a useful - // block tree into mapBlockIndex! + // block tree into BlockIndex()! - pcoinsdbview.reset(new CCoinsViewDB(nCoinDBCache, false, fReset || fReindexChainState)); - pcoinscatcher.reset(new CCoinsViewErrorCatcher(pcoinsdbview.get())); + ::ChainstateActive().InitCoinsDB( + /* cache_size_bytes */ nCoinDBCache, + /* in_memory */ false, + /* should_wipe */ fReset || fReindexChainState); + + ::ChainstateActive().CoinsErrorCatcher().AddReadErrCallback([]() { + uiInterface.ThreadSafeMessageBox( + _("Error reading from database, shutting down.").translated, + "", CClientUIInterface::MSG_ERROR); + }); // If necessary, upgrade from older database format. // This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate - if (!pcoinsdbview->Upgrade()) { - strLoadError = _("Error upgrading chainstate database"); + if (!::ChainstateActive().CoinsDB().Upgrade()) { + strLoadError = _("Error upgrading chainstate database").translated; break; } // ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate - if (!ReplayBlocks(chainparams, pcoinsdbview.get())) { - strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate."); + if (!ReplayBlocks(chainparams, &::ChainstateActive().CoinsDB())) { + strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.").translated; break; } // The on-disk coinsdb is now in a good state, create the cache - pcoinsTip.reset(new CCoinsViewCache(pcoinscatcher.get())); + ::ChainstateActive().InitCoinsCache(); + assert(::ChainstateActive().CanFlushToDisk()); - is_coinsview_empty = fReset || fReindexChainState || pcoinsTip->GetBestBlock().IsNull(); + is_coinsview_empty = fReset || fReindexChainState || + ::ChainstateActive().CoinsTip().GetBestBlock().IsNull(); if (!is_coinsview_empty) { - // LoadChainTip sets ::ChainActive() based on pcoinsTip's best block + // LoadChainTip sets ::ChainActive() based on CoinsTip()'s best block if (!LoadChainTip(chainparams)) { - strLoadError = _("Error initializing block database"); + strLoadError = _("Error initializing block database").translated; break; } assert(::ChainActive().Tip() != nullptr); } } catch (const std::exception& e) { LogPrintf("%s\n", e.what()); - strLoadError = _("Error opening block database"); + strLoadError = _("Error opening block database").translated; break; } if (!fReset) { // Note that RewindBlockIndex MUST run even if we're about to -reindex-chainstate. // It both disconnects blocks based on ::ChainActive(), and drops block data in - // mapBlockIndex based on lack of available witness data. - uiInterface.InitMessage(_("Rewinding blocks...")); + // BlockIndex() based on lack of available witness data. + uiInterface.InitMessage(_("Rewinding blocks...").translated); if (!RewindBlockIndex(chainparams)) { - strLoadError = _("Unable to rewind the database to a pre-fork state. You will need to redownload the blockchain"); + strLoadError = _("Unable to rewind the database to a pre-fork state. You will need to redownload the blockchain").translated; break; } } @@ -1582,7 +1584,7 @@ bool AppInitMain(InitInterfaces& interfaces) try { LOCK(cs_main); if (!is_coinsview_empty) { - uiInterface.InitMessage(_("Verifying blocks...")); + uiInterface.InitMessage(_("Verifying blocks...").translated); if (fHavePruned && gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) { LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n", MIN_BLOCKS_TO_KEEP); @@ -1593,19 +1595,19 @@ bool AppInitMain(InitInterfaces& interfaces) if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) { strLoadError = _("The block database contains a block which appears to be from the future. " "This may be due to your computer's date and time being set incorrectly. " - "Only rebuild the block database if you are sure that your computer's date and time are correct"); + "Only rebuild the block database if you are sure that your computer's date and time are correct").translated; break; } - if (!CVerifyDB().VerifyDB(chainparams, pcoinsdbview.get(), gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL), + if (!CVerifyDB().VerifyDB(chainparams, &::ChainstateActive().CoinsDB(), gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL), gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) { - strLoadError = _("Corrupted block database detected"); + strLoadError = _("Corrupted block database detected").translated; break; } } } catch (const std::exception& e) { LogPrintf("%s\n", e.what()); - strLoadError = _("Error opening block database"); + strLoadError = _("Error opening block database").translated; break; } @@ -1617,7 +1619,7 @@ bool AppInitMain(InitInterfaces& interfaces) // first suggest a reindex if (!fReset) { bool fRet = uiInterface.ThreadSafeQuestion( - strLoadError + ".\n\n" + _("Do you want to rebuild the block database now?"), + strLoadError + ".\n\n" + _("Do you want to rebuild the block database now?").translated, strLoadError + ".\nPlease restart with -reindex or -reindex-chainstate to recover.", "", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT); if (fRet) { @@ -1674,28 +1676,25 @@ bool AppInitMain(InitInterfaces& interfaces) LogPrintf("Unsetting NODE_NETWORK on prune mode\n"); nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK); if (!fReindex) { - uiInterface.InitMessage(_("Pruning blockstore...")); + uiInterface.InitMessage(_("Pruning blockstore...").translated); ::ChainstateActive().PruneAndFlush(); } } - if (chainparams.GetConsensus().vDeployments[Consensus::DEPLOYMENT_SEGWIT].nTimeout != 0) { - // Only advertise witness capabilities if they have a reasonable start time. - // This allows us to have the code merged without a defined softfork, by setting its - // end time to 0. - // Note that setting NODE_WITNESS is never required: the only downside from not - // doing so is that after activation, no upgraded nodes will fetch from you. + if (chainparams.GetConsensus().SegwitHeight != std::numeric_limits<int>::max()) { + // Advertise witness capabilities. + // The option to not set NODE_WITNESS is only used in the tests and should be removed. nLocalServices = ServiceFlags(nLocalServices | NODE_WITNESS); } // ********************************************************* Step 11: import blocks if (!CheckDiskSpace(GetDataDir())) { - InitError(strprintf(_("Error: Disk space is low for %s"), GetDataDir())); + InitError(strprintf(_("Error: Disk space is low for %s").translated, GetDataDir())); return false; } if (!CheckDiskSpace(GetBlocksDir())) { - InitError(strprintf(_("Error: Disk space is low for %s"), GetBlocksDir())); + InitError(strprintf(_("Error: Disk space is low for %s").translated, GetBlocksDir())); return false; } @@ -1708,8 +1707,10 @@ bool AppInitMain(InitInterfaces& interfaces) fHaveGenesis = true; } +#if HAVE_SYSTEM if (gArgs.IsArgSet("-blocknotify")) uiInterface.NotifyBlockTip_connect(BlockNotifyCallback); +#endif std::vector<fs::path> vImportFiles; for (const std::string& strFile : gArgs.GetArgs("-loadblock")) { @@ -1741,7 +1742,7 @@ bool AppInitMain(InitInterfaces& interfaces) //// debug print { LOCK(cs_main); - LogPrintf("mapBlockIndex.size() = %u\n", mapBlockIndex.size()); + LogPrintf("block tree size = %u\n", ::BlockIndex().size()); chain_active_height = ::ChainActive().Height(); } LogPrintf("nBestHeight = %d\n", chain_active_height); @@ -1759,7 +1760,8 @@ bool AppInitMain(InitInterfaces& interfaces) CConnman::Options connOptions; connOptions.nLocalServices = nLocalServices; connOptions.nMaxConnections = nMaxConnections; - connOptions.nMaxOutbound = std::min(MAX_OUTBOUND_CONNECTIONS, connOptions.nMaxConnections); + connOptions.m_max_outbound_full_relay = std::min(MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, connOptions.nMaxConnections); + connOptions.m_max_outbound_block_relay = std::min(MAX_BLOCKS_ONLY_CONNECTIONS, connOptions.nMaxConnections-connOptions.m_max_outbound_full_relay); connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS; connOptions.nMaxFeeler = 1; connOptions.nBestHeight = chain_active_height; @@ -1782,21 +1784,16 @@ bool AppInitMain(InitInterfaces& interfaces) connOptions.vBinds.push_back(addrBind); } for (const std::string& strBind : gArgs.GetArgs("-whitebind")) { - CService addrBind; - if (!Lookup(strBind.c_str(), addrBind, 0, false)) { - return InitError(ResolveErrMsg("whitebind", strBind)); - } - if (addrBind.GetPort() == 0) { - return InitError(strprintf(_("Need to specify a port with -whitebind: '%s'"), strBind)); - } - connOptions.vWhiteBinds.push_back(addrBind); + NetWhitebindPermissions whitebind; + std::string error; + if (!NetWhitebindPermissions::TryParse(strBind, whitebind, error)) return InitError(error); + connOptions.vWhiteBinds.push_back(whitebind); } for (const auto& net : gArgs.GetArgs("-whitelist")) { - CSubNet subnet; - LookupSubNet(net.c_str(), subnet); - if (!subnet.IsValid()) - return InitError(strprintf(_("Invalid netmask specified in -whitelist: '%s'"), net)); + NetWhitelistPermissions subnet; + std::string error; + if (!NetWhitelistPermissions::TryParse(net, subnet, error)) return InitError(error); connOptions.vWhitelistedRange.push_back(subnet); } @@ -1817,7 +1814,7 @@ bool AppInitMain(InitInterfaces& interfaces) // ********************************************************* Step 13: finished SetRPCWarmupFinished(); - uiInterface.InitMessage(_("Done loading")); + uiInterface.InitMessage(_("Done loading").translated); for (const auto& client : interfaces.chain_clients) { client->start(scheduler); diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index 161dd01ffe..b8b9ecded9 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -9,7 +9,9 @@ #include <interfaces/handler.h> #include <interfaces/wallet.h> #include <net.h> +#include <net_processing.h> #include <node/coin.h> +#include <node/transaction.h> #include <policy/fees.h> #include <policy/policy.h> #include <policy/rbf.h> @@ -149,12 +151,6 @@ class LockImpl : public Chain::Lock, public UniqueLock<CCriticalSection> LockAssertion lock(::cs_main); return CheckFinalTx(tx); } - bool submitToMemoryPool(const CTransactionRef& tx, CAmount absurd_fee, CValidationState& state) override - { - LockAssertion lock(::cs_main); - return AcceptToMemoryPool(::mempool, state, tx, nullptr /* missing inputs */, nullptr /* txn replaced */, - false /* bypass limits */, absurd_fee); - } using UniqueLock::UniqueLock; }; @@ -205,7 +201,7 @@ public: class RpcHandlerImpl : public Handler { public: - RpcHandlerImpl(const CRPCCommand& command) : m_command(command), m_wrapped_command(&command) + explicit RpcHandlerImpl(const CRPCCommand& command) : m_command(command), m_wrapped_command(&command) { m_command.actor = [this](const JSONRPCRequest& request, UniValue& result, bool last_handler) { if (!m_wrapped_command) return false; @@ -290,10 +286,13 @@ public: auto it = ::mempool.GetIter(txid); return it && (*it)->GetCountWithDescendants() > 1; } - void relayTransaction(const uint256& txid) override + bool broadcastTransaction(const CTransactionRef& tx, std::string& err_string, const CAmount& max_tx_fee, bool relay) override { - CInv inv(MSG_TX, txid); - g_connman->ForEachNode([&inv](CNode* node) { node->PushInventory(inv); }); + const TransactionError err = BroadcastTransaction(tx, err_string, max_tx_fee, relay, /*wait_callback*/ false); + // Chain clients only care about failures to accept the tx to the mempool. Disregard non-mempool related failures. + // Note: this will need to be updated if BroadcastTransactions() is updated to return other non-mempool failures + // that Chain clients do not need to know about. + return TransactionError::OK == err; } void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) override { @@ -333,7 +332,6 @@ public: LOCK(cs_main); return ::fHavePruned; } - bool p2pEnabled() override { return g_connman != nullptr; } bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !isInitialBlockDownload(); } bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); } bool shutdownRequested() override { return ShutdownRequested(); } diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index e675defd47..da670a3370 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -43,10 +43,6 @@ class Wallet; //! asynchronously //! (https://github.com/bitcoin/bitcoin/pull/10973#issuecomment-380101269). //! -//! * The relayTransactions() and submitToMemoryPool() methods could be replaced -//! with a higher-level broadcastTransaction method -//! (https://github.com/bitcoin/bitcoin/pull/14978#issuecomment-459373984). -//! //! * The initMessages() and loadWallet() methods which the wallet uses to send //! notifications to the GUI should go away when GUI and wallet can directly //! communicate with each other without going through the node @@ -127,11 +123,6 @@ public: //! Check if transaction will be final given chain height current time. virtual bool checkFinalTx(const CTransaction& tx) = 0; - - //! Add transaction to memory pool if the transaction fee is below the - //! amount specified by absurd_fee. Returns false if the transaction - //! could not be added due to the fee or for another reason. - virtual bool submitToMemoryPool(const CTransactionRef& tx, CAmount absurd_fee, CValidationState& state) = 0; }; //! Return Lock interface. Chain is locked when this is called, and @@ -164,8 +155,10 @@ public: //! Check if transaction has descendants in mempool. virtual bool hasDescendantsInMempool(const uint256& txid) = 0; - //! Relay transaction. - virtual void relayTransaction(const uint256& txid) = 0; + //! Transaction is added to memory pool, if the transaction fee is below the + //! amount specified by max_tx_fee, and broadcast to all peers if relay is set to true. + //! Return false if the transaction could not be added due to the fee or for another reason. + virtual bool broadcastTransaction(const CTransactionRef& tx, std::string& err_string, const CAmount& max_tx_fee, bool relay) = 0; //! Calculate mempool ancestor and descendant counts for the given transaction. virtual void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) = 0; @@ -194,9 +187,6 @@ public: //! Check if any block has been pruned. virtual bool havePruned() = 0; - //! Check if p2p enabled. - virtual bool p2pEnabled() = 0; - //! Check if the node is ready to broadcast transactions. virtual bool isReadyToBroadcast() = 0; diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index 584d218dba..ccafc3ac8c 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -24,6 +24,7 @@ #include <primitives/block.h> #include <rpc/server.h> #include <shutdown.h> +#include <support/allocators/secure.h> #include <sync.h> #include <txmempool.h> #include <ui_interface.h> @@ -43,6 +44,7 @@ fs::path GetWalletDir(); std::vector<fs::path> ListWalletDir(); std::vector<std::shared_ptr<CWallet>> GetWallets(); std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::string& warning); +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::shared_ptr<CWallet>& result); namespace interfaces { @@ -54,6 +56,7 @@ class NodeImpl : public Node { public: NodeImpl() { m_interfaces.chain = MakeChain(); } + void initError(const std::string& message) override { InitError(message); } bool parseParameters(int argc, const char* const argv[], std::string& error) override { return gArgs.ParseParameters(argc, argv, error); @@ -197,6 +200,7 @@ public: return GuessVerificationProgress(Params().TxData(), tip); } bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); } + bool isAddressTypeSet() override { return !::gArgs.GetArg("-addresstype", "").empty(); } bool getReindex() override { return ::fReindex; } bool getImporting() override { return ::fImporting; } void setNetworkActive(bool active) override @@ -230,7 +234,7 @@ public: bool getUnspentOutput(const COutPoint& output, Coin& coin) override { LOCK(::cs_main); - return ::pcoinsTip->GetCoin(output, coin); + return ::ChainstateActive().CoinsTip().GetCoin(output, coin); } std::string getWalletDir() override { @@ -256,6 +260,13 @@ public: { return MakeWallet(LoadWallet(*m_interfaces.chain, name, error, warning)); } + WalletCreationStatus createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::unique_ptr<Wallet>& result) override + { + std::shared_ptr<CWallet> wallet; + WalletCreationStatus status = CreateWallet(*m_interfaces.chain, passphrase, wallet_creation_flags, name, error, warning, wallet); + result = MakeWallet(wallet); + return status; + } std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) override { return MakeHandler(::uiInterface.InitMessage_connect(fn)); diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 1ccd2a31b7..e8c3d0b721 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -9,6 +9,7 @@ #include <amount.h> // For CAmount #include <net.h> // For CConnman::NumConnections #include <netaddress.h> // For Network +#include <support/allocators/secure.h> // For SecureString #include <functional> #include <memory> @@ -27,6 +28,7 @@ class RPCTimerInterface; class UniValue; class proxyType; struct CNodeStateStats; +enum class WalletCreationStatus; namespace interfaces { class Handler; @@ -38,6 +40,9 @@ class Node public: virtual ~Node() {} + //! Send init error. + virtual void initError(const std::string& message) = 0; + //! Set command line arguments. virtual bool parseParameters(int argc, const char* const argv[], std::string& error) = 0; @@ -147,6 +152,9 @@ public: //! Is initial block download. virtual bool isInitialBlockDownload() = 0; + //! Is -addresstype set. + virtual bool isAddressTypeSet() = 0; + //! Get reindex. virtual bool getReindex() = 0; @@ -194,6 +202,9 @@ public: //! with handleLoadWallet. virtual std::unique_ptr<Wallet> loadWallet(const std::string& name, std::string& error, std::string& warning) = 0; + //! Create a wallet from file + virtual WalletCreationStatus createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::unique_ptr<Wallet>& result) = 0; + //! Register handler for init messages. using InitMessageFn = std::function<void(const std::string& message)>; virtual std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) = 0; diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 34c982e1e6..0c8d92eba5 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -33,32 +33,6 @@ namespace interfaces { namespace { -class PendingWalletTxImpl : public PendingWalletTx -{ -public: - explicit PendingWalletTxImpl(CWallet& wallet) : m_wallet(wallet), m_key(&wallet) {} - - const CTransaction& get() override { return *m_tx; } - - bool commit(WalletValueMap value_map, - WalletOrderForm order_form, - std::string& reject_reason) override - { - 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, state)) { - reject_reason = state.GetRejectReason(); - return false; - } - return true; - } - - CTransactionRef m_tx; - CWallet& m_wallet; - CReserveKey m_key; -}; - //! Construct wallet tx struct. WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, const CWalletTx& wtx) { @@ -91,7 +65,7 @@ WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, co WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx) { WalletTxStatus result; - result.block_height = locked_chain.getBlockHeight(wtx.hashBlock).get_value_or(std::numeric_limits<int>::max()); + result.block_height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock).get_value_or(std::numeric_limits<int>::max()); result.blocks_to_maturity = wtx.GetBlocksToMaturity(locked_chain); result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain); result.time_received = wtx.nTimeReceived; @@ -140,9 +114,11 @@ public: void abortRescan() override { m_wallet->AbortRescan(); } bool backupWallet(const std::string& filename) override { return m_wallet->BackupWallet(filename); } std::string getWalletName() override { return m_wallet->GetName(); } - bool getKeyFromPool(bool internal, CPubKey& pub_key) override + bool getNewDestination(const OutputType type, const std::string label, CTxDestination& dest) override { - return m_wallet->GetKeyFromPool(pub_key, internal); + LOCK(m_wallet->cs_wallet); + std::string error; + return m_wallet->GetNewDestination(type, label, dest, error); } bool getPubKey(const CKeyID& address, CPubKey& pub_key) override { return m_wallet->GetPubKey(address, pub_key); } bool getPrivKey(const CKeyID& address, CKey& key) override { return m_wallet->GetKey(address, key); } @@ -226,7 +202,7 @@ public: LOCK(m_wallet->cs_wallet); return m_wallet->ListLockedCoins(outputs); } - std::unique_ptr<PendingWalletTx> createTransaction(const std::vector<CRecipient>& recipients, + CTransactionRef createTransaction(const std::vector<CRecipient>& recipients, const CCoinControl& coin_control, bool sign, int& change_pos, @@ -235,12 +211,26 @@ public: { auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); - auto pending = MakeUnique<PendingWalletTxImpl>(*m_wallet); - if (!m_wallet->CreateTransaction(*locked_chain, recipients, pending->m_tx, pending->m_key, fee, change_pos, + CTransactionRef tx; + if (!m_wallet->CreateTransaction(*locked_chain, recipients, tx, fee, change_pos, fail_reason, coin_control, sign)) { return {}; } - return std::move(pending); + return tx; + } + bool commitTransaction(CTransactionRef tx, + WalletValueMap value_map, + WalletOrderForm order_form, + std::string& reject_reason) override + { + auto locked_chain = m_wallet->chain().lock(); + LOCK(m_wallet->cs_wallet); + CValidationState state; + if (!m_wallet->CommitTransaction(std::move(tx), std::move(value_map), std::move(order_form), state)) { + reject_reason = state.GetRejectReason(); + return false; + } + return true; } bool transactionCanBeAbandoned(const uint256& txid) override { return m_wallet->TransactionCanBeAbandoned(txid); } bool abandonTransaction(const uint256& txid) override @@ -476,7 +466,7 @@ public: } std::unique_ptr<Handler> handleStatusChanged(StatusChangedFn fn) override { - return MakeHandler(m_wallet->NotifyStatusChanged.connect([fn](CCryptoKeyStore*) { fn(); })); + return MakeHandler(m_wallet->NotifyStatusChanged.connect([fn](CWallet*) { fn(); })); } std::unique_ptr<Handler> handleAddressBookChanged(AddressBookChangedFn fn) override { diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 9c9b29a813..89e056b18b 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -34,7 +34,6 @@ struct CRecipient; namespace interfaces { class Handler; -class PendingWalletTx; struct WalletAddress; struct WalletBalances; struct WalletTx; @@ -78,8 +77,8 @@ public: //! Get wallet name. virtual std::string getWalletName() = 0; - // Get key from pool. - virtual bool getKeyFromPool(bool internal, CPubKey& pub_key) = 0; + // Get a new address. + virtual bool getNewDestination(const OutputType type, const std::string label, CTxDestination& dest) = 0; //! Get public key. virtual bool getPubKey(const CKeyID& address, CPubKey& pub_key) = 0; @@ -134,13 +133,19 @@ public: virtual void listLockedCoins(std::vector<COutPoint>& outputs) = 0; //! Create transaction. - virtual std::unique_ptr<PendingWalletTx> createTransaction(const std::vector<CRecipient>& recipients, + virtual CTransactionRef createTransaction(const std::vector<CRecipient>& recipients, const CCoinControl& coin_control, bool sign, int& change_pos, CAmount& fee, std::string& fail_reason) = 0; + //! Commit transaction. + virtual bool commitTransaction(CTransactionRef tx, + WalletValueMap value_map, + WalletOrderForm order_form, + std::string& reject_reason) = 0; + //! Return whether transaction can be abandoned. virtual bool transactionCanBeAbandoned(const uint256& txid) = 0; @@ -288,21 +293,6 @@ public: virtual std::unique_ptr<Handler> handleCanGetAddressesChanged(CanGetAddressesChangedFn fn) = 0; }; -//! Tracking object returned by CreateTransaction and passed to CommitTransaction. -class PendingWalletTx -{ -public: - virtual ~PendingWalletTx() {} - - //! Get transaction data. - virtual const CTransaction& get() = 0; - - //! Send pending transaction and commit to wallet. - virtual bool commit(WalletValueMap value_map, - WalletOrderForm order_form, - std::string& reject_reason) = 0; -}; - //! Information about one wallet address. struct WalletAddress { diff --git a/src/keystore.h b/src/keystore.h deleted file mode 100644 index 4bd99e255d..0000000000 --- a/src/keystore.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-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_KEYSTORE_H -#define BITCOIN_KEYSTORE_H - -#include <key.h> -#include <pubkey.h> -#include <script/script.h> -#include <script/sign.h> -#include <script/standard.h> -#include <sync.h> - -#include <boost/signals2/signal.hpp> - -/** A virtual base class for key stores */ -class CKeyStore : public SigningProvider -{ -public: - //! Add a key to the store. - virtual bool AddKeyPubKey(const CKey &key, const CPubKey &pubkey) =0; - - //! Check whether a key corresponding to a given address is present in the store. - virtual bool HaveKey(const CKeyID &address) const =0; - virtual std::set<CKeyID> GetKeys() const =0; - - //! Support for BIP 0013 : see https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki - virtual bool AddCScript(const CScript& redeemScript) =0; - virtual bool HaveCScript(const CScriptID &hash) const =0; - virtual std::set<CScriptID> GetCScripts() const =0; - - //! Support for Watch-only addresses - virtual bool AddWatchOnly(const CScript &dest) =0; - virtual bool RemoveWatchOnly(const CScript &dest) =0; - virtual bool HaveWatchOnly(const CScript &dest) const =0; - virtual bool HaveWatchOnly() const =0; -}; - -/** Basic key store, that keeps keys in an address->secret map */ -class CBasicKeyStore : public CKeyStore -{ -protected: - mutable CCriticalSection cs_KeyStore; - - using KeyMap = std::map<CKeyID, CKey>; - using WatchKeyMap = std::map<CKeyID, CPubKey>; - using ScriptMap = std::map<CScriptID, CScript>; - using WatchOnlySet = std::set<CScript>; - - KeyMap mapKeys GUARDED_BY(cs_KeyStore); - WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore); - ScriptMap mapScripts GUARDED_BY(cs_KeyStore); - WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore); - - void ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - -public: - bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; - bool AddKey(const CKey &key) { return AddKeyPubKey(key, key.GetPubKey()); } - bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; - bool HaveKey(const CKeyID &address) const override; - std::set<CKeyID> GetKeys() const override; - bool GetKey(const CKeyID &address, CKey &keyOut) const override; - bool AddCScript(const CScript& redeemScript) override; - bool HaveCScript(const CScriptID &hash) const override; - std::set<CScriptID> GetCScripts() const override; - bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const override; - - bool AddWatchOnly(const CScript &dest) override; - bool RemoveWatchOnly(const CScript &dest) override; - bool HaveWatchOnly(const CScript &dest) const override; - bool HaveWatchOnly() const override; -}; - -/** Return the CKeyID of the key involved in a script (if there is a unique one). */ -CKeyID GetKeyForDestination(const CKeyStore& store, const CTxDestination& dest); - -/** Checks if a CKey is in the given CKeyStore compressed or otherwise*/ -bool HaveKey(const CKeyStore& store, const CKey& key); - -#endif // BITCOIN_KEYSTORE_H diff --git a/src/net.cpp b/src/net.cpp index 75a47d7ad2..89f82aa3d2 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -15,11 +15,13 @@ #include <consensus/consensus.h> #include <crypto/common.h> #include <crypto/sha256.h> -#include <primitives/transaction.h> #include <netbase.h> +#include <net_permissions.h> +#include <primitives/transaction.h> #include <scheduler.h> #include <ui_interface.h> #include <util/strencodings.h> +#include <util/translation.h> #ifdef WIN32 #include <string.h> @@ -36,6 +38,9 @@ #include <miniupnpc/miniwget.h> #include <miniupnpc/upnpcommands.h> #include <miniupnpc/upnperrors.h> +// The minimum supported miniUPnPc API version is set to 10. This keeps compatibility +// with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. +static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed"); #endif #include <unordered_map> @@ -63,7 +68,6 @@ enum BindFlags { BF_NONE = 0, BF_EXPLICIT = (1U << 0), BF_REPORT_ERROR = (1U << 1), - BF_WHITELIST = (1U << 2), }; // The set of sockets cannot be modified while waiting @@ -348,7 +352,7 @@ static CAddress GetBindAddress(SOCKET sock) return addr_bind; } -CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection) +CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection, bool block_relay_only) { if (pszDest == nullptr) { if (IsLocal(addrConnect)) @@ -438,7 +442,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); CAddress addr_bind = GetBindAddress(hSocket); - CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false); + CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false, block_relay_only); pnode->AddRef(); return pnode; @@ -455,12 +459,10 @@ void CNode::CloseSocketDisconnect() } } -bool CConnman::IsWhitelistedRange(const CNetAddr &addr) { - for (const CSubNet& subnet : vWhitelistedRange) { - if (subnet.Match(addr)) - return true; +void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const { + for (const auto& subnet : vWhitelistedRange) { + if (subnet.m_subnet.Match(addr)) NetPermissions::AddFlag(flags, subnet.m_flags); } - return false; } std::string CNode::GetAddrName() const { @@ -497,9 +499,11 @@ void CNode::copyStats(CNodeStats &stats) X(nServices); X(addr); X(addrBind); - { - LOCK(cs_filter); - X(fRelayTxes); + if (m_tx_relay != nullptr) { + LOCK(m_tx_relay->cs_filter); + stats.fRelayTxes = m_tx_relay->fRelayTxes; + } else { + stats.fRelayTxes = false; } X(nLastSend); X(nLastRecv); @@ -524,10 +528,13 @@ void CNode::copyStats(CNodeStats &stats) X(mapRecvBytesPerMsgCmd); X(nRecvBytes); } - X(fWhitelisted); - { - LOCK(cs_feeFilter); - X(minFeeFilter); + X(m_legacyWhitelisted); + X(m_permissionFlags); + if (m_tx_relay != nullptr) { + LOCK(m_tx_relay->cs_feeFilter); + stats.minFeeFilter = m_tx_relay->minFeeFilter; + } else { + stats.minFeeFilter = 0; } // It is common for nodes with good ping times to suddenly become lagged, @@ -809,17 +816,23 @@ bool CConnman::AttemptToEvictConnection() LOCK(cs_vNodes); for (const CNode* node : vNodes) { - if (node->fWhitelisted) + if (node->HasPermission(PF_NOBAN)) continue; if (!node->fInbound) continue; if (node->fDisconnect) continue; - LOCK(node->cs_filter); + bool peer_relay_txes = false; + bool peer_filter_not_null = false; + if (node->m_tx_relay != nullptr) { + LOCK(node->m_tx_relay->cs_filter); + peer_relay_txes = node->m_tx_relay->fRelayTxes; + peer_filter_not_null = node->m_tx_relay->pfilter != nullptr; + } NodeEvictionCandidate candidate = {node->GetId(), node->nTimeConnected, node->nMinPingUsecTime, node->nLastBlockTime, node->nLastTXTime, HasAllDesirableServiceFlags(node->nServices), - node->fRelayTxes, node->pfilter != nullptr, node->addr, node->nKeyedNetGroup, + peer_relay_txes, peer_filter_not_null, node->addr, node->nKeyedNetGroup, node->m_prefer_evict}; vEvictionCandidates.push_back(candidate); } @@ -892,7 +905,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len); CAddress addr; int nInbound = 0; - int nMaxInbound = nMaxConnections - (nMaxOutbound + nMaxFeeler); + int nMaxInbound = nMaxConnections - m_max_outbound; if (hSocket != INVALID_SOCKET) { if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) { @@ -900,7 +913,19 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { } } - bool whitelisted = hListenSocket.whitelisted || IsWhitelistedRange(addr); + NetPermissionFlags permissionFlags = NetPermissionFlags::PF_NONE; + hListenSocket.AddSocketPermissionFlags(permissionFlags); + AddWhitelistPermissionFlags(permissionFlags, addr); + bool legacyWhitelisted = false; + if (NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_ISIMPLICIT)) { + NetPermissions::ClearFlag(permissionFlags, PF_ISIMPLICIT); + if (gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) NetPermissions::AddFlag(permissionFlags, PF_FORCERELAY); + if (gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) NetPermissions::AddFlag(permissionFlags, PF_RELAY); + NetPermissions::AddFlag(permissionFlags, PF_MEMPOOL); + NetPermissions::AddFlag(permissionFlags, PF_NOBAN); + legacyWhitelisted = true; + } + { LOCK(cs_vNodes); for (const CNode* pnode : vNodes) { @@ -937,7 +962,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { // Don't accept connections from banned peers, but if our inbound slots aren't almost full, accept // if the only banning reason was an automatic misbehavior ban. - if (!whitelisted && bannedlevel > ((nInbound + 1 < nMaxInbound) ? 1 : 0)) + if (!NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_NOBAN) && bannedlevel > ((nInbound + 1 < nMaxInbound) ? 1 : 0)) { LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToString()); CloseSocket(hSocket); @@ -958,9 +983,15 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); CAddress addr_bind = GetBindAddress(hSocket); - CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true); + ServiceFlags nodeServices = nLocalServices; + if (NetPermissions::HasFlag(permissionFlags, PF_BLOOMFILTER)) { + nodeServices = static_cast<ServiceFlags>(nodeServices | NODE_BLOOM); + } + CNode* pnode = new CNode(id, nodeServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true); pnode->AddRef(); - pnode->fWhitelisted = whitelisted; + pnode->m_permissionFlags = permissionFlags; + // If this flag is present, the user probably expect that RPC and QT report it as whitelisted (backward compatibility) + pnode->m_legacyWhitelisted = legacyWhitelisted; pnode->m_prefer_evict = bannedlevel > 0; m_msgproc->InitializeNode(pnode); @@ -1403,16 +1434,10 @@ static void ThreadMapPort() struct UPNPDev * devlist = nullptr; char lanaddr[64]; -#ifndef UPNPDISCOVER_SUCCESS - /* miniupnpc 1.5 */ - devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0); -#elif MINIUPNPC_API_VERSION < 14 - /* miniupnpc 1.6 */ int error = 0; +#if MINIUPNPC_API_VERSION < 14 devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error); #else - /* miniupnpc 1.9.20150730 */ - int error = 0; devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error); #endif @@ -1426,43 +1451,32 @@ static void ThreadMapPort() if (fDiscover) { char externalIPAddress[40]; r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); - if(r != UPNPCOMMAND_SUCCESS) + if (r != UPNPCOMMAND_SUCCESS) { LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r); - else - { - if(externalIPAddress[0]) - { + } else { + if (externalIPAddress[0]) { CNetAddr resolved; - if(LookupHost(externalIPAddress, resolved, false)) { + if (LookupHost(externalIPAddress, resolved, false)) { LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToString().c_str()); AddLocal(resolved, LOCAL_UPNP); } - } - else + } else { LogPrintf("UPnP: GetExternalIPAddress failed.\n"); + } } } - std::string strDesc = "Bitcoin " + FormatFullVersion(); + std::string strDesc = PACKAGE_NAME " " + FormatFullVersion(); do { -#ifndef UPNPDISCOVER_SUCCESS - /* miniupnpc 1.5 */ - r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, - port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0); -#else - /* miniupnpc 1.6 */ - r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, - port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); -#endif + r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); - if(r!=UPNPCOMMAND_SUCCESS) - LogPrintf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", - port, port, lanaddr, r, strupnperror(r)); - else + if (r != UPNPCOMMAND_SUCCESS) { + LogPrintf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", port, port, lanaddr, r, strupnperror(r)); + } else { LogPrintf("UPnP Port Mapping successful.\n"); - } - while(g_upnp_interrupt.sleep_for(std::chrono::minutes(20))); + } + } while (g_upnp_interrupt.sleep_for(std::chrono::minutes(20))); r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", 0); LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r); @@ -1651,7 +1665,7 @@ int CConnman::GetExtraOutboundCount() } } } - return std::max(nOutbound - nMaxOutbound, 0); + return std::max(nOutbound - m_max_outbound_full_relay - m_max_outbound_block_relay, 0); } void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) @@ -1711,7 +1725,8 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) CAddress addrConnect; // Only connect out to one peer per network group (/16 for IPv4). - int nOutbound = 0; + int nOutboundFullRelay = 0; + int nOutboundBlockRelay = 0; std::set<std::vector<unsigned char> > setConnected; { LOCK(cs_vNodes); @@ -1723,7 +1738,11 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // also have the added issue that they're attacker controlled and could be used // to prevent us from connecting to particular hosts if we used them here. setConnected.insert(pnode->addr.GetGroup()); - nOutbound++; + if (pnode->m_tx_relay == nullptr) { + nOutboundBlockRelay++; + } else if (!pnode->fFeeler) { + nOutboundFullRelay++; + } } } } @@ -1742,7 +1761,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // bool fFeeler = false; - if (nOutbound >= nMaxOutbound && !GetTryNewOutboundPeer()) { + if (nOutboundFullRelay >= m_max_outbound_full_relay && nOutboundBlockRelay >= m_max_outbound_block_relay && !GetTryNewOutboundPeer()) { int64_t nTime = GetTimeMicros(); // The current time right now (in microseconds). if (nTime > nNextFeeler) { nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL); @@ -1816,7 +1835,14 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToString()); } - OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, false, fFeeler); + // Open this connection as block-relay-only if we're already at our + // full-relay capacity, but not yet at our block-relay peer limit. + // (It should not be possible for fFeeler to be set if we're not + // also at our block-relay peer limit, but check against that as + // well for sanity.) + bool block_relay_only = nOutboundBlockRelay < m_max_outbound_block_relay && !fFeeler && nOutboundFullRelay >= m_max_outbound_full_relay; + + OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, false, fFeeler, false, block_relay_only); } } } @@ -1903,7 +1929,7 @@ void CConnman::ThreadOpenAddedConnections() } // if successful, this moves the passed grant to the constructed node -void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool manual_connection) +void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool manual_connection, bool block_relay_only) { // // Initiate outbound network connection @@ -1922,7 +1948,7 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai } else if (FindNode(std::string(pszDest))) return; - CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, manual_connection); + CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, manual_connection, block_relay_only); if (!pnode) return; @@ -1996,7 +2022,7 @@ void CConnman::ThreadMessageHandler() -bool CConnman::BindListenPort(const CService &addrBind, std::string& strError, bool fWhitelisted) +bool CConnman::BindListenPort(const CService& addrBind, std::string& strError, NetPermissionFlags permissions) { strError = ""; int nOne = 1; @@ -2039,9 +2065,9 @@ bool CConnman::BindListenPort(const CService &addrBind, std::string& strError, b { int nErr = WSAGetLastError(); if (nErr == WSAEADDRINUSE) - strError = strprintf(_("Unable to bind to %s on this computer. %s is probably already running."), addrBind.ToString(), _(PACKAGE_NAME)); + strError = strprintf(_("Unable to bind to %s on this computer. %s is probably already running.").translated, addrBind.ToString(), PACKAGE_NAME); else - strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %s)"), addrBind.ToString(), NetworkErrorString(nErr)); + strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %s)").translated, addrBind.ToString(), NetworkErrorString(nErr)); LogPrintf("%s\n", strError); CloseSocket(hListenSocket); return false; @@ -2051,15 +2077,15 @@ bool CConnman::BindListenPort(const CService &addrBind, std::string& strError, b // Listen for incoming connections if (listen(hListenSocket, SOMAXCONN) == SOCKET_ERROR) { - strError = strprintf(_("Error: Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); + strError = strprintf(_("Error: Listening for incoming connections failed (listen returned error %s)").translated, NetworkErrorString(WSAGetLastError())); LogPrintf("%s\n", strError); CloseSocket(hListenSocket); return false; } - vhListenSocket.push_back(ListenSocket(hListenSocket, fWhitelisted)); + vhListenSocket.push_back(ListenSocket(hListenSocket, permissions)); - if (addrBind.IsRoutable() && fDiscover && !fWhitelisted) + if (addrBind.IsRoutable() && fDiscover && (permissions & PF_NOBAN) == 0) AddLocal(addrBind, LOCAL_BIND); return true; @@ -2143,11 +2169,11 @@ NodeId CConnman::GetNewNodeId() } -bool CConnman::Bind(const CService &addr, unsigned int flags) { +bool CConnman::Bind(const CService &addr, unsigned int flags, NetPermissionFlags permissions) { if (!(flags & BF_EXPLICIT) && !IsReachable(addr)) return false; std::string strError; - if (!BindListenPort(addr, strError, (flags & BF_WHITELIST) != 0)) { + if (!BindListenPort(addr, strError, permissions)) { if ((flags & BF_REPORT_ERROR) && clientInterface) { clientInterface->ThreadSafeMessageBox(strError, "", CClientUIInterface::MSG_ERROR); } @@ -2156,20 +2182,21 @@ bool CConnman::Bind(const CService &addr, unsigned int flags) { return true; } -bool CConnman::InitBinds(const std::vector<CService>& binds, const std::vector<CService>& whiteBinds) { +bool CConnman::InitBinds(const std::vector<CService>& binds, const std::vector<NetWhitebindPermissions>& whiteBinds) +{ bool fBound = false; for (const auto& addrBind : binds) { - fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR)); + fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR), NetPermissionFlags::PF_NONE); } for (const auto& addrBind : whiteBinds) { - fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST)); + fBound |= Bind(addrBind.m_service, (BF_EXPLICIT | BF_REPORT_ERROR), addrBind.m_flags); } if (binds.empty() && whiteBinds.empty()) { struct in_addr inaddr_any; inaddr_any.s_addr = INADDR_ANY; struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT; - fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE); - fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE); + fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE, NetPermissionFlags::PF_NONE); + fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE, NetPermissionFlags::PF_NONE); } return fBound; } @@ -2192,7 +2219,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds)) { if (clientInterface) { clientInterface->ThreadSafeMessageBox( - _("Failed to listen on any port. Use -listen=0 if you want this."), + _("Failed to listen on any port. Use -listen=0 if you want this.").translated, "", CClientUIInterface::MSG_ERROR); } return false; @@ -2203,7 +2230,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) } if (clientInterface) { - clientInterface->InitMessage(_("Loading P2P addresses...")); + clientInterface->InitMessage(_("Loading P2P addresses...").translated); } // Load addresses from peers.dat int64_t nStart = GetTimeMillis(); @@ -2218,13 +2245,13 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) } } - uiInterface.InitMessage(_("Starting network threads...")); + uiInterface.InitMessage(_("Starting network threads...").translated); fAddressesInitialized = true; if (semOutbound == nullptr) { // initialize semaphore - semOutbound = MakeUnique<CSemaphore>(std::min((nMaxOutbound + nMaxFeeler), nMaxConnections)); + semOutbound = MakeUnique<CSemaphore>(std::min(m_max_outbound, nMaxConnections)); } if (semAddnode == nullptr) { // initialize semaphore @@ -2258,7 +2285,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) if (connOptions.m_use_addrman_outgoing && !connOptions.m_specified_outgoing.empty()) { if (clientInterface) { clientInterface->ThreadSafeMessageBox( - _("Cannot provide specific connections and have addrman find outgoing connections at the same."), + _("Cannot provide specific connections and have addrman find outgoing connections at the same.").translated, "", CClientUIInterface::MSG_ERROR); } return false; @@ -2302,7 +2329,7 @@ void CConnman::Interrupt() InterruptSocks5(true); if (semOutbound) { - for (int i=0; i<(nMaxOutbound + nMaxFeeler); i++) { + for (int i=0; i<m_max_outbound; i++) { semOutbound->post(); } } @@ -2612,14 +2639,17 @@ int CConnman::GetBestHeight() const unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; } -CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, bool fInboundIn) +CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, bool fInboundIn, bool block_relay_only) : nTimeConnected(GetSystemTimeInSeconds()), addr(addrIn), addrBind(addrBindIn), fInbound(fInboundIn), nKeyedNetGroup(nKeyedNetGroupIn), addrKnown(5000, 0.001), - filterInventoryKnown(50000, 0.000001), + // Don't relay addr messages to peers that we connect to as block-relay-only + // peers (to prevent adversaries from inferring these links from addr + // traffic). + m_addr_relay_peer(!block_relay_only), id(idIn), nLocalHostNonce(nLocalHostNonceIn), nLocalServices(nLocalServicesIn), @@ -2628,8 +2658,9 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn hSocket = hSocketIn; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; hashContinue = uint256(); - filterInventoryKnown.reset(); - pfilter = MakeUnique<CBloomFilter>(); + if (!block_relay_only) { + m_tx_relay = MakeUnique<TxRelay>(); + } for (const std::string &msg : getAllNetMessageTypes()) mapRecvBytesPerMsgCmd[msg] = 0; @@ -15,6 +15,7 @@ #include <hash.h> #include <limitedmap.h> #include <netaddress.h> +#include <net_permissions.h> #include <policy/feerate.h> #include <protocol.h> #include <random.h> @@ -39,6 +40,11 @@ class CScheduler; class CNode; class BanMan; +/** Default for -whitelistrelay. */ +static const bool DEFAULT_WHITELISTRELAY = true; +/** Default for -whitelistforcerelay. */ +static const bool DEFAULT_WHITELISTFORCERELAY = false; + /** Time between pings automatically sent out for latency probing and keepalive (in seconds). */ static const int PING_INTERVAL = 2 * 60; /** Time after which to disconnect, after waiting for a ping response (or inactivity). */ @@ -55,10 +61,12 @@ static const unsigned int MAX_ADDR_TO_SEND = 1000; static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = 4 * 1000 * 1000; /** Maximum length of the user agent string in `version` message */ static const unsigned int MAX_SUBVERSION_LENGTH = 256; -/** Maximum number of automatic outgoing nodes */ -static const int MAX_OUTBOUND_CONNECTIONS = 8; +/** Maximum number of automatic outgoing nodes over which we'll relay everything (blocks, tx, addrs, etc) */ +static const int MAX_OUTBOUND_FULL_RELAY_CONNECTIONS = 8; /** Maximum number of addnode outgoing nodes */ static const int MAX_ADDNODE_CONNECTIONS = 8; +/** Maximum number of block-relay-only outgoing connections */ +static const int MAX_BLOCKS_ONLY_CONNECTIONS = 2; /** -listen default */ static const bool DEFAULT_LISTEN = true; /** -upnp default */ @@ -125,7 +133,8 @@ public: { ServiceFlags nLocalServices = NODE_NONE; int nMaxConnections = 0; - int nMaxOutbound = 0; + int m_max_outbound_full_relay = 0; + int m_max_outbound_block_relay = 0; int nMaxAddnode = 0; int nMaxFeeler = 0; int nBestHeight = 0; @@ -138,8 +147,9 @@ public: uint64_t nMaxOutboundLimit = 0; int64_t m_peer_connect_timeout = DEFAULT_PEER_CONNECT_TIMEOUT; std::vector<std::string> vSeedNodes; - std::vector<CSubNet> vWhitelistedRange; - std::vector<CService> vBinds, vWhiteBinds; + std::vector<NetWhitelistPermissions> vWhitelistedRange; + std::vector<NetWhitebindPermissions> vWhiteBinds; + std::vector<CService> vBinds; bool m_use_addrman_outgoing = true; std::vector<std::string> m_specified_outgoing; std::vector<std::string> m_added_nodes; @@ -148,10 +158,12 @@ public: void Init(const Options& connOptions) { nLocalServices = connOptions.nLocalServices; nMaxConnections = connOptions.nMaxConnections; - nMaxOutbound = std::min(connOptions.nMaxOutbound, connOptions.nMaxConnections); + m_max_outbound_full_relay = std::min(connOptions.m_max_outbound_full_relay, connOptions.nMaxConnections); + m_max_outbound_block_relay = connOptions.m_max_outbound_block_relay; m_use_addrman_outgoing = connOptions.m_use_addrman_outgoing; nMaxAddnode = connOptions.nMaxAddnode; nMaxFeeler = connOptions.nMaxFeeler; + m_max_outbound = m_max_outbound_full_relay + m_max_outbound_block_relay + nMaxFeeler; nBestHeight = connOptions.nBestHeight; clientInterface = connOptions.uiInterface; m_banman = connOptions.m_banman; @@ -190,7 +202,7 @@ public: bool GetNetworkActive() const { return fNetworkActive; }; bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; }; void SetNetworkActive(bool active); - void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false, bool fFeeler = false, bool manual_connection = false); + void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false, bool fFeeler = false, bool manual_connection = false, bool block_relay_only = false); bool CheckIncomingNonce(uint64_t nonce); bool ForNode(NodeId id, std::function<bool(CNode* pnode)> func); @@ -246,7 +258,7 @@ public: void AddNewAddresses(const std::vector<CAddress>& vAddr, const CAddress& addrFrom, int64_t nTimePenalty = 0); std::vector<CAddress> GetAddresses(); - // This allows temporarily exceeding nMaxOutbound, with the goal of finding + // This allows temporarily exceeding m_max_outbound_full_relay, with the goal of finding // a peer that is better than all our current peers. void SetTryNewOutboundPeer(bool flag); bool GetTryNewOutboundPeer(); @@ -314,15 +326,17 @@ public: private: struct ListenSocket { + public: SOCKET socket; - bool whitelisted; - - ListenSocket(SOCKET socket_, bool whitelisted_) : socket(socket_), whitelisted(whitelisted_) {} + inline void AddSocketPermissionFlags(NetPermissionFlags& flags) const { NetPermissions::AddFlag(flags, m_permissions); } + ListenSocket(SOCKET socket_, NetPermissionFlags permissions_) : socket(socket_), m_permissions(permissions_) {} + private: + NetPermissionFlags m_permissions; }; - bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false); - bool Bind(const CService &addr, unsigned int flags); - bool InitBinds(const std::vector<CService>& binds, const std::vector<CService>& whiteBinds); + bool BindListenPort(const CService& bindAddr, std::string& strError, NetPermissionFlags permissions); + bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions); + bool InitBinds(const std::vector<CService>& binds, const std::vector<NetWhitebindPermissions>& whiteBinds); void ThreadOpenAddedConnections(); void AddOneShot(const std::string& strDest); void ProcessOneShot(); @@ -346,8 +360,8 @@ private: CNode* FindNode(const CService& addr); bool AttemptToEvictConnection(); - CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection); - bool IsWhitelistedRange(const CNetAddr &addr); + CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection, bool block_relay_only); + void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const; void DeleteNode(CNode* pnode); @@ -380,7 +394,7 @@ private: // Whitelisted ranges. Any node connecting from these is automatically // whitelisted (as well as those connecting to whitelisted binds). - std::vector<CSubNet> vWhitelistedRange; + std::vector<NetWhitelistPermissions> vWhitelistedRange; unsigned int nSendBufferMaxSize{0}; unsigned int nReceiveFloodSize{0}; @@ -405,9 +419,17 @@ private: std::unique_ptr<CSemaphore> semOutbound; std::unique_ptr<CSemaphore> semAddnode; int nMaxConnections; - int nMaxOutbound; + + // How many full-relay (tx, block, addr) outbound peers we want + int m_max_outbound_full_relay; + + // How many block-relay only outbound peers we want + // We do not relay tx or addr messages with these peers + int m_max_outbound_block_relay; + int nMaxAddnode; int nMaxFeeler; + int m_max_outbound; bool m_use_addrman_outgoing; std::atomic<int> nBestHeight; CClientUIInterface* clientInterface; @@ -433,7 +455,7 @@ private: std::thread threadMessageHandler; /** flag for deciding to connect to an extra outbound peer, - * in excess of nMaxOutbound + * in excess of m_max_outbound_full_relay * This takes the place of a feeler connection */ std::atomic_bool m_try_another_outbound_peer; @@ -448,7 +470,6 @@ void StartMapPort(); void InterruptMapPort(); void StopMapPort(); unsigned short GetListenPort(); -bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false); struct CombinerAll { @@ -555,7 +576,8 @@ public: mapMsgCmdSize mapSendBytesPerMsgCmd; uint64_t nRecvBytes; mapMsgCmdSize mapRecvBytesPerMsgCmd; - bool fWhitelisted; + NetPermissionFlags m_permissionFlags; + bool m_legacyWhitelisted; double dPingTime; double dPingWait; double dMinPing; @@ -657,7 +679,11 @@ public: */ std::string cleanSubVer GUARDED_BY(cs_SubVer){}; bool m_prefer_evict{false}; // This peer is preferred for eviction. - bool fWhitelisted{false}; // This peer can bypass DoS banning. + bool HasPermission(NetPermissionFlags permission) const { + return NetPermissions::HasFlag(m_permissionFlags, permission); + } + // This boolean is unusued in actual processing, only present for backward compatibility at RPC/QT level + bool m_legacyWhitelisted{false}; bool fFeeler{false}; // If true this node is being used as a short lived feeler. bool fOneShot{false}; bool m_manual_connection{false}; @@ -668,15 +694,8 @@ public: // Setting fDisconnect to true will cause the node to be disconnected the // next time DisconnectNodes() runs std::atomic_bool fDisconnect{false}; - // We use fRelayTxes for two purposes - - // a) it allows us to not relay tx invs before receiving the peer's version message - // b) the peer may tell us in its version message that we should not relay tx invs - // unless it loads a bloom filter. - bool fRelayTxes GUARDED_BY(cs_filter){false}; bool fSentAddr{false}; CSemaphoreGrant grantOutbound; - mutable CCriticalSection cs_filter; - std::unique_ptr<CBloomFilter> pfilter PT_GUARDED_BY(cs_filter); std::atomic<int> nRefCount{0}; const uint64_t nKeyedNetGroup; @@ -695,28 +714,51 @@ public: std::vector<CAddress> vAddrToSend; CRollingBloomFilter addrKnown; bool fGetAddr{false}; - std::set<uint256> setKnown; int64_t nNextAddrSend GUARDED_BY(cs_sendProcessing){0}; int64_t nNextLocalAddrSend GUARDED_BY(cs_sendProcessing){0}; - // inventory based relay - CRollingBloomFilter filterInventoryKnown GUARDED_BY(cs_inventory); - // Set of transaction ids we still have to announce. - // They are sorted by the mempool before relay, so the order is not important. - std::set<uint256> setInventoryTxToSend; + const bool m_addr_relay_peer; + bool IsAddrRelayPeer() const { return m_addr_relay_peer; } + // List of block ids we still have announce. // There is no final sorting before sending, as they are always sent immediately // and in the order requested. std::vector<uint256> vInventoryBlockToSend GUARDED_BY(cs_inventory); CCriticalSection cs_inventory; - int64_t nNextInvSend{0}; + + struct TxRelay { + TxRelay() { pfilter = MakeUnique<CBloomFilter>(); } + mutable CCriticalSection cs_filter; + // We use fRelayTxes for two purposes - + // a) it allows us to not relay tx invs before receiving the peer's version message + // b) the peer may tell us in its version message that we should not relay tx invs + // unless it loads a bloom filter. + bool fRelayTxes GUARDED_BY(cs_filter){false}; + std::unique_ptr<CBloomFilter> pfilter PT_GUARDED_BY(cs_filter) GUARDED_BY(cs_filter); + + mutable CCriticalSection cs_tx_inventory; + CRollingBloomFilter filterInventoryKnown GUARDED_BY(cs_tx_inventory){50000, 0.000001}; + // Set of transaction ids we still have to announce. + // They are sorted by the mempool before relay, so the order is not important. + std::set<uint256> setInventoryTxToSend; + // Used for BIP35 mempool sending + bool fSendMempool GUARDED_BY(cs_tx_inventory){false}; + // Last time a "MEMPOOL" request was serviced. + std::atomic<int64_t> timeLastMempoolReq{0}; + int64_t nNextInvSend{0}; + + CCriticalSection cs_feeFilter; + // Minimum fee rate with which to filter inv's to this node + CAmount minFeeFilter GUARDED_BY(cs_feeFilter){0}; + CAmount lastSentFeeFilter{0}; + int64_t nextSendTimeFeeFilter{0}; + }; + + // m_tx_relay == nullptr if we're not relaying transactions with this peer + std::unique_ptr<TxRelay> m_tx_relay; + // Used for headers announcements - unfiltered blocks to relay std::vector<uint256> vBlockHashesToAnnounce GUARDED_BY(cs_inventory); - // Used for BIP35 mempool sending - bool fSendMempool GUARDED_BY(cs_inventory){false}; - - // Last time a "MEMPOOL" request was serviced. - std::atomic<int64_t> timeLastMempoolReq{0}; // Block and TXN accept times std::atomic<int64_t> nLastBlockTime{0}; @@ -733,15 +775,10 @@ public: std::atomic<int64_t> nMinPingUsecTime{std::numeric_limits<int64_t>::max()}; // Whether a ping is requested. std::atomic<bool> fPingQueued{false}; - // Minimum fee rate with which to filter inv's to this node - CAmount minFeeFilter GUARDED_BY(cs_feeFilter){0}; - CCriticalSection cs_feeFilter; - CAmount lastSentFeeFilter{0}; - int64_t nextSendTimeFeeFilter{0}; std::set<uint256> orphan_work_set; - CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false); + CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false, bool block_relay_only = false); ~CNode(); CNode(const CNode&) = delete; CNode& operator=(const CNode&) = delete; @@ -753,6 +790,7 @@ private: const ServiceFlags nLocalServices; const int nMyStartingHeight; int nSendVersion{0}; + NetPermissionFlags m_permissionFlags{ PF_NONE }; std::list<CNetMessage> vRecvMsg; // Used only by SocketHandler thread mutable CCriticalSection cs_addrName; @@ -833,20 +871,21 @@ public: void AddInventoryKnown(const CInv& inv) { - { - LOCK(cs_inventory); - filterInventoryKnown.insert(inv.hash); + if (m_tx_relay != nullptr) { + LOCK(m_tx_relay->cs_tx_inventory); + m_tx_relay->filterInventoryKnown.insert(inv.hash); } } void PushInventory(const CInv& inv) { - LOCK(cs_inventory); - if (inv.type == MSG_TX) { - if (!filterInventoryKnown.contains(inv.hash)) { - setInventoryTxToSend.insert(inv.hash); + if (inv.type == MSG_TX && m_tx_relay != nullptr) { + LOCK(m_tx_relay->cs_tx_inventory); + if (!m_tx_relay->filterInventoryKnown.contains(inv.hash)) { + m_tx_relay->setInventoryTxToSend.insert(inv.hash); } } else if (inv.type == MSG_BLOCK) { + LOCK(cs_inventory); vInventoryBlockToSend.push_back(inv.hash); } } diff --git a/src/net_permissions.cpp b/src/net_permissions.cpp new file mode 100644 index 0000000000..ef6c40ce20 --- /dev/null +++ b/src/net_permissions.cpp @@ -0,0 +1,107 @@ +// Copyright (c) 2009-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 <net_permissions.h> +#include <netbase.h> +#include <util/error.h> +#include <util/system.h> +#include <util/translation.h> + +// The parse the following format "perm1,perm2@xxxxxx" +bool TryParsePermissionFlags(const std::string str, NetPermissionFlags& output, size_t& readen, std::string& error) +{ + NetPermissionFlags flags = PF_NONE; + const auto atSeparator = str.find('@'); + + // if '@' is not found (ie, "xxxxx"), the caller should apply implicit permissions + if (atSeparator == std::string::npos) { + NetPermissions::AddFlag(flags, PF_ISIMPLICIT); + readen = 0; + } + // else (ie, "perm1,perm2@xxxxx"), let's enumerate the permissions by splitting by ',' and calculate the flags + else { + readen = 0; + // permissions == perm1,perm2 + const auto permissions = str.substr(0, atSeparator); + while (readen < permissions.length()) { + const auto commaSeparator = permissions.find(',', readen); + const auto len = commaSeparator == std::string::npos ? permissions.length() - readen : commaSeparator - readen; + // permission == perm1 + const auto permission = permissions.substr(readen, len); + readen += len; // We read "perm1" + if (commaSeparator != std::string::npos) readen++; // We read "," + + if (permission == "bloomfilter" || permission == "bloom") NetPermissions::AddFlag(flags, PF_BLOOMFILTER); + else if (permission == "noban") NetPermissions::AddFlag(flags, PF_NOBAN); + else if (permission == "forcerelay") NetPermissions::AddFlag(flags, PF_FORCERELAY); + else if (permission == "mempool") NetPermissions::AddFlag(flags, PF_MEMPOOL); + else if (permission == "all") NetPermissions::AddFlag(flags, PF_ALL); + else if (permission == "relay") NetPermissions::AddFlag(flags, PF_RELAY); + else if (permission.length() == 0); // Allow empty entries + else { + error = strprintf(_("Invalid P2P permission: '%s'").translated, permission); + return false; + } + } + readen++; + } + + output = flags; + error = ""; + return true; +} + +std::vector<std::string> NetPermissions::ToStrings(NetPermissionFlags flags) +{ + std::vector<std::string> strings; + if (NetPermissions::HasFlag(flags, PF_BLOOMFILTER)) strings.push_back("bloomfilter"); + if (NetPermissions::HasFlag(flags, PF_NOBAN)) strings.push_back("noban"); + if (NetPermissions::HasFlag(flags, PF_FORCERELAY)) strings.push_back("forcerelay"); + if (NetPermissions::HasFlag(flags, PF_RELAY)) strings.push_back("relay"); + if (NetPermissions::HasFlag(flags, PF_MEMPOOL)) strings.push_back("mempool"); + return strings; +} + +bool NetWhitebindPermissions::TryParse(const std::string str, NetWhitebindPermissions& output, std::string& error) +{ + NetPermissionFlags flags; + size_t offset; + if (!TryParsePermissionFlags(str, flags, offset, error)) return false; + + const std::string strBind = str.substr(offset); + CService addrBind; + if (!Lookup(strBind.c_str(), addrBind, 0, false)) { + error = ResolveErrMsg("whitebind", strBind); + return false; + } + if (addrBind.GetPort() == 0) { + error = strprintf(_("Need to specify a port with -whitebind: '%s'").translated, strBind); + return false; + } + + output.m_flags = flags; + output.m_service = addrBind; + error = ""; + return true; +} + +bool NetWhitelistPermissions::TryParse(const std::string str, NetWhitelistPermissions& output, std::string& error) +{ + NetPermissionFlags flags; + size_t offset; + if (!TryParsePermissionFlags(str, flags, offset, error)) return false; + + const std::string net = str.substr(offset); + CSubNet subnet; + LookupSubNet(net.c_str(), subnet); + if (!subnet.IsValid()) { + error = strprintf(_("Invalid netmask specified in -whitelist: '%s'").translated, net); + return false; + } + + output.m_flags = flags; + output.m_subnet = subnet; + error = ""; + return true; +} diff --git a/src/net_permissions.h b/src/net_permissions.h new file mode 100644 index 0000000000..b3987de65f --- /dev/null +++ b/src/net_permissions.h @@ -0,0 +1,62 @@ +// Copyright (c) 2009-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 <string> +#include <vector> +#include <netaddress.h> + +#ifndef BITCOIN_NET_PERMISSIONS_H +#define BITCOIN_NET_PERMISSIONS_H +enum NetPermissionFlags +{ + PF_NONE = 0, + // Can query bloomfilter even if -peerbloomfilters is false + PF_BLOOMFILTER = (1U << 1), + // Relay and accept transactions from this peer, even if -blocksonly is true + PF_RELAY = (1U << 3), + // Always relay transactions from this peer, even if already in mempool or rejected from policy + // Keep parameter interaction: forcerelay implies relay + PF_FORCERELAY = (1U << 2) | PF_RELAY, + // Can't be banned for misbehavior + PF_NOBAN = (1U << 4), + // Can query the mempool + PF_MEMPOOL = (1U << 5), + + // True if the user did not specifically set fine grained permissions + PF_ISIMPLICIT = (1U << 31), + PF_ALL = PF_BLOOMFILTER | PF_FORCERELAY | PF_RELAY | PF_NOBAN | PF_MEMPOOL, +}; +class NetPermissions +{ +public: + NetPermissionFlags m_flags; + static std::vector<std::string> ToStrings(NetPermissionFlags flags); + static inline bool HasFlag(const NetPermissionFlags& flags, NetPermissionFlags f) + { + return (flags & f) == f; + } + static inline void AddFlag(NetPermissionFlags& flags, NetPermissionFlags f) + { + flags = static_cast<NetPermissionFlags>(flags | f); + } + static inline void ClearFlag(NetPermissionFlags& flags, NetPermissionFlags f) + { + flags = static_cast<NetPermissionFlags>(flags & ~f); + } +}; +class NetWhitebindPermissions : public NetPermissions +{ +public: + static bool TryParse(const std::string str, NetWhitebindPermissions& output, std::string& error); + CService m_service; +}; + +class NetWhitelistPermissions : public NetPermissions +{ +public: + static bool TryParse(const std::string str, NetWhitelistPermissions& output, std::string& error); + CSubNet m_subnet; +}; + +#endif // BITCOIN_NET_PERMISSIONS_H
\ No newline at end of file diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 4b43b2cdf2..7f2fea5584 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -68,13 +68,13 @@ static constexpr int32_t MAX_PEER_TX_IN_FLIGHT = 100; /** Maximum number of announced transactions from a peer */ static constexpr int32_t MAX_PEER_TX_ANNOUNCEMENTS = 2 * MAX_INV_SZ; /** How many microseconds to delay requesting transactions from inbound peers */ -static constexpr int64_t INBOUND_PEER_TX_DELAY = 2 * 1000000; // 2 seconds +static constexpr std::chrono::microseconds INBOUND_PEER_TX_DELAY{std::chrono::seconds{2}}; /** How long to wait (in microseconds) before downloading a transaction from an additional peer */ -static constexpr int64_t GETDATA_TX_INTERVAL = 60 * 1000000; // 1 minute +static constexpr std::chrono::microseconds GETDATA_TX_INTERVAL{std::chrono::seconds{60}}; /** Maximum delay (in microseconds) for transaction requests to avoid biasing some peers over others. */ -static constexpr int64_t MAX_GETDATA_RANDOM_DELAY = 2 * 1000000; // 2 seconds +static constexpr std::chrono::microseconds MAX_GETDATA_RANDOM_DELAY{std::chrono::seconds{2}}; /** How long to wait (in microseconds) before expiring an in-flight getdata request to a peer */ -static constexpr int64_t TX_EXPIRY_INTERVAL = 10 * GETDATA_TX_INTERVAL; +static constexpr std::chrono::microseconds TX_EXPIRY_INTERVAL{GETDATA_TX_INTERVAL * 10}; static_assert(INBOUND_PEER_TX_DELAY >= MAX_GETDATA_RANDOM_DELAY, "To preserve security, MAX_GETDATA_RANDOM_DELAY should not exceed INBOUND_PEER_DELAY"); /** Limit to avoid sending big packets. Not used in processing incoming GETDATA for compatibility */ @@ -262,7 +262,7 @@ struct CNodeState { bool fSupportsDesiredCmpctVersion; /** State used to enforce CHAIN_SYNC_TIMEOUT - * Only in effect for outbound, non-manual connections, with + * Only in effect for outbound, non-manual, full-relay connections, with * m_protect == false * Algorithm: if a peer's best known block has less work than our tip, * set a timeout CHAIN_SYNC_TIMEOUT seconds in the future: @@ -340,16 +340,16 @@ struct CNodeState { /* Track when to attempt download of announced transactions (process * time in micros -> txid) */ - std::multimap<int64_t, uint256> m_tx_process_time; + std::multimap<std::chrono::microseconds, uint256> m_tx_process_time; //! Store all the transactions a peer has recently announced std::set<uint256> m_tx_announced; //! Store transactions which were requested by us, with timestamp - std::map<uint256, int64_t> m_tx_in_flight; + std::map<uint256, std::chrono::microseconds> m_tx_in_flight; //! Periodically check for stuck getdata requests - int64_t m_check_expiry_timer{0}; + std::chrono::microseconds m_check_expiry_timer{0}; }; TxDownloadState m_tx_download; @@ -391,7 +391,7 @@ struct CNodeState { }; // Keeps track of the time (in microseconds) when transactions were requested last time -limitedmap<uint256, int64_t> g_already_asked_for GUARDED_BY(cs_main)(MAX_INV_SZ); +limitedmap<uint256, std::chrono::microseconds> g_already_asked_for GUARDED_BY(cs_main)(MAX_INV_SZ); /** Map maintaining per-node state. */ static std::map<NodeId, CNodeState> mapNodeState GUARDED_BY(cs_main); @@ -408,7 +408,7 @@ static void UpdatePreferredDownload(CNode* node, CNodeState* state) EXCLUSIVE_LO nPreferredDownload -= state->fPreferredDownload; // Whether this node should be marked as a preferred download node. - state->fPreferredDownload = (!node->fInbound || node->fWhitelisted) && !node->fOneShot && !node->fClient; + state->fPreferredDownload = (!node->fInbound || node->HasPermission(PF_NOBAN)) && !node->fOneShot && !node->fClient; nPreferredDownload += state->fPreferredDownload; } @@ -425,7 +425,7 @@ static void PushNodeVersion(CNode *pnode, CConnman* connman, int64_t nTime) CAddress addrMe = CAddress(CService(), nLocalNodeServices); connman->PushMessage(pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe, - nonce, strSubVersion, nNodeStartingHeight, ::g_relay_txes)); + nonce, strSubVersion, nNodeStartingHeight, ::g_relay_txes && pnode->m_tx_relay != nullptr)); if (fLogIPs) { LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, them=%s, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), addrYou.ToString(), nodeid); @@ -688,16 +688,16 @@ void EraseTxRequest(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main) g_already_asked_for.erase(txid); } -int64_t GetTxRequestTime(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +std::chrono::microseconds GetTxRequestTime(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { auto it = g_already_asked_for.find(txid); if (it != g_already_asked_for.end()) { return it->second; } - return 0; + return {}; } -void UpdateTxRequestTime(const uint256& txid, int64_t request_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +void UpdateTxRequestTime(const uint256& txid, std::chrono::microseconds request_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { auto it = g_already_asked_for.find(txid); if (it == g_already_asked_for.end()) { @@ -707,17 +707,17 @@ void UpdateTxRequestTime(const uint256& txid, int64_t request_time) EXCLUSIVE_LO } } -int64_t CalculateTxGetDataTime(const uint256& txid, int64_t current_time, bool use_inbound_delay) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +std::chrono::microseconds CalculateTxGetDataTime(const uint256& txid, std::chrono::microseconds current_time, bool use_inbound_delay) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - int64_t process_time; - int64_t last_request_time = GetTxRequestTime(txid); + std::chrono::microseconds process_time; + const auto last_request_time = GetTxRequestTime(txid); // First time requesting this tx - if (last_request_time == 0) { + if (last_request_time.count() == 0) { process_time = current_time; } else { // Randomize the delay to avoid biasing some peers over others (such as due to // fixed ordering of peer processing in ThreadMessageHandler) - process_time = last_request_time + GETDATA_TX_INTERVAL + GetRand(MAX_GETDATA_RANDOM_DELAY); + process_time = last_request_time + GETDATA_TX_INTERVAL + GetRandMicros(MAX_GETDATA_RANDOM_DELAY); } // We delay processing announcements from inbound peers @@ -726,7 +726,7 @@ int64_t CalculateTxGetDataTime(const uint256& txid, int64_t current_time, bool u return process_time; } -void RequestTx(CNodeState* state, const uint256& txid, int64_t nNow) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +void RequestTx(CNodeState* state, const uint256& txid, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { CNodeState::TxDownloadState& peer_download_state = state->m_tx_download; if (peer_download_state.m_tx_announced.size() >= MAX_PEER_TX_ANNOUNCEMENTS || @@ -740,7 +740,7 @@ void RequestTx(CNodeState* state, const uint256& txid, int64_t nNow) EXCLUSIVE_L // Calculate the time to try requesting this transaction. Use // fPreferredDownload as a proxy for outbound peers. - int64_t process_time = CalculateTxGetDataTime(txid, nNow, !state->fPreferredDownload); + const auto process_time = CalculateTxGetDataTime(txid, current_time, !state->fPreferredDownload); peer_download_state.m_tx_process_time.emplace(process_time, txid); } @@ -757,7 +757,7 @@ void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) } // Returns true for outbound peers, excluding manual connections, feelers, and -// one-shots +// one-shots. static bool IsOutboundDisconnectionCandidate(const CNode *node) { return !(node->fInbound || node->m_manual_connection || node->fFeeler || node->fOneShot); @@ -1291,11 +1291,12 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) LOCK(g_cs_orphans); if (mapOrphanTransactions.count(inv.hash)) return true; } + const CCoinsViewCache& coins_cache = ::ChainstateActive().CoinsTip(); return recentRejects->contains(inv.hash) || mempool.exists(inv.hash) || - pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 0)) || // Best effort: only try output 0 and 1 - pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 1)); + coins_cache.HaveCoinInCache(COutPoint(inv.hash, 0)) || // Best effort: only try output 0 and 1 + coins_cache.HaveCoinInCache(COutPoint(inv.hash, 1)); } case MSG_BLOCK: case MSG_WITNESS_BLOCK: @@ -1305,10 +1306,10 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) return true; } -static void RelayTransaction(const CTransaction& tx, CConnman* connman) +void RelayTransaction(const uint256& txid, const CConnman& connman) { - CInv inv(MSG_TX, tx.GetHash()); - connman->ForEachNode([&inv](CNode* pnode) + CInv inv(MSG_TX, txid); + connman.ForEachNode([&inv](CNode* pnode) { pnode->PushInventory(inv); }); @@ -1329,7 +1330,7 @@ static void RelayAddress(const CAddress& addr, bool fReachable, CConnman* connma assert(nRelayNodes <= best.size()); auto sortfunc = [&best, &hasher, nRelayNodes](CNode* pnode) { - if (pnode->nVersion >= CADDR_TIME_VERSION) { + if (pnode->nVersion >= CADDR_TIME_VERSION && pnode->IsAddrRelayPeer()) { uint64_t hashKey = CSipHasher(hasher).Write(pnode->GetId()).Finalize(); for (unsigned int i = 0; i < nRelayNodes; i++) { if (hashKey > best[i].first) { @@ -1398,7 +1399,7 @@ void static ProcessGetBlockData(CNode* pfrom, const CChainParams& chainparams, c const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); // disconnect node in case we have reached the outbound limit for serving historical blocks // never disconnect whitelisted nodes - if (send && connman->OutboundTargetReached(true) && ( ((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.type == MSG_FILTERED_BLOCK) && !pfrom->fWhitelisted) + if (send && connman->OutboundTargetReached(true) && ( ((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.type == MSG_FILTERED_BLOCK) && !pfrom->HasPermission(PF_NOBAN)) { LogPrint(BCLog::NET, "historical block serving limit reached, disconnect peer=%d\n", pfrom->GetId()); @@ -1407,7 +1408,7 @@ void static ProcessGetBlockData(CNode* pfrom, const CChainParams& chainparams, c send = false; } // Avoid leaking prune-height by never sending blocks below the NODE_NETWORK_LIMITED threshold - if (send && !pfrom->fWhitelisted && ( + if (send && !pfrom->HasPermission(PF_NOBAN) && ( (((pfrom->GetLocalServices() & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((pfrom->GetLocalServices() & NODE_NETWORK) != NODE_NETWORK) && (::ChainActive().Tip()->nHeight - pindex->nHeight > (int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2 /* add two blocks buffer extension for possible races */) ) )) { LogPrint(BCLog::NET, "Ignore block request below NODE_NETWORK_LIMITED threshold from peer=%d\n", pfrom->GetId()); @@ -1448,11 +1449,11 @@ void static ProcessGetBlockData(CNode* pfrom, const CChainParams& chainparams, c { bool sendMerkleBlock = false; CMerkleBlock merkleBlock; - { - LOCK(pfrom->cs_filter); - if (pfrom->pfilter) { + if (pfrom->m_tx_relay != nullptr) { + LOCK(pfrom->m_tx_relay->cs_filter); + if (pfrom->m_tx_relay->pfilter) { sendMerkleBlock = true; - merkleBlock = CMerkleBlock(*pblock, *pfrom->pfilter); + merkleBlock = CMerkleBlock(*pblock, *pfrom->m_tx_relay->pfilter); } } if (sendMerkleBlock) { @@ -1512,7 +1513,12 @@ void static ProcessGetData(CNode* pfrom, const CChainParams& chainparams, CConnm std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin(); std::vector<CInv> vNotFound; const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); - { + + // Note that if we receive a getdata for a MSG_TX or MSG_WITNESS_TX from a + // block-relay-only outbound peer, we will stop processing further getdata + // messages from this peer (likely resulting in our peer eventually + // disconnecting us). + if (pfrom->m_tx_relay != nullptr) { LOCK(cs_main); while (it != pfrom->vRecvGetData.end() && (it->type == MSG_TX || it->type == MSG_WITNESS_TX)) { @@ -1532,11 +1538,11 @@ void static ProcessGetData(CNode* pfrom, const CChainParams& chainparams, CConnm if (mi != mapRelay.end()) { connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *mi->second)); push = true; - } else if (pfrom->timeLastMempoolReq) { + } else if (pfrom->m_tx_relay->timeLastMempoolReq) { auto txinfo = mempool.info(inv.hash); // To protect privacy, do not answer getdata using the mempool when // that TX couldn't have been INVed in reply to a MEMPOOL request. - if (txinfo.tx && txinfo.nTime <= pfrom->timeLastMempoolReq) { + if (txinfo.tx && txinfo.nTime <= pfrom->m_tx_relay->timeLastMempoolReq) { connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *txinfo.tx)); push = true; } @@ -1772,9 +1778,11 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve } } - if (!pfrom->fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && nodestate->pindexBestKnownBlock != nullptr) { - // If this is an outbound peer, check to see if we should protect + if (!pfrom->fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && nodestate->pindexBestKnownBlock != nullptr && pfrom->m_tx_relay != nullptr) { + // If this is an outbound full-relay peer, check to see if we should protect // it from the bad/lagging chain logic. + // Note that block-relay-only peers are already implicitly protected, so we + // only consider setting m_protect for the full-relay peers. if (g_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= ::ChainActive().Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) { LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom->GetId()); nodestate->m_chain_sync.m_protect = true; @@ -1811,7 +1819,7 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se if (setMisbehaving.count(fromPeer)) continue; if (AcceptToMemoryPool(mempool, orphan_state, porphanTx, &fMissingInputs2, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString()); - RelayTransaction(orphanTx, connman); + RelayTransaction(orphanHash, *connman); for (unsigned int i = 0; i < orphanTx.vout.size(); i++) { auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(orphanHash, i)); if (it_by_prev != mapOrphanTransactionsByPrev.end()) { @@ -1844,7 +1852,7 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se EraseOrphanTx(orphanHash); done = true; } - mempool.check(pcoinsTip.get()); + mempool.check(&::ChainstateActive().CoinsTip()); } } @@ -1995,9 +2003,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // set nodes not capable of serving the complete blockchain history as "limited nodes" pfrom->m_limited_node = (!(nServices & NODE_NETWORK) && (nServices & NODE_NETWORK_LIMITED)); - { - LOCK(pfrom->cs_filter); - pfrom->fRelayTxes = fRelay; // set to true after we get the first filter* message + if (pfrom->m_tx_relay != nullptr) { + LOCK(pfrom->m_tx_relay->cs_filter); + pfrom->m_tx_relay->fRelayTxes = fRelay; // set to true after we get the first filter* message } // Change version @@ -2016,7 +2024,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr UpdatePreferredDownload(pfrom, State(pfrom->GetId())); } - if (!pfrom->fInbound) + if (!pfrom->fInbound && pfrom->IsAddrRelayPeer()) { // Advertise our address if (fListen && !::ChainstateActive().IsInitialBlockDownload()) @@ -2088,9 +2096,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // Mark this node as currently connected, so we update its timestamp later. LOCK(cs_main); State(pfrom->GetId())->fCurrentlyConnected = true; - LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s\n", - pfrom->nVersion.load(), pfrom->nStartingHeight, pfrom->GetId(), - (fLogIPs ? strprintf(", peeraddr=%s", pfrom->addr.ToString()) : "")); + LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s (%s)\n", + pfrom->nVersion.load(), pfrom->nStartingHeight, + pfrom->GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom->addr.ToString()) : ""), + pfrom->m_tx_relay == nullptr ? "block-relay" : "full-relay"); } if (pfrom->nVersion >= SENDHEADERS_VERSION) { @@ -2131,6 +2140,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // Don't want addr from older versions unless seeding if (pfrom->nVersion < CADDR_TIME_VERSION && connman->GetAddressCount() > 1000) return true; + if (!pfrom->IsAddrRelayPeer()) { + return true; + } if (vAddr.size() > 1000) { LOCK(cs_main); @@ -2214,16 +2226,18 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr return false; } - bool fBlocksOnly = !g_relay_txes; + // We won't accept tx inv's if we're in blocks-only mode, or this is a + // block-relay-only peer + bool fBlocksOnly = !g_relay_txes || (pfrom->m_tx_relay == nullptr); // Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true - if (pfrom->fWhitelisted && gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) + if (pfrom->HasPermission(PF_RELAY)) fBlocksOnly = false; LOCK(cs_main); uint32_t nFetchFlags = GetFetchFlags(pfrom); - int64_t nNow = GetTimeMicros(); + const auto current_time = GetTime<std::chrono::microseconds>(); for (CInv &inv : vInv) { @@ -2253,9 +2267,11 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr { pfrom->AddInventoryKnown(inv); if (fBlocksOnly) { - LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol peer=%d\n", inv.hash.ToString(), pfrom->GetId()); + LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol, disconnecting peer=%d\n", inv.hash.ToString(), pfrom->GetId()); + pfrom->fDisconnect = true; + return true; } else if (!fAlreadyHave && !fImporting && !fReindex && !::ChainstateActive().IsInitialBlockDownload()) { - RequestTx(State(pfrom->GetId()), inv.hash, nNow); + RequestTx(State(pfrom->GetId()), inv.hash, current_time); } } } @@ -2412,7 +2428,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } LOCK(cs_main); - if (::ChainstateActive().IsInitialBlockDownload() && !pfrom->fWhitelisted) { + if (::ChainstateActive().IsInitialBlockDownload() && !pfrom->HasPermission(PF_NOBAN)) { LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because node is in initial block download\n", pfrom->GetId()); return true; } @@ -2470,9 +2486,11 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (strCommand == NetMsgType::TX) { // Stop processing the transaction early if // We are in blocks only mode and peer is either not whitelisted or whitelistrelay is off - if (!g_relay_txes && (!pfrom->fWhitelisted || !gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY))) + // or if this peer is supposed to be a block-relay-only peer + if ((!g_relay_txes && !pfrom->HasPermission(PF_RELAY)) || (pfrom->m_tx_relay == nullptr)) { LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom->GetId()); + pfrom->fDisconnect = true; return true; } @@ -2497,8 +2515,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, ptx, &fMissingInputs, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { - mempool.check(pcoinsTip.get()); - RelayTransaction(tx, connman); + mempool.check(&::ChainstateActive().CoinsTip()); + RelayTransaction(tx.GetHash(), *connman); for (unsigned int i = 0; i < tx.vout.size(); i++) { auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(inv.hash, i)); if (it_by_prev != mapOrphanTransactionsByPrev.end()) { @@ -2529,12 +2547,12 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } if (!fRejectedParents) { uint32_t nFetchFlags = GetFetchFlags(pfrom); - int64_t nNow = GetTimeMicros(); + const auto current_time = GetTime<std::chrono::microseconds>(); for (const CTxIn& txin : tx.vin) { CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash); pfrom->AddInventoryKnown(_inv); - if (!AlreadyHave(_inv)) RequestTx(State(pfrom->GetId()), _inv.hash, nNow); + if (!AlreadyHave(_inv)) RequestTx(State(pfrom->GetId()), _inv.hash, current_time); } AddOrphanTx(ptx, pfrom->GetId()); @@ -2565,7 +2583,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr AddToCompactExtraTransactions(ptx); } - if (pfrom->fWhitelisted && gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) { + if (pfrom->HasPermission(PF_FORCERELAY)) { // Always relay transactions received from whitelisted peers, even // if they were already in the mempool or rejected from it due // to policy, allowing the node to function as a gateway for @@ -2577,7 +2595,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->GetId(), FormatStateMessage(state)); } else { LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->GetId()); - RelayTransaction(tx, connman); + RelayTransaction(tx.GetHash(), *connman); } } } @@ -2989,6 +3007,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LogPrint(BCLog::NET, "Ignoring \"getaddr\" from outbound connection. peer=%d\n", pfrom->GetId()); return true; } + if (!pfrom->IsAddrRelayPeer()) { + LogPrint(BCLog::NET, "Ignoring \"getaddr\" from block-relay-only connection. peer=%d\n", pfrom->GetId()); + return true; + } // Only send one GetAddr response per connection to reduce resource waste // and discourage addr stamping of INV announcements. @@ -3010,22 +3032,30 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } if (strCommand == NetMsgType::MEMPOOL) { - if (!(pfrom->GetLocalServices() & NODE_BLOOM) && !pfrom->fWhitelisted) + if (!(pfrom->GetLocalServices() & NODE_BLOOM) && !pfrom->HasPermission(PF_MEMPOOL)) { - LogPrint(BCLog::NET, "mempool request with bloom filters disabled, disconnect peer=%d\n", pfrom->GetId()); - pfrom->fDisconnect = true; + if (!pfrom->HasPermission(PF_NOBAN)) + { + LogPrint(BCLog::NET, "mempool request with bloom filters disabled, disconnect peer=%d\n", pfrom->GetId()); + pfrom->fDisconnect = true; + } return true; } - if (connman->OutboundTargetReached(false) && !pfrom->fWhitelisted) + if (connman->OutboundTargetReached(false) && !pfrom->HasPermission(PF_MEMPOOL)) { - LogPrint(BCLog::NET, "mempool request with bandwidth limit reached, disconnect peer=%d\n", pfrom->GetId()); - pfrom->fDisconnect = true; + if (!pfrom->HasPermission(PF_NOBAN)) + { + LogPrint(BCLog::NET, "mempool request with bandwidth limit reached, disconnect peer=%d\n", pfrom->GetId()); + pfrom->fDisconnect = true; + } return true; } - LOCK(pfrom->cs_inventory); - pfrom->fSendMempool = true; + if (pfrom->m_tx_relay != nullptr) { + LOCK(pfrom->m_tx_relay->cs_tx_inventory); + pfrom->m_tx_relay->fSendMempool = true; + } return true; } @@ -3116,12 +3146,12 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LOCK(cs_main); Misbehaving(pfrom->GetId(), 100); } - else + else if (pfrom->m_tx_relay != nullptr) { - LOCK(pfrom->cs_filter); - pfrom->pfilter.reset(new CBloomFilter(filter)); - pfrom->pfilter->UpdateEmptyFull(); - pfrom->fRelayTxes = true; + LOCK(pfrom->m_tx_relay->cs_filter); + pfrom->m_tx_relay->pfilter.reset(new CBloomFilter(filter)); + pfrom->m_tx_relay->pfilter->UpdateEmptyFull(); + pfrom->m_tx_relay->fRelayTxes = true; } return true; } @@ -3135,10 +3165,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr bool bad = false; if (vData.size() > MAX_SCRIPT_ELEMENT_SIZE) { bad = true; - } else { - LOCK(pfrom->cs_filter); - if (pfrom->pfilter) { - pfrom->pfilter->insert(vData); + } else if (pfrom->m_tx_relay != nullptr) { + LOCK(pfrom->m_tx_relay->cs_filter); + if (pfrom->m_tx_relay->pfilter) { + pfrom->m_tx_relay->pfilter->insert(vData); } else { bad = true; } @@ -3151,11 +3181,14 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } if (strCommand == NetMsgType::FILTERCLEAR) { - LOCK(pfrom->cs_filter); + if (pfrom->m_tx_relay == nullptr) { + return true; + } + LOCK(pfrom->m_tx_relay->cs_filter); if (pfrom->GetLocalServices() & NODE_BLOOM) { - pfrom->pfilter.reset(new CBloomFilter()); + pfrom->m_tx_relay->pfilter.reset(new CBloomFilter()); } - pfrom->fRelayTxes = true; + pfrom->m_tx_relay->fRelayTxes = true; return true; } @@ -3163,9 +3196,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr CAmount newFeeFilter = 0; vRecv >> newFeeFilter; if (MoneyRange(newFeeFilter)) { - { - LOCK(pfrom->cs_feeFilter); - pfrom->minFeeFilter = newFeeFilter; + if (pfrom->m_tx_relay != nullptr) { + LOCK(pfrom->m_tx_relay->cs_feeFilter); + pfrom->m_tx_relay->minFeeFilter = newFeeFilter; } LogPrint(BCLog::NET, "received: feefilter of %s from peer=%d\n", CFeeRate(newFeeFilter).ToString(), pfrom->GetId()); } @@ -3216,7 +3249,7 @@ bool PeerLogicValidation::SendRejectsAndCheckIfBanned(CNode* pnode, bool enable_ if (state.fShouldBan) { state.fShouldBan = false; - if (pnode->fWhitelisted) + if (pnode->HasPermission(PF_NOBAN)) LogPrintf("Warning: not punishing whitelisted peer %s!\n", pnode->addr.ToString()); else if (pnode->m_manual_connection) LogPrintf("Warning: not punishing manually-connected peer %s!\n", pnode->addr.ToString()); @@ -3442,6 +3475,8 @@ void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds) if (state == nullptr) return; // shouldn't be possible, but just in case // Don't evict our protected peers if (state->m_chain_sync.m_protect) return; + // Don't evict our block-relay-only peers. + if (pnode->m_tx_relay == nullptr) return; if (state->m_last_block_announcement < oldest_block_announcement || (state->m_last_block_announcement == oldest_block_announcement && pnode->GetId() > worst_peer)) { worst_peer = pnode->GetId(); oldest_block_announcement = state->m_last_block_announcement; @@ -3569,7 +3604,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // Address refresh broadcast int64_t nNow = GetTimeMicros(); - if (!::ChainstateActive().IsInitialBlockDownload() && pto->nNextLocalAddrSend < nNow) { + if (pto->IsAddrRelayPeer() && !::ChainstateActive().IsInitialBlockDownload() && pto->nNextLocalAddrSend < nNow) { AdvertiseLocal(pto); pto->nNextLocalAddrSend = PoissonNextSend(nNow, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); } @@ -3577,7 +3612,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // // Message: addr // - if (pto->nNextAddrSend < nNow) { + if (pto->IsAddrRelayPeer() && pto->nNextAddrSend < nNow) { pto->nNextAddrSend = PoissonNextSend(nNow, AVG_ADDRESS_BROADCAST_INTERVAL); std::vector<CAddress> vAddr; vAddr.reserve(pto->vAddrToSend.size()); @@ -3785,120 +3820,123 @@ bool PeerLogicValidation::SendMessages(CNode* pto) } pto->vInventoryBlockToSend.clear(); - // Check whether periodic sends should happen - bool fSendTrickle = pto->fWhitelisted; - if (pto->nNextInvSend < nNow) { - fSendTrickle = true; - if (pto->fInbound) { - pto->nNextInvSend = connman->PoissonNextSendInbound(nNow, INVENTORY_BROADCAST_INTERVAL); - } else { - // Use half the delay for outbound peers, as there is less privacy concern for them. - pto->nNextInvSend = PoissonNextSend(nNow, INVENTORY_BROADCAST_INTERVAL >> 1); + if (pto->m_tx_relay != nullptr) { + LOCK(pto->m_tx_relay->cs_tx_inventory); + // Check whether periodic sends should happen + bool fSendTrickle = pto->HasPermission(PF_NOBAN); + if (pto->m_tx_relay->nNextInvSend < nNow) { + fSendTrickle = true; + if (pto->fInbound) { + pto->m_tx_relay->nNextInvSend = connman->PoissonNextSendInbound(nNow, INVENTORY_BROADCAST_INTERVAL); + } else { + // Use half the delay for outbound peers, as there is less privacy concern for them. + pto->m_tx_relay->nNextInvSend = PoissonNextSend(nNow, INVENTORY_BROADCAST_INTERVAL >> 1); + } } - } - - // Time to send but the peer has requested we not relay transactions. - if (fSendTrickle) { - LOCK(pto->cs_filter); - if (!pto->fRelayTxes) pto->setInventoryTxToSend.clear(); - } - // Respond to BIP35 mempool requests - if (fSendTrickle && pto->fSendMempool) { - auto vtxinfo = mempool.infoAll(); - pto->fSendMempool = false; - CAmount filterrate = 0; - { - LOCK(pto->cs_feeFilter); - filterrate = pto->minFeeFilter; + // Time to send but the peer has requested we not relay transactions. + if (fSendTrickle) { + LOCK(pto->m_tx_relay->cs_filter); + if (!pto->m_tx_relay->fRelayTxes) pto->m_tx_relay->setInventoryTxToSend.clear(); } - LOCK(pto->cs_filter); - - for (const auto& txinfo : vtxinfo) { - const uint256& hash = txinfo.tx->GetHash(); - CInv inv(MSG_TX, hash); - pto->setInventoryTxToSend.erase(hash); - if (filterrate) { - if (txinfo.feeRate.GetFeePerK() < filterrate) - continue; - } - if (pto->pfilter) { - if (!pto->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; + // Respond to BIP35 mempool requests + if (fSendTrickle && pto->m_tx_relay->fSendMempool) { + auto vtxinfo = mempool.infoAll(); + pto->m_tx_relay->fSendMempool = false; + CAmount filterrate = 0; + { + LOCK(pto->m_tx_relay->cs_feeFilter); + filterrate = pto->m_tx_relay->minFeeFilter; } - pto->filterInventoryKnown.insert(hash); - vInv.push_back(inv); - if (vInv.size() == MAX_INV_SZ) { - connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); - vInv.clear(); + + LOCK(pto->m_tx_relay->cs_filter); + + for (const auto& txinfo : vtxinfo) { + const uint256& hash = txinfo.tx->GetHash(); + CInv inv(MSG_TX, hash); + pto->m_tx_relay->setInventoryTxToSend.erase(hash); + if (filterrate) { + if (txinfo.feeRate.GetFeePerK() < filterrate) + continue; + } + if (pto->m_tx_relay->pfilter) { + if (!pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; + } + pto->m_tx_relay->filterInventoryKnown.insert(hash); + vInv.push_back(inv); + if (vInv.size() == MAX_INV_SZ) { + connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); + vInv.clear(); + } } + pto->m_tx_relay->timeLastMempoolReq = GetTime(); } - pto->timeLastMempoolReq = GetTime(); - } - // Determine transactions to relay - if (fSendTrickle) { - // Produce a vector with all candidates for sending - std::vector<std::set<uint256>::iterator> vInvTx; - vInvTx.reserve(pto->setInventoryTxToSend.size()); - for (std::set<uint256>::iterator it = pto->setInventoryTxToSend.begin(); it != pto->setInventoryTxToSend.end(); it++) { - vInvTx.push_back(it); - } - CAmount filterrate = 0; - { - LOCK(pto->cs_feeFilter); - filterrate = pto->minFeeFilter; - } - // Topologically and fee-rate sort the inventory we send for privacy and priority reasons. - // A heap is used so that not all items need sorting if only a few are being sent. - CompareInvMempoolOrder compareInvMempoolOrder(&mempool); - std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); - // No reason to drain out at many times the network's capacity, - // especially since we have many peers and some will draw much shorter delays. - unsigned int nRelayedTransactions = 0; - LOCK(pto->cs_filter); - while (!vInvTx.empty() && nRelayedTransactions < INVENTORY_BROADCAST_MAX) { - // Fetch the top element from the heap - std::pop_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); - std::set<uint256>::iterator it = vInvTx.back(); - vInvTx.pop_back(); - uint256 hash = *it; - // Remove it from the to-be-sent set - pto->setInventoryTxToSend.erase(it); - // Check if not in the filter already - if (pto->filterInventoryKnown.contains(hash)) { - continue; + // Determine transactions to relay + if (fSendTrickle) { + // Produce a vector with all candidates for sending + std::vector<std::set<uint256>::iterator> vInvTx; + vInvTx.reserve(pto->m_tx_relay->setInventoryTxToSend.size()); + for (std::set<uint256>::iterator it = pto->m_tx_relay->setInventoryTxToSend.begin(); it != pto->m_tx_relay->setInventoryTxToSend.end(); it++) { + vInvTx.push_back(it); } - // Not in the mempool anymore? don't bother sending it. - auto txinfo = mempool.info(hash); - if (!txinfo.tx) { - continue; - } - if (filterrate && txinfo.feeRate.GetFeePerK() < filterrate) { - continue; - } - if (pto->pfilter && !pto->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; - // Send - vInv.push_back(CInv(MSG_TX, hash)); - nRelayedTransactions++; + CAmount filterrate = 0; { - // Expire old relay messages - while (!vRelayExpiration.empty() && vRelayExpiration.front().first < nNow) - { - mapRelay.erase(vRelayExpiration.front().second); - vRelayExpiration.pop_front(); + LOCK(pto->m_tx_relay->cs_feeFilter); + filterrate = pto->m_tx_relay->minFeeFilter; + } + // Topologically and fee-rate sort the inventory we send for privacy and priority reasons. + // A heap is used so that not all items need sorting if only a few are being sent. + CompareInvMempoolOrder compareInvMempoolOrder(&mempool); + std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); + // No reason to drain out at many times the network's capacity, + // especially since we have many peers and some will draw much shorter delays. + unsigned int nRelayedTransactions = 0; + LOCK(pto->m_tx_relay->cs_filter); + while (!vInvTx.empty() && nRelayedTransactions < INVENTORY_BROADCAST_MAX) { + // Fetch the top element from the heap + std::pop_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); + std::set<uint256>::iterator it = vInvTx.back(); + vInvTx.pop_back(); + uint256 hash = *it; + // Remove it from the to-be-sent set + pto->m_tx_relay->setInventoryTxToSend.erase(it); + // Check if not in the filter already + if (pto->m_tx_relay->filterInventoryKnown.contains(hash)) { + continue; + } + // Not in the mempool anymore? don't bother sending it. + auto txinfo = mempool.info(hash); + if (!txinfo.tx) { + continue; } + if (filterrate && txinfo.feeRate.GetFeePerK() < filterrate) { + continue; + } + if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; + // Send + vInv.push_back(CInv(MSG_TX, hash)); + nRelayedTransactions++; + { + // Expire old relay messages + while (!vRelayExpiration.empty() && vRelayExpiration.front().first < nNow) + { + mapRelay.erase(vRelayExpiration.front().second); + vRelayExpiration.pop_front(); + } - auto ret = mapRelay.insert(std::make_pair(hash, std::move(txinfo.tx))); - if (ret.second) { - vRelayExpiration.push_back(std::make_pair(nNow + 15 * 60 * 1000000, ret.first)); + auto ret = mapRelay.insert(std::make_pair(hash, std::move(txinfo.tx))); + if (ret.second) { + vRelayExpiration.push_back(std::make_pair(nNow + 15 * 60 * 1000000, ret.first)); + } } + if (vInv.size() == MAX_INV_SZ) { + connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); + vInv.clear(); + } + pto->m_tx_relay->filterInventoryKnown.insert(hash); } - if (vInv.size() == MAX_INV_SZ) { - connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); - vInv.clear(); - } - pto->filterInventoryKnown.insert(hash); } } } @@ -3906,6 +3944,9 @@ bool PeerLogicValidation::SendMessages(CNode* pto) connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); // Detect whether we're stalling + const auto current_time = GetTime<std::chrono::microseconds>(); + // nNow is the current system time (GetTimeMicros is not mockable) and + // should be replaced by the mockable current_time eventually nNow = GetTimeMicros(); if (state.nStallingSince && state.nStallingSince < nNow - 1000000 * BLOCK_STALLING_TIMEOUT) { // Stalling only triggers when the block download window cannot move. During normal steady state, @@ -3939,7 +3980,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // Note: If all our peers are inbound, then we won't // disconnect our sync peer for stalling; we have bigger // problems if we can't get any outbound peers. - if (!pto->fWhitelisted) { + if (!pto->HasPermission(PF_NOBAN)) { LogPrintf("Timeout downloading headers from peer=%d, disconnecting\n", pto->GetId()); pto->fDisconnect = true; return true; @@ -3998,9 +4039,9 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // were unresponsive in the past. // Eventually we should consider disconnecting peers, but this is // conservative. - if (state.m_tx_download.m_check_expiry_timer <= nNow) { + if (state.m_tx_download.m_check_expiry_timer <= current_time) { for (auto it=state.m_tx_download.m_tx_in_flight.begin(); it != state.m_tx_download.m_tx_in_flight.end();) { - if (it->second <= nNow - TX_EXPIRY_INTERVAL) { + if (it->second <= current_time - TX_EXPIRY_INTERVAL) { LogPrint(BCLog::NET, "timeout of inflight tx %s from peer=%d\n", it->first.ToString(), pto->GetId()); state.m_tx_download.m_tx_announced.erase(it->first); state.m_tx_download.m_tx_in_flight.erase(it++); @@ -4010,11 +4051,11 @@ bool PeerLogicValidation::SendMessages(CNode* pto) } // On average, we do this check every TX_EXPIRY_INTERVAL. Randomize // so that we're not doing this for all peers at the same time. - state.m_tx_download.m_check_expiry_timer = nNow + TX_EXPIRY_INTERVAL/2 + GetRand(TX_EXPIRY_INTERVAL); + state.m_tx_download.m_check_expiry_timer = current_time + TX_EXPIRY_INTERVAL / 2 + GetRandMicros(TX_EXPIRY_INTERVAL); } auto& tx_process_time = state.m_tx_download.m_tx_process_time; - while (!tx_process_time.empty() && tx_process_time.begin()->first <= nNow && state.m_tx_download.m_tx_in_flight.size() < MAX_PEER_TX_IN_FLIGHT) { + while (!tx_process_time.empty() && tx_process_time.begin()->first <= current_time && state.m_tx_download.m_tx_in_flight.size() < MAX_PEER_TX_IN_FLIGHT) { const uint256 txid = tx_process_time.begin()->second; // Erase this entry from tx_process_time (it may be added back for // processing at a later time, see below) @@ -4023,22 +4064,22 @@ bool PeerLogicValidation::SendMessages(CNode* pto) if (!AlreadyHave(inv)) { // If this transaction was last requested more than 1 minute ago, // then request. - int64_t last_request_time = GetTxRequestTime(inv.hash); - if (last_request_time <= nNow - GETDATA_TX_INTERVAL) { + const auto last_request_time = GetTxRequestTime(inv.hash); + if (last_request_time <= current_time - GETDATA_TX_INTERVAL) { LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(), pto->GetId()); vGetData.push_back(inv); if (vGetData.size() >= MAX_GETDATA_SZ) { connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); vGetData.clear(); } - UpdateTxRequestTime(inv.hash, nNow); - state.m_tx_download.m_tx_in_flight.emplace(inv.hash, nNow); + UpdateTxRequestTime(inv.hash, current_time); + state.m_tx_download.m_tx_in_flight.emplace(inv.hash, current_time); } else { // This transaction is in flight from someone else; queue // up processing to happen after the download times out // (with a slight delay for inbound peers, to prefer // requests to outbound peers). - int64_t next_process_time = CalculateTxGetDataTime(txid, nNow, !state.fPreferredDownload); + const auto next_process_time = CalculateTxGetDataTime(txid, current_time, !state.fPreferredDownload); tx_process_time.emplace(next_process_time, txid); } } else { @@ -4056,27 +4097,27 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // Message: feefilter // // We don't want white listed peers to filter txs to us if we have -whitelistforcerelay - if (pto->nVersion >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) && - !(pto->fWhitelisted && gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY))) { + if (pto->m_tx_relay != nullptr && pto->nVersion >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) && + !pto->HasPermission(PF_FORCERELAY)) { CAmount currentFilter = mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK(); int64_t timeNow = GetTimeMicros(); - if (timeNow > pto->nextSendTimeFeeFilter) { + if (timeNow > pto->m_tx_relay->nextSendTimeFeeFilter) { static CFeeRate default_feerate(DEFAULT_MIN_RELAY_TX_FEE); static FeeFilterRounder filterRounder(default_feerate); CAmount filterToSend = filterRounder.round(currentFilter); // We always have a fee filter of at least minRelayTxFee filterToSend = std::max(filterToSend, ::minRelayTxFee.GetFeePerK()); - if (filterToSend != pto->lastSentFeeFilter) { + if (filterToSend != pto->m_tx_relay->lastSentFeeFilter) { connman->PushMessage(pto, msgMaker.Make(NetMsgType::FEEFILTER, filterToSend)); - pto->lastSentFeeFilter = filterToSend; + pto->m_tx_relay->lastSentFeeFilter = filterToSend; } - pto->nextSendTimeFeeFilter = PoissonNextSend(timeNow, AVG_FEEFILTER_BROADCAST_INTERVAL); + pto->m_tx_relay->nextSendTimeFeeFilter = PoissonNextSend(timeNow, AVG_FEEFILTER_BROADCAST_INTERVAL); } // If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY // until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY. - else if (timeNow + MAX_FEEFILTER_CHANGE_DELAY * 1000000 < pto->nextSendTimeFeeFilter && - (currentFilter < 3 * pto->lastSentFeeFilter / 4 || currentFilter > 4 * pto->lastSentFeeFilter / 3)) { - pto->nextSendTimeFeeFilter = timeNow + GetRandInt(MAX_FEEFILTER_CHANGE_DELAY) * 1000000; + else if (timeNow + MAX_FEEFILTER_CHANGE_DELAY * 1000000 < pto->m_tx_relay->nextSendTimeFeeFilter && + (currentFilter < 3 * pto->m_tx_relay->lastSentFeeFilter / 4 || currentFilter > 4 * pto->m_tx_relay->lastSentFeeFilter / 3)) { + pto->m_tx_relay->nextSendTimeFeeFilter = timeNow + GetRandInt(MAX_FEEFILTER_CHANGE_DELAY) * 1000000; } } } diff --git a/src/net_processing.h b/src/net_processing.h index 39c22d7118..1d26164b18 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -19,6 +19,7 @@ static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100; static const unsigned int DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN = 100; /** Default for BIP61 (sending reject messages) */ static constexpr bool DEFAULT_ENABLE_BIP61{false}; +static const bool DEFAULT_PEERBLOOMFILTERS = false; class PeerLogicValidation final : public CValidationInterface, public NetEventsInterface { private: @@ -89,4 +90,7 @@ struct CNodeStateStats { /** Get statistics from node state */ bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats); +/** Relay transaction to every node */ +void RelayTransaction(const uint256&, const CConnman& connman); + #endif // BITCOIN_NET_PROCESSING_H diff --git a/src/netbase.cpp b/src/netbase.cpp index 78b3b6ae3a..0148aea428 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -37,8 +37,8 @@ bool fNameLookup = DEFAULT_NAME_LOOKUP; static const int SOCKS5_RECV_TIMEOUT = 20 * 1000; static std::atomic<bool> interruptSocks5Recv(false); -enum Network ParseNetwork(std::string net) { - Downcase(net); +enum Network ParseNetwork(const std::string& net_in) { + std::string net = ToLower(net_in); if (net == "ipv4") return NET_IPV4; if (net == "ipv6") return NET_IPV6; if (net == "onion") return NET_ONION; @@ -65,6 +65,12 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign { CNetAddr addr; + // From our perspective, onion addresses are not hostnames but rather + // direct encodings of CNetAddr much like IPv4 dotted-decimal notation + // or IPv6 colon-separated hextet notation. Since we can't use + // getaddrinfo to decode them and it wouldn't make sense to resolve + // them, we return a network address representing it instead. See + // CNetAddr::SetSpecial(const std::string&) for more details. if (addr.SetSpecial(std::string(pszName))) { vIP.push_back(addr); return true; @@ -74,15 +80,25 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign struct addrinfo aiHint; memset(&aiHint, 0, sizeof(struct addrinfo)); + // We want a TCP port, which is a streaming socket type aiHint.ai_socktype = SOCK_STREAM; aiHint.ai_protocol = IPPROTO_TCP; + // We don't care which address family (IPv4 or IPv6) is returned aiHint.ai_family = AF_UNSPEC; + // If we allow lookups of hostnames, use the AI_ADDRCONFIG flag to only + // return addresses whose family we have an address configured for. + // + // If we don't allow lookups, then use the AI_NUMERICHOST flag for + // getaddrinfo to only decode numerical network addresses and suppress + // hostname lookups. aiHint.ai_flags = fAllowLookup ? AI_ADDRCONFIG : AI_NUMERICHOST; struct addrinfo *aiRes = nullptr; int nErr = getaddrinfo(pszName, nullptr, &aiHint, &aiRes); if (nErr) return false; + // Traverse the linked list starting with aiTrav, add all non-internal + // IPv4,v6 addresses to vIP while respecting nMaxSolutions. struct addrinfo *aiTrav = aiRes; while (aiTrav != nullptr && (nMaxSolutions == 0 || vIP.size() < nMaxSolutions)) { @@ -112,6 +128,21 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign return (vIP.size() > 0); } +/** + * Resolve a host string to its corresponding network addresses. + * + * @param pszName The string representing a host. Could be a name or a numerical + * IP address (IPv6 addresses in their bracketed form are + * allowed). + * @param[out] vIP The resulting network addresses to which the specified host + * string resolved. + * + * @returns Whether or not the specified host string successfully resolved to + * any resulting network addresses. + * + * @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int) + * for additional parameter descriptions. + */ bool LookupHost(const char *pszName, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup) { std::string strHost(pszName); @@ -124,6 +155,12 @@ bool LookupHost(const char *pszName, std::vector<CNetAddr>& vIP, unsigned int nM return LookupIntern(strHost.c_str(), vIP, nMaxSolutions, fAllowLookup); } + /** + * Resolve a host string to its first corresponding network address. + * + * @see LookupHost(const char *, std::vector<CNetAddr>&, unsigned int, bool) for + * additional parameter descriptions. + */ bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup) { std::vector<CNetAddr> vIP; @@ -134,6 +171,26 @@ bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup) return true; } +/** + * Resolve a service string to its corresponding service. + * + * @param pszName The string representing a service. Could be a name or a + * numerical IP address (IPv6 addresses should be in their + * disambiguated bracketed form), optionally followed by a port + * number. (e.g. example.com:8333 or + * [2001:db8:85a3:8d3:1319:8a2e:370:7348]:420) + * @param[out] vAddr The resulting services to which the specified service string + * resolved. + * @param portDefault The default port for resulting services if not specified + * by the service string. + * @param fAllowLookup Whether or not hostname lookups are permitted. If yes, + * external queries may be performed. + * @param nMaxSolutions The maximum number of results we want, specifying 0 + * means "as many solutions as we get." + * + * @returns Whether or not the service string successfully resolved to any + * resulting services. + */ bool Lookup(const char *pszName, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions) { if (pszName[0] == 0) @@ -152,6 +209,12 @@ bool Lookup(const char *pszName, std::vector<CService>& vAddr, int portDefault, return true; } +/** + * Resolve a service string to its first corresponding service. + * + * @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int) + * for additional parameter descriptions. + */ bool Lookup(const char *pszName, CService& addr, int portDefault, bool fAllowLookup) { std::vector<CService> vService; @@ -162,6 +225,16 @@ bool Lookup(const char *pszName, CService& addr, int portDefault, bool fAllowLoo return true; } +/** + * Resolve a service string with a numeric IP to its first corresponding + * service. + * + * @returns The resulting CService if the resolution was successful, [::]:0 + * otherwise. + * + * @see Lookup(const char *, CService&, int, bool) for additional parameter + * descriptions. + */ CService LookupNumeric(const char *pszName, int portDefault) { CService addr; @@ -231,22 +304,29 @@ enum class IntrRecvError { }; /** - * Read bytes from socket. This will either read the full number of bytes requested - * or return False on error or timeout. - * This function can be interrupted by calling InterruptSocks5() + * Try to read a specified number of bytes from a socket. Please read the "see + * also" section for more detail. * - * @param data Buffer to receive into - * @param len Length of data to receive - * @param timeout Timeout in milliseconds for receive operation + * @param data The buffer where the read bytes should be stored. + * @param len The number of bytes to read into the specified buffer. + * @param timeout The total timeout in milliseconds for this read. + * @param hSocket The socket (has to be in non-blocking mode) from which to read + * bytes. * - * @note This function requires that hSocket is in non-blocking mode. + * @returns An IntrRecvError indicating the resulting status of this read. + * IntrRecvError::OK only if all of the specified number of bytes were + * read. + * + * @see This function can be interrupted by calling InterruptSocks5(bool). + * Sockets can be made non-blocking with SetSocketNonBlocking(const + * SOCKET&, bool). */ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, const SOCKET& hSocket) { int64_t curTime = GetTimeMillis(); int64_t endTime = curTime + timeout; - // Maximum time to wait in one select call. It will take up until this time (in millis) - // to break off in case of an interruption. + // Maximum time to wait for I/O readiness. It will take up until this time + // (in millis) to break off in case of an interruption. const int64_t maxWait = 1000; while (len > 0 && curTime < endTime) { ssize_t ret = recv(hSocket, (char*)data, len, 0); // Optimistically try the recv first @@ -261,11 +341,13 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, c if (!IsSelectableSocket(hSocket)) { return IntrRecvError::NetworkError; } + // Only wait at most maxWait milliseconds at a time, unless + // we're approaching the end of the specified total timeout int timeout_ms = std::min(endTime - curTime, maxWait); #ifdef USE_POLL struct pollfd pollfd = {}; pollfd.fd = hSocket; - pollfd.events = POLLIN | POLLOUT; + pollfd.events = POLLIN; int nRet = poll(&pollfd, 1, timeout_ms); #else struct timeval tval = MillisToTimeval(timeout_ms); @@ -320,7 +402,24 @@ static std::string Socks5ErrorString(uint8_t err) } } -/** Connect using SOCKS5 (as described in RFC1928) */ +/** + * Connect to a specified destination service through an already connected + * SOCKS5 proxy. + * + * @param strDest The destination fully-qualified domain name. + * @param port The destination port. + * @param auth The credentials with which to authenticate with the specified + * SOCKS5 proxy. + * @param hSocket The SOCKS5 proxy socket. + * + * @returns Whether or not the operation succeeded. + * + * @note The specified SOCKS5 proxy socket must already be connected to the + * SOCKS5 proxy. + * + * @see <a href="https://www.ietf.org/rfc/rfc1928.txt">RFC1928: SOCKS Protocol + * Version 5</a> + */ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials *auth, const SOCKET& hSocket) { IntrRecvError recvr; @@ -328,15 +427,15 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials if (strDest.size() > 255) { return error("Hostname too long"); } - // Accepted authentication methods + // Construct the version identifier/method selection message std::vector<uint8_t> vSocks5Init; - vSocks5Init.push_back(SOCKSVersion::SOCKS5); + vSocks5Init.push_back(SOCKSVersion::SOCKS5); // We want the SOCK5 protocol if (auth) { - vSocks5Init.push_back(0x02); // Number of methods + vSocks5Init.push_back(0x02); // 2 method identifiers follow... vSocks5Init.push_back(SOCKS5Method::NOAUTH); vSocks5Init.push_back(SOCKS5Method::USER_PASS); } else { - vSocks5Init.push_back(0x01); // Number of methods + vSocks5Init.push_back(0x01); // 1 method identifier follows... vSocks5Init.push_back(SOCKS5Method::NOAUTH); } ssize_t ret = send(hSocket, (const char*)vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL); @@ -440,8 +539,16 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials return true; } +/** + * Try to create a socket file descriptor with specific properties in the + * communications domain (address family) of the specified service. + * + * For details on the desired properties, see the inline comments in the source + * code. + */ SOCKET CreateSocket(const CService &addrConnect) { + // Create a sockaddr from the specified service. struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); if (!addrConnect.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { @@ -449,10 +556,13 @@ SOCKET CreateSocket(const CService &addrConnect) return INVALID_SOCKET; } + // Create a TCP socket in the address family of the specified service. SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP); if (hSocket == INVALID_SOCKET) return INVALID_SOCKET; + // Ensure that waiting for I/O on this socket won't result in undefined + // behavior. if (!IsSelectableSocket(hSocket)) { CloseSocket(hSocket); LogPrintf("Cannot create connection: non-selectable socket created (fd >= FD_SETSIZE ?)\n"); @@ -461,17 +571,18 @@ SOCKET CreateSocket(const CService &addrConnect) #ifdef SO_NOSIGPIPE int set = 1; - // Different way of disabling SIGPIPE on BSD + // Set the no-sigpipe option on the socket for BSD systems, other UNIXes + // should use the MSG_NOSIGNAL flag for every send. setsockopt(hSocket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int)); #endif - //Disable Nagle's algorithm + // Set the no-delay option (disable Nagle's algorithm) on the TCP socket. SetSocketNoDelay(hSocket); - // Set to non-blocking + // Set the non-blocking option on the socket. if (!SetSocketNonBlocking(hSocket, true)) { CloseSocket(hSocket); - LogPrintf("ConnectSocketDirectly: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError())); + LogPrintf("CreateSocket: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError())); } return hSocket; } @@ -486,8 +597,21 @@ static void LogConnectFailure(bool manual_connection, const char* fmt, const Arg } } +/** + * Try to connect to the specified service on the specified socket. + * + * @param addrConnect The service to which to connect. + * @param hSocket The socket on which to connect. + * @param nTimeout Wait this many milliseconds for the connection to be + * established. + * @param manual_connection Whether or not the connection was manually requested + * (e.g. thru the addnode RPC) + * + * @returns Whether or not a connection was successfully made. + */ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, int nTimeout, bool manual_connection) { + // Create a sockaddr from the specified service. struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); if (hSocket == INVALID_SOCKET) { @@ -498,12 +622,17 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i LogPrintf("Cannot connect to %s: unsupported network\n", addrConnect.ToString()); return false; } + + // Connect to the addrConnect service on the hSocket socket. if (connect(hSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR) { int nErr = WSAGetLastError(); // WSAEINVAL is here because some legacy version of winsock uses it if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) { + // Connection didn't actually fail, but is being established + // asynchronously. Thus, use async I/O api (select/poll) + // synchronously to check for successful connection with a timeout. #ifdef USE_POLL struct pollfd pollfd = {}; pollfd.fd = hSocket; @@ -516,6 +645,10 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i FD_SET(hSocket, &fdset); int nRet = select(hSocket + 1, nullptr, &fdset, nullptr, &timeout); #endif + // Upon successful completion, both select and poll return the total + // number of file descriptors that have been selected. A value of 0 + // indicates that the call timed out and no file descriptors have + // been selected. if (nRet == 0) { LogPrint(BCLog::NET, "connection to %s timeout\n", addrConnect.ToString()); @@ -526,6 +659,11 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i LogPrintf("select() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); return false; } + + // Even if the select/poll was successful, the connect might not + // have been successful. The reason for this failure is hidden away + // in the SO_ERROR for the socket in modern systems. We read it into + // nRet here. socklen_t nRetSize = sizeof(nRet); if (getsockopt(hSocket, SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&nRet, &nRetSize) == SOCKET_ERROR) { @@ -569,6 +707,22 @@ bool GetProxy(enum Network net, proxyType &proxyInfoOut) { return true; } +/** + * Set the name proxy to use for all connections to nodes specified by a + * hostname. After setting this proxy, connecting to a node sepcified by a + * hostname won't result in a local lookup of said hostname, rather, connect to + * the node by asking the name proxy for a proxy connection to the hostname, + * effectively delegating the hostname lookup to the specified proxy. + * + * This delegation increases privacy for those who set the name proxy as they no + * longer leak their external hostname queries to their DNS servers. + * + * @returns Whether or not the operation succeeded. + * + * @note SOCKS5's support for UDP-over-SOCKS5 has been considered, but no SOCK5 + * server in common use (most notably Tor) actually implements UDP + * support, and a DNS resolver is beyond the scope of this project. + */ bool SetNameProxy(const proxyType &addrProxy) { if (!addrProxy.IsValid()) return false; @@ -599,6 +753,21 @@ bool IsProxy(const CNetAddr &addr) { return false; } +/** + * Connect to a specified destination service through a SOCKS5 proxy by first + * connecting to the SOCKS5 proxy. + * + * @param proxy The SOCKS5 proxy. + * @param strDest The destination service to which to connect. + * @param port The destination port. + * @param hSocket The socket on which to connect to the SOCKS5 proxy. + * @param nTimeout Wait this many milliseconds for the connection to the SOCKS5 + * proxy to be established. + * @param outProxyConnectionFailed[out] Whether or not the connection to the + * SOCKS5 proxy failed. + * + * @returns Whether or not the operation succeeded. + */ bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int port, const SOCKET& hSocket, int nTimeout, bool *outProxyConnectionFailed) { // first connect to proxy server @@ -623,6 +792,17 @@ bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int return true; } +/** + * Parse and resolve a specified subnet string into the appropriate internal + * representation. + * + * @param pszName A string representation of a subnet of the form `network + * address [ "/", ( CIDR-style suffix | netmask ) ]`(e.g. + * `2001:db8::/32`, `192.0.2.0/255.255.255.0`, or `8.8.8.8`). + * @param ret The resulting internal representation of a subnet. + * + * @returns Whether the operation succeeded or not. + */ bool LookupSubNet(const char* pszName, CSubNet& ret) { std::string strSubnet(pszName); @@ -630,6 +810,8 @@ bool LookupSubNet(const char* pszName, CSubNet& ret) std::vector<CNetAddr> vIP; std::string strAddress = strSubnet.substr(0, slash); + // TODO: Use LookupHost(const char *, CNetAddr&, bool) instead to just get + // one CNetAddr. if (LookupHost(strAddress.c_str(), vIP, 1, false)) { CNetAddr network = vIP[0]; @@ -637,8 +819,8 @@ bool LookupSubNet(const char* pszName, CSubNet& ret) { std::string strNetmask = strSubnet.substr(slash + 1); int32_t n; - // IPv4 addresses start at offset 12, and first 12 bytes must match, so just offset n - if (ParseInt32(strNetmask, &n)) { // If valid number, assume /24 syntax + if (ParseInt32(strNetmask, &n)) { + // If valid number, assume CIDR variable-length subnet masking ret = CSubNet(network, n); return ret.IsValid(); } diff --git a/src/netbase.h b/src/netbase.h index 708df5b8e2..313a575687 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -37,7 +37,7 @@ public: bool randomize_credentials; }; -enum Network ParseNetwork(std::string net); +enum Network ParseNetwork(const std::string& net); std::string GetNetworkName(enum Network net); bool SetProxy(enum Network net, const proxyType &addrProxy); bool GetProxy(enum Network net, proxyType &proxyInfoOut); diff --git a/src/node/coin.cpp b/src/node/coin.cpp index bb98e63f3a..ad8d1d3af4 100644 --- a/src/node/coin.cpp +++ b/src/node/coin.cpp @@ -10,8 +10,7 @@ void FindCoins(std::map<COutPoint, Coin>& coins) { LOCK2(cs_main, ::mempool.cs); - assert(pcoinsTip); - CCoinsViewCache& chain_view = *::pcoinsTip; + CCoinsViewCache& chain_view = ::ChainstateActive().CoinsTip(); CCoinsViewMemPool mempool_view(&chain_view, ::mempool); for (auto& coin : coins) { if (!mempool_view.GetCoin(coin.first, coin.second)) { diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp new file mode 100644 index 0000000000..e1891b9898 --- /dev/null +++ b/src/node/coinstats.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2019 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 <node/coinstats.h> + +#include <amount.h> +#include <coins.h> +#include <chain.h> +#include <hash.h> +#include <serialize.h> +#include <validation.h> +#include <uint256.h> +#include <util/system.h> + +#include <map> + +#include <boost/thread.hpp> + + +static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs) +{ + assert(!outputs.empty()); + ss << hash; + ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase ? 1u : 0u); + stats.nTransactions++; + for (const auto& output : outputs) { + ss << VARINT(output.first + 1); + ss << output.second.out.scriptPubKey; + ss << VARINT(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED); + stats.nTransactionOutputs++; + stats.nTotalAmount += output.second.out.nValue; + stats.nBogoSize += 32 /* txid */ + 4 /* vout index */ + 4 /* height + coinbase */ + 8 /* amount */ + + 2 /* scriptPubKey len */ + output.second.out.scriptPubKey.size() /* scriptPubKey */; + } + ss << VARINT(0u); +} + +//! Calculate statistics about the unspent transaction output set +bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) +{ + std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor()); + assert(pcursor); + + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + stats.hashBlock = pcursor->GetBestBlock(); + { + LOCK(cs_main); + stats.nHeight = LookupBlockIndex(stats.hashBlock)->nHeight; + } + ss << stats.hashBlock; + uint256 prevkey; + std::map<uint32_t, Coin> outputs; + while (pcursor->Valid()) { + boost::this_thread::interruption_point(); + COutPoint key; + Coin coin; + if (pcursor->GetKey(key) && pcursor->GetValue(coin)) { + if (!outputs.empty() && key.hash != prevkey) { + ApplyStats(stats, ss, prevkey, outputs); + outputs.clear(); + } + prevkey = key.hash; + outputs[key.n] = std::move(coin); + } else { + return error("%s: unable to read value", __func__); + } + pcursor->Next(); + } + if (!outputs.empty()) { + ApplyStats(stats, ss, prevkey, outputs); + } + stats.hashSerialized = ss.GetHash(); + stats.nDiskSize = view->EstimateSize(); + return true; +} diff --git a/src/node/coinstats.h b/src/node/coinstats.h new file mode 100644 index 0000000000..7c11aab8bd --- /dev/null +++ b/src/node/coinstats.h @@ -0,0 +1,33 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2019 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_NODE_COINSTATS_H +#define BITCOIN_NODE_COINSTATS_H + +#include <amount.h> +#include <uint256.h> + +#include <cstdint> + +class CCoinsView; + +struct CCoinsStats +{ + int nHeight; + uint256 hashBlock; + uint64_t nTransactions; + uint64_t nTransactionOutputs; + uint64_t nBogoSize; + uint256 hashSerialized; + uint64_t nDiskSize; + CAmount nTotalAmount; + + CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nBogoSize(0), nDiskSize(0), nTotalAmount(0) {} +}; + +//! Calculate statistics about the unspent transaction output set +bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats); + +#endif // BITCOIN_NODE_COINSTATS_H diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 5ffb15ed3c..7e8291ddc8 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -5,6 +5,7 @@ #include <consensus/validation.h> #include <net.h> +#include <net_processing.h> #include <txmempool.h> #include <util/validation.h> #include <validation.h> @@ -13,26 +14,33 @@ #include <future> -TransactionError BroadcastTransaction(const CTransactionRef tx, uint256& hashTx, std::string& err_string, const CAmount& highfee) +TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback) { + // BroadcastTransaction can be called by either sendrawtransaction RPC or wallet RPCs. + // g_connman is assigned both before chain clients and before RPC server is accepting calls, + // and reset after chain clients and RPC sever are stopped. g_connman should never be null here. + assert(g_connman); std::promise<void> promise; - hashTx = tx->GetHash(); + uint256 hashTx = tx->GetHash(); + bool callback_set = false; { // cs_main scope LOCK(cs_main); - CCoinsViewCache &view = *pcoinsTip; - bool fHaveChain = false; - for (size_t o = 0; !fHaveChain && o < tx->vout.size(); o++) { + // If the transaction is already confirmed in the chain, don't do anything + // and return early. + CCoinsViewCache &view = ::ChainstateActive().CoinsTip(); + for (size_t o = 0; o < tx->vout.size(); o++) { const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o)); - fHaveChain = !existingCoin.IsSpent(); + // IsSpent doesnt mean the coin is spent, it means the output doesnt' exist. + // So if the output does exist, then this transaction exists in the chain. + if (!existingCoin.IsSpent()) return TransactionError::ALREADY_IN_CHAIN; } - bool fHaveMempool = mempool.exists(hashTx); - if (!fHaveMempool && !fHaveChain) { - // push to local node and sync with wallets + if (!mempool.exists(hashTx)) { + // Transaction is not already in the mempool. Submit it. CValidationState state; bool fMissingInputs; if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs, - nullptr /* plTxnReplaced */, false /* bypass_limits */, highfee)) { + nullptr /* plTxnReplaced */, false /* bypass_limits */, max_tx_fee)) { if (state.IsInvalid()) { err_string = FormatStateMessage(state); return TransactionError::MEMPOOL_REJECTED; @@ -43,36 +51,37 @@ TransactionError BroadcastTransaction(const CTransactionRef tx, uint256& hashTx, err_string = FormatStateMessage(state); return TransactionError::MEMPOOL_ERROR; } - } else { - // If wallet is enabled, ensure that the wallet has been made aware - // of the new transaction prior to returning. This prevents a race - // where a user might call sendrawtransaction with a transaction - // to/from their wallet, immediately call some wallet RPC, and get - // a stale result because callbacks have not yet been processed. + } + + // Transaction was accepted to the mempool. + + if (wait_callback) { + // For transactions broadcast from outside the wallet, make sure + // that the wallet has been notified of the transaction before + // continuing. + // + // This prevents a race where a user might call sendrawtransaction + // with a transaction to/from their wallet, immediately call some + // wallet RPC, and get a stale result because callbacks have not + // yet been processed. CallFunctionInValidationInterfaceQueue([&promise] { promise.set_value(); }); + callback_set = true; } - } else if (fHaveChain) { - return TransactionError::ALREADY_IN_CHAIN; - } else { - // Make sure we don't block forever if re-sending - // a transaction already in mempool. - promise.set_value(); } } // cs_main - promise.get_future().wait(); - - if (!g_connman) { - return TransactionError::P2P_DISABLED; + if (callback_set) { + // Wait until Validation Interface clients have been notified of the + // transaction entering the mempool. + promise.get_future().wait(); } - CInv inv(MSG_TX, hashTx); - g_connman->ForEachNode([&inv](CNode* pnode) { - pnode->PushInventory(inv); - }); + if (relay) { + RelayTransaction(hashTx, *g_connman); + } return TransactionError::OK; } diff --git a/src/node/transaction.h b/src/node/transaction.h index 51033f94e5..cf64fc28d9 100644 --- a/src/node/transaction.h +++ b/src/node/transaction.h @@ -11,14 +11,21 @@ #include <util/error.h> /** - * Broadcast a transaction + * Submit a transaction to the mempool and (optionally) relay it to all P2P peers. + * + * Mempool submission can be synchronous (will await mempool entry notification + * over the CValidationInterface) or asynchronous (will submit and not wait for + * notification), depending on the value of wait_callback. wait_callback MUST + * NOT be set while cs_main, cs_mempool or cs_wallet are held to avoid + * deadlock. * * @param[in] tx the transaction to broadcast - * @param[out] &txid the txid of the transaction, if successfully broadcast * @param[out] &err_string reference to std::string to fill with error string if available - * @param[in] highfee Reject txs with fees higher than this (if 0, accept any fee) + * @param[in] max_tx_fee reject txs with fees higher than this (if 0, accept any fee) + * @param[in] relay flag if both mempool insertion and p2p relay are requested + * @param[in] wait_callback, wait until callbacks have been processed to avoid stale result due to a sequentially RPC. * return error */ -NODISCARD TransactionError BroadcastTransaction(CTransactionRef tx, uint256& txid, std::string& err_string, const CAmount& highfee); +NODISCARD TransactionError BroadcastTransaction(CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback); #endif // BITCOIN_NODE_TRANSACTION_H diff --git a/src/noui.cpp b/src/noui.cpp index caab9f326e..c07939cc79 100644 --- a/src/noui.cpp +++ b/src/noui.cpp @@ -13,6 +13,12 @@ #include <string> #include <boost/signals2/connection.hpp> +#include <boost/signals2/signal.hpp> + +/** Store connections so we can disconnect them when suppressing output */ +boost::signals2::connection noui_ThreadSafeMessageBoxConn; +boost::signals2::connection noui_ThreadSafeQuestionConn; +boost::signals2::connection noui_InitMessageConn; bool noui_ThreadSafeMessageBox(const std::string& message, const std::string& caption, unsigned int style) { @@ -57,7 +63,39 @@ void noui_InitMessage(const std::string& message) void noui_connect() { - uiInterface.ThreadSafeMessageBox_connect(noui_ThreadSafeMessageBox); - uiInterface.ThreadSafeQuestion_connect(noui_ThreadSafeQuestion); - uiInterface.InitMessage_connect(noui_InitMessage); + noui_ThreadSafeMessageBoxConn = uiInterface.ThreadSafeMessageBox_connect(noui_ThreadSafeMessageBox); + noui_ThreadSafeQuestionConn = uiInterface.ThreadSafeQuestion_connect(noui_ThreadSafeQuestion); + noui_InitMessageConn = uiInterface.InitMessage_connect(noui_InitMessage); +} + +bool noui_ThreadSafeMessageBoxSuppressed(const std::string& message, const std::string& caption, unsigned int style) +{ + return false; +} + +bool noui_ThreadSafeQuestionSuppressed(const std::string& /* ignored interactive message */, const std::string& message, const std::string& caption, unsigned int style) +{ + return false; } + +void noui_InitMessageSuppressed(const std::string& message) +{ +} + +void noui_suppress() +{ + noui_ThreadSafeMessageBoxConn.disconnect(); + noui_ThreadSafeQuestionConn.disconnect(); + noui_InitMessageConn.disconnect(); + noui_ThreadSafeMessageBoxConn = uiInterface.ThreadSafeMessageBox_connect(noui_ThreadSafeMessageBoxSuppressed); + noui_ThreadSafeQuestionConn = uiInterface.ThreadSafeQuestion_connect(noui_ThreadSafeQuestionSuppressed); + noui_InitMessageConn = uiInterface.InitMessage_connect(noui_InitMessageSuppressed); +} + +void noui_reconnect() +{ + noui_ThreadSafeMessageBoxConn.disconnect(); + noui_ThreadSafeQuestionConn.disconnect(); + noui_InitMessageConn.disconnect(); + noui_connect(); +}
\ No newline at end of file diff --git a/src/noui.h b/src/noui.h index 79a79a9af2..854aeeacca 100644 --- a/src/noui.h +++ b/src/noui.h @@ -17,4 +17,10 @@ void noui_InitMessage(const std::string& message); /** Connect all bitcoind signal handlers */ void noui_connect(); +/** Suppress all bitcoind signal handlers. Used to suppress output during test runs that produce expected errors */ +void noui_suppress(); + +/** Reconnects the regular Non-GUI handlers after having used noui_suppress */ +void noui_reconnect(); + #endif // BITCOIN_NOUI_H diff --git a/src/obj-test/.gitignore b/src/obj-test/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/src/obj-test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/src/obj/.gitignore b/src/obj/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/src/obj/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/src/outputtype.cpp b/src/outputtype.cpp index 73ffb801f2..bcaa05f4b6 100644 --- a/src/outputtype.cpp +++ b/src/outputtype.cpp @@ -5,9 +5,10 @@ #include <outputtype.h> -#include <keystore.h> #include <pubkey.h> #include <script/script.h> +#include <script/sign.h> +#include <script/signingprovider.h> #include <script/standard.h> #include <assert.h> @@ -73,7 +74,7 @@ std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key) } } -CTxDestination AddAndGetDestinationForScript(CKeyStore& keystore, const CScript& script, OutputType type) +CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, const CScript& script, OutputType type) { // Add script to keystore keystore.AddCScript(script); @@ -98,4 +99,3 @@ CTxDestination AddAndGetDestinationForScript(CKeyStore& keystore, const CScript& default: assert(false); } } - diff --git a/src/outputtype.h b/src/outputtype.h index 6c30fd1950..6acbaa2f3e 100644 --- a/src/outputtype.h +++ b/src/outputtype.h @@ -7,7 +7,7 @@ #define BITCOIN_OUTPUTTYPE_H #include <attributes.h> -#include <keystore.h> +#include <script/signingprovider.h> #include <script/standard.h> #include <string> @@ -44,7 +44,7 @@ std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key); * This function will automatically add the script (and any other * necessary scripts) to the keystore. */ -CTxDestination AddAndGetDestinationForScript(CKeyStore& keystore, const CScript& script, OutputType); +CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, const CScript& script, OutputType); #endif // BITCOIN_OUTPUTTYPE_H diff --git a/src/policy/fees.h b/src/policy/fees.h index 6e61f76178..16683bf5ad 100644 --- a/src/policy/fees.h +++ b/src/policy/fees.h @@ -43,7 +43,6 @@ enum class FeeReason { PAYTXFEE, FALLBACK, REQUIRED, - MAXTXFEE, }; /* Used to determine type of fee estimation requested */ diff --git a/src/protocol.h b/src/protocol.h index a790a06906..91d043947b 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -261,9 +261,6 @@ enum ServiceFlags : uint64_t { // NODE_WITNESS indicates that a node can be asked for blocks and transactions including // witness data. NODE_WITNESS = (1 << 3), - // NODE_XTHIN means the node supports Xtreme Thinblocks - // If this is turned off then the node will not service nor make xthin requests - NODE_XTHIN = (1 << 4), // NODE_NETWORK_LIMITED means the same as NODE_NETWORK with the limitation of only // serving the last 288 (2 day) blocks // See BIP159 for details on how this is implemented. diff --git a/src/psbt.cpp b/src/psbt.cpp index d765133190..fe74002e82 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -212,6 +212,25 @@ bool PSBTInputSigned(const PSBTInput& input) return !input.final_script_sig.empty() || !input.final_script_witness.IsNull(); } +void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index) +{ + const CTxOut& out = psbt.tx->vout.at(index); + PSBTOutput& psbt_out = psbt.outputs.at(index); + + // Fill a SignatureData with output info + SignatureData sigdata; + psbt_out.FillSignatureData(sigdata); + + // Construct a would-be spend of this output, to update sigdata with. + // Note that ProduceSignature is used to fill in metadata (not actual signatures), + // so provider does not need to provide any private keys (it can be a HidingSigningProvider). + MutableTransactionSignatureCreator creator(psbt.tx.get_ptr(), /* index */ 0, out.nValue, SIGHASH_ALL); + ProduceSignature(provider, creator, out.scriptPubKey, sigdata); + + // Put redeem_script, witness_script, key paths, into PSBTOutput. + psbt_out.FromSignatureData(sigdata); +} + bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash, SignatureData* out_sigdata, bool use_dummy) { PSBTInput& input = psbt.inputs.at(index); diff --git a/src/psbt.h b/src/psbt.h index 1bc1e91a84..6d77db0c6f 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -12,6 +12,7 @@ #include <primitives/transaction.h> #include <pubkey.h> #include <script/sign.h> +#include <script/signingprovider.h> // Magic bytes static constexpr uint8_t PSBT_MAGIC_BYTES[5] = {'p', 's', 'b', 't', 0xff}; @@ -565,6 +566,12 @@ bool PSBTInputSigned(const PSBTInput& input); /** Signs a PSBTInput, verifying that all provided data matches what is being signed. */ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool use_dummy = false); +/** Updates a PSBTOutput with information from provider. + * + * This fills in the redeem_script, witness_script, and hd_keypaths where possible. + */ +void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index); + /** * Finalizes a PSBT if possible, combining partial signatures. * diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index fa6c9c9f7a..131cceccbe 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -10,6 +10,8 @@ #include <key_io.h> #include <wallet/wallet.h> +#include <algorithm> + #include <QFont> #include <QDebug> @@ -86,18 +88,18 @@ public: QString::fromStdString(EncodeDestination(address.dest)))); } } - // qLowerBound() and qUpperBound() require our cachedAddressTable list to be sorted in asc order + // std::lower_bound() and std::upper_bound() require our cachedAddressTable list to be sorted in asc order // Even though the map is already sorted this re-sorting step is needed because the originating map // is sorted by binary address, not by base58() address. - qSort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan()); + std::sort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan()); } void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status) { // Find address / label in model - QList<AddressTableEntry>::iterator lower = qLowerBound( + QList<AddressTableEntry>::iterator lower = std::lower_bound( cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); - QList<AddressTableEntry>::iterator upper = qUpperBound( + QList<AddressTableEntry>::iterator upper = std::upper_bound( cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); int lowerIndex = (lower - cachedAddressTable.begin()); int upperIndex = (upper - cachedAddressTable.begin()); @@ -358,12 +360,15 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con return QString(); } } + + // Add entry + walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel, "send"); } else if(type == Receive) { // Generate a new address to associate with given label - CPubKey newKey; - if(!walletModel->wallet().getKeyFromPool(false /* internal */, newKey)) + CTxDestination dest; + if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest)) { WalletModel::UnlockContext ctx(walletModel->requestUnlock()); if(!ctx.isValid()) @@ -372,23 +377,18 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con editStatus = WALLET_UNLOCK_FAILURE; return QString(); } - if(!walletModel->wallet().getKeyFromPool(false /* internal */, newKey)) + if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest)) { editStatus = KEY_GENERATION_FAILURE; return QString(); } } - walletModel->wallet().learnRelatedScripts(newKey, address_type); - strAddress = EncodeDestination(GetDestinationForKey(newKey, address_type)); + strAddress = EncodeDestination(dest); } else { return QString(); } - - // Add entry - walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel, - (type == Send ? "send" : "receive")); return QString::fromStdString(strAddress); } diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index a89a15bc9d..c9f17d12ec 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -18,12 +18,13 @@ #include <QMessageBox> #include <QPushButton> -AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent) : +AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureString* passphrase_out) : QDialog(parent), ui(new Ui::AskPassphraseDialog), mode(_mode), model(nullptr), - fCapsLock(false) + fCapsLock(false), + m_passphrase_out(passphrase_out) { ui->setupUi(this); @@ -90,7 +91,7 @@ void AskPassphraseDialog::setModel(WalletModel *_model) void AskPassphraseDialog::accept() { SecureString oldpass, newpass1, newpass2; - if(!model) + if (!model && mode != Encrypt) return; oldpass.reserve(MAX_PASSPHRASE_SIZE); newpass1.reserve(MAX_PASSPHRASE_SIZE); @@ -119,24 +120,33 @@ void AskPassphraseDialog::accept() { if(newpass1 == newpass2) { - if(model->setWalletEncrypted(true, newpass1)) - { - QMessageBox::warning(this, tr("Wallet encrypted"), + QString encryption_reminder = tr("Remember that encrypting your wallet cannot fully protect " + "your bitcoins from being stolen by malware infecting your computer."); + if (m_passphrase_out) { + m_passphrase_out->assign(newpass1); + QMessageBox::warning(this, tr("Wallet to be encrypted"), "<qt>" + - tr("Your wallet is now encrypted. " - "Remember that encrypting your wallet cannot fully protect " - "your bitcoins from being stolen by malware infecting your computer.") + - "<br><br><b>" + - tr("IMPORTANT: Any previous backups you have made of your wallet file " - "should be replaced with the newly generated, encrypted wallet file. " - "For security reasons, previous backups of the unencrypted wallet file " - "will become useless as soon as you start using the new, encrypted wallet.") + + tr("Your wallet is about to be encrypted. ") + encryption_reminder + "</b></qt>"); - } - else - { - QMessageBox::critical(this, tr("Wallet encryption failed"), - tr("Wallet encryption failed due to an internal error. Your wallet was not encrypted.")); + } else { + assert(model != nullptr); + if(model->setWalletEncrypted(true, newpass1)) + { + QMessageBox::warning(this, tr("Wallet encrypted"), + "<qt>" + + tr("Your wallet is now encrypted. ") + encryption_reminder + + "<br><br><b>" + + tr("IMPORTANT: Any previous backups you have made of your wallet file " + "should be replaced with the newly generated, encrypted wallet file. " + "For security reasons, previous backups of the unencrypted wallet file " + "will become useless as soon as you start using the new, encrypted wallet.") + + "</b></qt>"); + } + else + { + QMessageBox::critical(this, tr("Wallet encryption failed"), + tr("Wallet encryption failed due to an internal error. Your wallet was not encrypted.")); + } } QDialog::accept(); // Success } diff --git a/src/qt/askpassphrasedialog.h b/src/qt/askpassphrasedialog.h index ac31569f63..bdfd3fb9a0 100644 --- a/src/qt/askpassphrasedialog.h +++ b/src/qt/askpassphrasedialog.h @@ -7,6 +7,8 @@ #include <QDialog> +#include <support/allocators/secure.h> + class WalletModel; namespace Ui { @@ -27,7 +29,7 @@ public: Decrypt /**< Ask passphrase and decrypt wallet */ }; - explicit AskPassphraseDialog(Mode mode, QWidget *parent); + explicit AskPassphraseDialog(Mode mode, QWidget *parent, SecureString* passphrase_out = nullptr); ~AskPassphraseDialog(); void accept(); @@ -39,6 +41,7 @@ private: Mode mode; WalletModel *model; bool fCapsLock; + SecureString* m_passphrase_out; private Q_SLOTS: void textChanged(); diff --git a/src/qt/bantablemodel.cpp b/src/qt/bantablemodel.cpp index 8a6b205cd8..efc726e09e 100644 --- a/src/qt/bantablemodel.cpp +++ b/src/qt/bantablemodel.cpp @@ -10,6 +10,8 @@ #include <sync.h> #include <util/time.h> +#include <algorithm> + #include <QDebug> #include <QList> @@ -61,7 +63,7 @@ public: if (sortColumn >= 0) // sort cachedBanlist (use stable sort to prevent rows jumping around unnecessarily) - qStableSort(cachedBanlist.begin(), cachedBanlist.end(), BannedNodeLessThan(sortColumn, sortOrder)); + std::stable_sort(cachedBanlist.begin(), cachedBanlist.end(), BannedNodeLessThan(sortColumn, sortOrder)); } int size() const diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 2fdbcca043..adc19df935 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -10,8 +10,8 @@ #include <qt/bitcoingui.h> #include <chainparams.h> -#include <qt/clientmodel.h> #include <fs.h> +#include <qt/clientmodel.h> #include <qt/guiconstants.h> #include <qt/guiutil.h> #include <qt/intro.h> @@ -25,20 +25,18 @@ #ifdef ENABLE_WALLET #include <qt/paymentserver.h> #include <qt/walletcontroller.h> -#endif +#include <qt/walletmodel.h> +#endif // ENABLE_WALLET #include <interfaces/handler.h> #include <interfaces/node.h> #include <noui.h> -#include <util/threadnames.h> #include <ui_interface.h> #include <uint256.h> #include <util/system.h> +#include <util/threadnames.h> #include <memory> -#include <stdint.h> - -#include <boost/thread.hpp> #include <QApplication> #include <QDebug> @@ -171,8 +169,11 @@ void BitcoinCore::shutdown() } } -BitcoinApplication::BitcoinApplication(interfaces::Node& node, int &argc, char **argv): - QApplication(argc, argv), +static int qt_argc = 1; +static const char* qt_argv = "bitcoin-qt"; + +BitcoinApplication::BitcoinApplication(interfaces::Node& node): + QApplication(qt_argc, const_cast<char **>(&qt_argv)), coreThread(nullptr), m_node(node), optionsModel(nullptr), @@ -210,12 +211,6 @@ BitcoinApplication::~BitcoinApplication() delete window; window = nullptr; -#ifdef ENABLE_WALLET - delete paymentServer; - paymentServer = nullptr; - delete m_wallet_controller; - m_wallet_controller = nullptr; -#endif delete optionsModel; optionsModel = nullptr; delete platformStyle; @@ -331,24 +326,21 @@ void BitcoinApplication::initializeResult(bool success) { // Log this only after AppInitMain finishes, as then logging setup is guaranteed complete qInfo() << "Platform customization:" << platformStyle->getName(); -#ifdef ENABLE_WALLET - m_wallet_controller = new WalletController(m_node, platformStyle, optionsModel, this); -#ifdef ENABLE_BIP70 - PaymentServer::LoadRootCAs(); -#endif - if (paymentServer) { - paymentServer->setOptionsModel(optionsModel); -#ifdef ENABLE_BIP70 - connect(m_wallet_controller, &WalletController::coinsSent, paymentServer, &PaymentServer::fetchPaymentACK); -#endif - } -#endif - clientModel = new ClientModel(m_node, optionsModel); window->setClientModel(clientModel); #ifdef ENABLE_WALLET - window->setWalletController(m_wallet_controller); + if (WalletModel::isWalletEnabled()) { + m_wallet_controller = new WalletController(m_node, platformStyle, optionsModel, this); + window->setWalletController(m_wallet_controller); + if (paymentServer) { + paymentServer->setOptionsModel(optionsModel); +#ifdef ENABLE_BIP70 + PaymentServer::LoadRootCAs(); + connect(m_wallet_controller, &WalletController::coinsSent, paymentServer, &PaymentServer::fetchPaymentACK); #endif + } + } +#endif // ENABLE_WALLET // If -min option passed, start window minimized (iconified) or minimized to tray if (!gArgs.GetBoolArg("-min", false)) { @@ -402,18 +394,17 @@ WId BitcoinApplication::getMainWinId() const static void SetupUIArgs() { #if defined(ENABLE_WALLET) && defined(ENABLE_BIP70) - gArgs.AddArg("-allowselfsignedrootcertificates", strprintf("Allow self signed root certificates (default: %u)", DEFAULT_SELFSIGNED_ROOTCERTS), true, OptionsCategory::GUI); + gArgs.AddArg("-allowselfsignedrootcertificates", strprintf("Allow self signed root certificates (default: %u)", DEFAULT_SELFSIGNED_ROOTCERTS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::GUI); #endif - gArgs.AddArg("-choosedatadir", strprintf("Choose data directory on startup (default: %u)", DEFAULT_CHOOSE_DATADIR), false, OptionsCategory::GUI); - gArgs.AddArg("-lang=<lang>", "Set language, for example \"de_DE\" (default: system locale)", false, OptionsCategory::GUI); - gArgs.AddArg("-min", "Start minimized", false, OptionsCategory::GUI); - gArgs.AddArg("-resetguisettings", "Reset all settings changed in the GUI", false, OptionsCategory::GUI); - gArgs.AddArg("-rootcertificates=<file>", "Set SSL root certificates for payment request (default: -system-)", false, OptionsCategory::GUI); - gArgs.AddArg("-splash", strprintf("Show splash screen on startup (default: %u)", DEFAULT_SPLASHSCREEN), false, OptionsCategory::GUI); - gArgs.AddArg("-uiplatform", strprintf("Select platform to customize UI for (one of windows, macosx, other; default: %s)", BitcoinGUI::DEFAULT_UIPLATFORM), true, OptionsCategory::GUI); + gArgs.AddArg("-choosedatadir", strprintf("Choose data directory on startup (default: %u)", DEFAULT_CHOOSE_DATADIR), ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + gArgs.AddArg("-lang=<lang>", "Set language, for example \"de_DE\" (default: system locale)", ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + gArgs.AddArg("-min", "Start minimized", ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + gArgs.AddArg("-resetguisettings", "Reset all settings changed in the GUI", ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + gArgs.AddArg("-rootcertificates=<file>", "Set SSL root certificates for payment request (default: -system-)", ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + gArgs.AddArg("-splash", strprintf("Show splash screen on startup (default: %u)", DEFAULT_SPLASHSCREEN), ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + gArgs.AddArg("-uiplatform", strprintf("Select platform to customize UI for (one of windows, macosx, other; default: %s)", BitcoinGUI::DEFAULT_UIPLATFORM), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::GUI); } -#ifndef BITCOIN_QT_TEST int GuiMain(int argc, char* argv[]) { #ifdef WIN32 @@ -445,10 +436,13 @@ int GuiMain(int argc, char* argv[]) QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); #endif - BitcoinApplication app(*node, argc, argv); + BitcoinApplication app(*node); // Register meta types used for QMetaObject::invokeMethod qRegisterMetaType< bool* >(); +#ifdef ENABLE_WALLET + qRegisterMetaType<WalletModel*>(); +#endif // Need to pass name here as CAmount is a typedef (see http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType) // IMPORTANT if it is no longer a typedef use the normal variant above qRegisterMetaType< CAmount >("CAmount"); @@ -460,8 +454,11 @@ int GuiMain(int argc, char* argv[]) SetupUIArgs(); std::string error; if (!node->parseParameters(argc, argv, error)) { - QMessageBox::critical(nullptr, QObject::tr(PACKAGE_NAME), - QObject::tr("Error parsing command line arguments: %1.").arg(QString::fromStdString(error))); + node->initError(strprintf("Error parsing command line arguments: %s\n", error)); + // Create a message box, because the gui has neither been created nor has subscribed to core signals + QMessageBox::critical(nullptr, PACKAGE_NAME, + // message can not be translated because translations have not been initialized + QString::fromStdString("Error parsing command line arguments: %1.").arg(QString::fromStdString(error))); return EXIT_FAILURE; } @@ -493,16 +490,17 @@ int GuiMain(int argc, char* argv[]) if (!Intro::pickDataDirectory(*node)) return EXIT_SUCCESS; - /// 6. Determine availability of data and blocks directory and parse bitcoin.conf + /// 6. Determine availability of data directory and parse bitcoin.conf /// - Do not call GetDataDir(true) before this step finishes - if (!fs::is_directory(GetDataDir(false))) - { - QMessageBox::critical(nullptr, QObject::tr(PACKAGE_NAME), + if (!CheckDataDirOption()) { + node->initError(strprintf("Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", ""))); + QMessageBox::critical(nullptr, PACKAGE_NAME, QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(gArgs.GetArg("-datadir", "")))); return EXIT_FAILURE; } if (!node->readConfigFiles(error)) { - QMessageBox::critical(nullptr, QObject::tr(PACKAGE_NAME), + node->initError(strprintf("Error reading configuration file: %s\n", error)); + QMessageBox::critical(nullptr, PACKAGE_NAME, QObject::tr("Error: Cannot parse configuration file: %1.").arg(QString::fromStdString(error))); return EXIT_FAILURE; } @@ -517,7 +515,8 @@ int GuiMain(int argc, char* argv[]) try { node->selectParams(gArgs.GetChainName()); } catch(std::exception &e) { - QMessageBox::critical(nullptr, QObject::tr(PACKAGE_NAME), QObject::tr("Error: %1").arg(e.what())); + node->initError(strprintf("%s\n", e.what())); + QMessageBox::critical(nullptr, PACKAGE_NAME, QObject::tr("Error: %1").arg(e.what())); return EXIT_FAILURE; } #ifdef ENABLE_WALLET @@ -544,8 +543,10 @@ int GuiMain(int argc, char* argv[]) // Start up the payment server early, too, so impatient users that click on // bitcoin: links repeatedly have their payment requests routed to this process: - app.createPaymentServer(); -#endif + if (WalletModel::isWalletEnabled()) { + app.createPaymentServer(); + } +#endif // ENABLE_WALLET /// 9. Main GUI initialization // Install global event filter that makes sure that long tooltips can be word-wrapped @@ -574,7 +575,7 @@ int GuiMain(int argc, char* argv[]) if (app.baseInitialize()) { app.requestInitialize(); #if defined(Q_OS_WIN) - WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely...").arg(QObject::tr(PACKAGE_NAME)), (HWND)app.getMainWinId()); + WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely...").arg(PACKAGE_NAME), (HWND)app.getMainWinId()); #endif app.exec(); app.requestShutdown(); @@ -593,4 +594,3 @@ int GuiMain(int argc, char* argv[]) } return rv; } -#endif // BITCOIN_QT_TEST diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h index 370712d953..3869193a3a 100644 --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -11,7 +11,6 @@ #include <QApplication> #include <memory> -#include <vector> class BitcoinGUI; class ClientModel; @@ -57,7 +56,7 @@ class BitcoinApplication: public QApplication { Q_OBJECT public: - explicit BitcoinApplication(interfaces::Node& node, int &argc, char **argv); + explicit BitcoinApplication(interfaces::Node& node); ~BitcoinApplication(); #ifdef ENABLE_WALLET diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc index fddc2a5685..037b23e4b2 100644 --- a/src/qt/bitcoin.qrc +++ b/src/qt/bitcoin.qrc @@ -2,7 +2,6 @@ <qresource prefix="/icons"> <file alias="bitcoin">res/icons/bitcoin.png</file> <file alias="address-book">res/icons/address-book.png</file> - <file alias="quit">res/icons/quit.png</file> <file alias="send">res/icons/send.png</file> <file alias="connect_0">res/icons/connect0.png</file> <file alias="connect_1">res/icons/connect1.png</file> @@ -20,7 +19,6 @@ <file alias="eye">res/icons/eye.png</file> <file alias="eye_minus">res/icons/eye_minus.png</file> <file alias="eye_plus">res/icons/eye_plus.png</file> - <file alias="options">res/icons/configure.png</file> <file alias="receiving_addresses">res/icons/receive.png</file> <file alias="editpaste">res/icons/editpaste.png</file> <file alias="editcopy">res/icons/editcopy.png</file> @@ -37,14 +35,6 @@ <file alias="tx_inout">res/icons/tx_inout.png</file> <file alias="lock_closed">res/icons/lock_closed.png</file> <file alias="lock_open">res/icons/lock_open.png</file> - <file alias="key">res/icons/key.png</file> - <file alias="filesave">res/icons/filesave.png</file> - <file alias="debugwindow">res/icons/debugwindow.png</file> - <file alias="open">res/icons/open.png</file> - <file alias="info">res/icons/info.png</file> - <file alias="about">res/icons/about.png</file> - <file alias="about_qt">res/icons/about_qt.png</file> - <file alias="verify">res/icons/verify.png</file> <file alias="warning">res/icons/warning.png</file> <file alias="fontbigger">res/icons/fontbigger.png</file> <file alias="fontsmaller">res/icons/fontsmaller.png</file> diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index 5854ade655..9fa49b87fa 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -6,6 +6,7 @@ #include <qt/bitcoinunits.h> #include <qt/guiconstants.h> +#include <qt/guiutil.h> #include <qt/qvaluecombobox.h> #include <QApplication> @@ -121,7 +122,7 @@ public: const QFontMetrics fm(fontMetrics()); int h = lineEdit()->minimumSizeHint().height(); - int w = fm.width(BitcoinUnits::format(BitcoinUnits::BTC, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways)); + int w = GUIUtil::TextWidth(fm, BitcoinUnits::format(BitcoinUnits::BTC, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways)); w += 2; // cursor blinking space QStyleOptionSpinBox opt; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index babb2ce518..de6e1ca00e 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -6,6 +6,7 @@ #include <qt/bitcoinunits.h> #include <qt/clientmodel.h> +#include <qt/createwalletdialog.h> #include <qt/guiconstants.h> #include <qt/guiutil.h> #include <qt/modaloverlay.h> @@ -36,14 +37,10 @@ #include <ui_interface.h> #include <util/system.h> -#include <iostream> -#include <memory> - #include <QAction> #include <QApplication> #include <QComboBox> #include <QDateTime> -#include <QDesktopWidget> #include <QDragEnterEvent> #include <QListWidget> #include <QMenu> @@ -51,6 +48,7 @@ #include <QMessageBox> #include <QMimeData> #include <QProgressDialog> +#include <QScreen> #include <QSettings> #include <QShortcut> #include <QStackedWidget> @@ -84,7 +82,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty QSettings settings; if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) { // Restore failed (perhaps missing setting), center the window - move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center()); + move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center()); } #ifdef ENABLE_WALLET @@ -251,7 +249,7 @@ void BitcoinGUI::createActions() sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2)); tabGroup->addAction(sendCoinsAction); - sendCoinsMenuAction = new QAction(platformStyle->TextColorIcon(":/icons/send"), sendCoinsAction->text(), this); + sendCoinsMenuAction = new QAction(sendCoinsAction->text(), this); sendCoinsMenuAction->setStatusTip(sendCoinsAction->statusTip()); sendCoinsMenuAction->setToolTip(sendCoinsMenuAction->statusTip()); @@ -262,7 +260,7 @@ void BitcoinGUI::createActions() receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3)); tabGroup->addAction(receiveCoinsAction); - receiveCoinsMenuAction = new QAction(platformStyle->TextColorIcon(":/icons/receiving_addresses"), receiveCoinsAction->text(), this); + receiveCoinsMenuAction = new QAction(receiveCoinsAction->text(), this); receiveCoinsMenuAction->setStatusTip(receiveCoinsAction->statusTip()); receiveCoinsMenuAction->setToolTip(receiveCoinsMenuAction->statusTip()); @@ -290,48 +288,48 @@ void BitcoinGUI::createActions() connect(historyAction, &QAction::triggered, this, &BitcoinGUI::gotoHistoryPage); #endif // ENABLE_WALLET - quitAction = new QAction(platformStyle->TextColorIcon(":/icons/quit"), tr("E&xit"), this); + quitAction = new QAction(tr("E&xit"), this); quitAction->setStatusTip(tr("Quit application")); quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q)); quitAction->setMenuRole(QAction::QuitRole); - aboutAction = new QAction(platformStyle->TextColorIcon(":/icons/about"), tr("&About %1").arg(tr(PACKAGE_NAME)), this); - aboutAction->setStatusTip(tr("Show information about %1").arg(tr(PACKAGE_NAME))); + aboutAction = new QAction(tr("&About %1").arg(PACKAGE_NAME), this); + aboutAction->setStatusTip(tr("Show information about %1").arg(PACKAGE_NAME)); aboutAction->setMenuRole(QAction::AboutRole); aboutAction->setEnabled(false); - aboutQtAction = new QAction(platformStyle->TextColorIcon(":/icons/about_qt"), tr("About &Qt"), this); + aboutQtAction = new QAction(tr("About &Qt"), this); aboutQtAction->setStatusTip(tr("Show information about Qt")); aboutQtAction->setMenuRole(QAction::AboutQtRole); - optionsAction = new QAction(platformStyle->TextColorIcon(":/icons/options"), tr("&Options..."), this); - optionsAction->setStatusTip(tr("Modify configuration options for %1").arg(tr(PACKAGE_NAME))); + optionsAction = new QAction(tr("&Options..."), this); + optionsAction->setStatusTip(tr("Modify configuration options for %1").arg(PACKAGE_NAME)); optionsAction->setMenuRole(QAction::PreferencesRole); optionsAction->setEnabled(false); - toggleHideAction = new QAction(platformStyle->TextColorIcon(":/icons/about"), tr("&Show / Hide"), this); + toggleHideAction = new QAction(tr("&Show / Hide"), this); toggleHideAction->setStatusTip(tr("Show or hide the main Window")); - encryptWalletAction = new QAction(platformStyle->TextColorIcon(":/icons/lock_closed"), tr("&Encrypt Wallet..."), this); + encryptWalletAction = new QAction(tr("&Encrypt Wallet..."), this); encryptWalletAction->setStatusTip(tr("Encrypt the private keys that belong to your wallet")); encryptWalletAction->setCheckable(true); - backupWalletAction = new QAction(platformStyle->TextColorIcon(":/icons/filesave"), tr("&Backup Wallet..."), this); + backupWalletAction = new QAction(tr("&Backup Wallet..."), this); backupWalletAction->setStatusTip(tr("Backup wallet to another location")); - changePassphraseAction = new QAction(platformStyle->TextColorIcon(":/icons/key"), tr("&Change Passphrase..."), this); + changePassphraseAction = new QAction(tr("&Change Passphrase..."), this); changePassphraseAction->setStatusTip(tr("Change the passphrase used for wallet encryption")); - signMessageAction = new QAction(platformStyle->TextColorIcon(":/icons/edit"), tr("Sign &message..."), this); + signMessageAction = new QAction(tr("Sign &message..."), this); signMessageAction->setStatusTip(tr("Sign messages with your Bitcoin addresses to prove you own them")); - verifyMessageAction = new QAction(platformStyle->TextColorIcon(":/icons/verify"), tr("&Verify message..."), this); + verifyMessageAction = new QAction(tr("&Verify message..."), this); verifyMessageAction->setStatusTip(tr("Verify messages to ensure they were signed with specified Bitcoin addresses")); - openRPCConsoleAction = new QAction(platformStyle->TextColorIcon(":/icons/debugwindow"), tr("&Debug window"), this); + openRPCConsoleAction = new QAction(tr("&Debug window"), this); openRPCConsoleAction->setStatusTip(tr("Open debugging and diagnostic console")); // initially disable the debug window menu item openRPCConsoleAction->setEnabled(false); openRPCConsoleAction->setObjectName("openRPCConsoleAction"); - usedSendingAddressesAction = new QAction(platformStyle->TextColorIcon(":/icons/address-book"), tr("&Sending addresses"), this); + usedSendingAddressesAction = new QAction(tr("&Sending addresses"), this); usedSendingAddressesAction->setStatusTip(tr("Show the list of used sending addresses and labels")); - usedReceivingAddressesAction = new QAction(platformStyle->TextColorIcon(":/icons/address-book"), tr("&Receiving addresses"), this); + usedReceivingAddressesAction = new QAction(tr("&Receiving addresses"), this); usedReceivingAddressesAction->setStatusTip(tr("Show the list of used receiving addresses and labels")); - openAction = new QAction(platformStyle->TextColorIcon(":/icons/open"), tr("Open &URI..."), this); + openAction = new QAction(tr("Open &URI..."), this); openAction->setStatusTip(tr("Open a bitcoin: URI or payment request")); m_open_wallet_action = new QAction(tr("Open Wallet"), this); @@ -342,9 +340,12 @@ void BitcoinGUI::createActions() m_close_wallet_action = new QAction(tr("Close Wallet..."), this); m_close_wallet_action->setStatusTip(tr("Close wallet")); - showHelpMessageAction = new QAction(platformStyle->TextColorIcon(":/icons/info"), tr("&Command-line options"), this); + m_create_wallet_action = new QAction(tr("Create Wallet..."), this); + m_create_wallet_action->setStatusTip(tr("Create a new wallet")); + + showHelpMessageAction = new QAction(tr("&Command-line options"), this); showHelpMessageAction->setMenuRole(QAction::NoRole); - showHelpMessageAction->setStatusTip(tr("Show the %1 help message to get a list with possible Bitcoin command-line options").arg(tr(PACKAGE_NAME))); + showHelpMessageAction->setStatusTip(tr("Show the %1 help message to get a list with possible Bitcoin command-line options").arg(PACKAGE_NAME)); connect(quitAction, &QAction::triggered, qApp, QApplication::quit); connect(aboutAction, &QAction::triggered, this, &BitcoinGUI::aboutClicked); @@ -371,46 +372,25 @@ void BitcoinGUI::createActions() connect(openAction, &QAction::triggered, this, &BitcoinGUI::openClicked); connect(m_open_wallet_menu, &QMenu::aboutToShow, [this] { m_open_wallet_menu->clear(); - std::vector<std::string> available_wallets = m_wallet_controller->getWalletsAvailableToOpen(); - std::vector<std::string> wallets = m_node.listWalletDir(); - for (const auto& path : wallets) { + for (const std::pair<const std::string, bool>& i : m_wallet_controller->listWalletDir()) { + const std::string& path = i.first; QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path); QAction* action = m_open_wallet_menu->addAction(name); - if (std::find(available_wallets.begin(), available_wallets.end(), path) == available_wallets.end()) { + if (i.second) { // This wallet is already loaded action->setEnabled(false); continue; } - connect(action, &QAction::triggered, [this, name, path] { - OpenWalletActivity* activity = m_wallet_controller->openWallet(path); - - QProgressDialog* dialog = new QProgressDialog(this); - dialog->setLabelText(tr("Opening Wallet <b>%1</b>...").arg(name.toHtmlEscaped())); - dialog->setRange(0, 0); - dialog->setCancelButton(nullptr); - dialog->setWindowModality(Qt::ApplicationModal); - dialog->show(); - - connect(activity, &OpenWalletActivity::message, this, [this] (QMessageBox::Icon icon, QString text) { - QMessageBox box; - box.setIcon(icon); - box.setText(tr("Open Wallet Failed")); - box.setInformativeText(text); - box.setStandardButtons(QMessageBox::Ok); - box.setDefaultButton(QMessageBox::Ok); - connect(this, &QObject::destroyed, &box, &QDialog::accept); - box.exec(); - }); + connect(action, &QAction::triggered, [this, path] { + auto activity = new OpenWalletActivity(m_wallet_controller, this); connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet); connect(activity, &OpenWalletActivity::finished, activity, &QObject::deleteLater); - connect(activity, &OpenWalletActivity::finished, dialog, &QObject::deleteLater); - bool invoked = QMetaObject::invokeMethod(activity, "open"); - assert(invoked); + activity->open(path); }); } - if (wallets.empty()) { + if (m_open_wallet_menu->isEmpty()) { QAction* action = m_open_wallet_menu->addAction(tr("No wallets available")); action->setEnabled(false); } @@ -418,6 +398,12 @@ void BitcoinGUI::createActions() connect(m_close_wallet_action, &QAction::triggered, [this] { m_wallet_controller->closeWallet(walletFrame->currentWalletModel(), this); }); + connect(m_create_wallet_action, &QAction::triggered, [this] { + auto activity = new CreateWalletActivity(m_wallet_controller, this); + connect(activity, &CreateWalletActivity::created, this, &BitcoinGUI::setCurrentWallet); + connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater); + activity->create(); + }); } #endif // ENABLE_WALLET @@ -439,6 +425,7 @@ void BitcoinGUI::createMenuBar() QMenu *file = appMenuBar->addMenu(tr("&File")); if(walletFrame) { + file->addAction(m_create_wallet_action); file->addAction(m_open_wallet_action); file->addAction(m_close_wallet_action); file->addSeparator(); @@ -484,24 +471,16 @@ void BitcoinGUI::createMenuBar() connect(qApp, &QApplication::focusWindowChanged, [zoom_action] (QWindow* window) { zoom_action->setEnabled(window != nullptr); }); -#else - QAction* restore_action = window_menu->addAction(tr("Restore")); - connect(restore_action, &QAction::triggered, [] { - qApp->focusWindow()->showNormal(); - }); - - connect(qApp, &QApplication::focusWindowChanged, [restore_action] (QWindow* window) { - restore_action->setEnabled(window != nullptr); - }); #endif if (walletFrame) { +#ifdef Q_OS_MAC window_menu->addSeparator(); QAction* main_window_action = window_menu->addAction(tr("Main Window")); connect(main_window_action, &QAction::triggered, [this] { GUIUtil::bringToFront(this); }); - +#endif window_menu->addSeparator(); window_menu->addAction(usedSendingAddressesAction); window_menu->addAction(usedReceivingAddressesAction); @@ -640,7 +619,7 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller) connect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet); connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet); - for (WalletModel* wallet_model : m_wallet_controller->getWallets()) { + for (WalletModel* wallet_model : m_wallet_controller->getOpenWallets()) { addWallet(wallet_model); } } @@ -729,7 +708,7 @@ void BitcoinGUI::createTrayIcon() #ifndef Q_OS_MAC if (QSystemTrayIcon::isSystemTrayAvailable()) { trayIcon = new QSystemTrayIcon(m_network_style->getTrayAndWindowIcon(), this); - QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " + m_network_style->getTitleAddText(); + QString toolTip = tr("%1 client").arg(PACKAGE_NAME) + " " + m_network_style->getTitleAddText(); trayIcon->setToolTip(toolTip); } #endif @@ -1292,7 +1271,7 @@ void BitcoinGUI::updateProxyIcon() void BitcoinGUI::updateWindowTitle() { - QString window_title = tr(PACKAGE_NAME); + QString window_title = PACKAGE_NAME; #ifdef ENABLE_WALLET if (walletFrame) { WalletModel* const wallet_model = walletFrame->currentWalletModel(); @@ -1376,12 +1355,13 @@ static bool ThreadSafeMessageBox(BitcoinGUI* gui, const std::string& message, co style &= ~CClientUIInterface::SECURE; bool ret = false; // In case of modal message, use blocking connection to wait for user to click a button - QMetaObject::invokeMethod(gui, "message", + bool invoked = QMetaObject::invokeMethod(gui, "message", modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(caption)), Q_ARG(QString, QString::fromStdString(message)), Q_ARG(unsigned int, style), Q_ARG(bool*, &ret)); + assert(invoked); return ret; } @@ -1410,7 +1390,7 @@ UnitDisplayStatusBarControl::UnitDisplayStatusBarControl(const PlatformStyle *pl const QFontMetrics fm(font()); for (const BitcoinUnits::Unit unit : units) { - max_width = qMax(max_width, fm.width(BitcoinUnits::longName(unit))); + max_width = qMax(max_width, GUIUtil::TextWidth(fm, BitcoinUnits::longName(unit))); } setMinimumSize(max_width, 0); setAlignment(Qt::AlignRight | Qt::AlignVCenter); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 46ced79007..809cf8b4ed 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -147,6 +147,7 @@ private: QAction* openRPCConsoleAction = nullptr; QAction* openAction = nullptr; QAction* showHelpMessageAction = nullptr; + QAction* m_create_wallet_action{nullptr}; QAction* m_open_wallet_action{nullptr}; QMenu* m_open_wallet_menu{nullptr}; QAction* m_close_wallet_action{nullptr}; diff --git a/src/qt/bitcoinstrings.cpp b/src/qt/bitcoinstrings.cpp index dc997e96cc..5cde21eec6 100644 --- a/src/qt/bitcoinstrings.cpp +++ b/src/qt/bitcoinstrings.cpp @@ -9,14 +9,13 @@ #define UNUSED #endif static const char UNUSED *bitcoin_strings[] = { -QT_TRANSLATE_NOOP("bitcoin-core", "Bitcoin Core"), QT_TRANSLATE_NOOP("bitcoin-core", "The %s developers"), QT_TRANSLATE_NOOP("bitcoin-core", "" "-maxtxfee is set very high! Fees this large could be paid on a single " "transaction."), QT_TRANSLATE_NOOP("bitcoin-core", "" -"Can't generate a change-address key. Private keys are disabled for this " -"wallet."), +"Can't generate a change-address key. No keys in the internal keypool and " +"can't generate any keys."), QT_TRANSLATE_NOOP("bitcoin-core", "" "Cannot obtain a lock on data directory %s. %s is probably already running."), QT_TRANSLATE_NOOP("bitcoin-core", "" @@ -88,9 +87,6 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "Warning: The network does not appear to fully agree! Some miners appear to " "be experiencing issues."), QT_TRANSLATE_NOOP("bitcoin-core", "" -"Warning: Unknown block versions being mined! It's possible unknown rules are " -"in effect"), -QT_TRANSLATE_NOOP("bitcoin-core", "" "Warning: Wallet file corrupt, data salvaged! Original %s saved as %s in %s; " "if your balance or transactions are incorrect you should restore from a " "backup."), @@ -124,25 +120,23 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Error loading wallet %s. Duplicate -wallet fi QT_TRANSLATE_NOOP("bitcoin-core", "Error opening block database"), QT_TRANSLATE_NOOP("bitcoin-core", "Error reading from database, shutting down."), QT_TRANSLATE_NOOP("bitcoin-core", "Error upgrading chainstate database"), -QT_TRANSLATE_NOOP("bitcoin-core", "Error"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: A fatal internal error occurred, see debug.log for details"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Disk space is low for %s"), -QT_TRANSLATE_NOOP("bitcoin-core", "Error: Disk space is low!"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Disk space is too low!"), QT_TRANSLATE_NOOP("bitcoin-core", "Failed to listen on any port. Use -listen=0 if you want this."), QT_TRANSLATE_NOOP("bitcoin-core", "Failed to rescan the wallet during initialization"), QT_TRANSLATE_NOOP("bitcoin-core", "Importing..."), QT_TRANSLATE_NOOP("bitcoin-core", "Incorrect or no genesis block found. Wrong datadir for network?"), -QT_TRANSLATE_NOOP("bitcoin-core", "Information"), QT_TRANSLATE_NOOP("bitcoin-core", "Initialization sanity check failed. %s is shutting down."), QT_TRANSLATE_NOOP("bitcoin-core", "Insufficient funds"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid -onion address or hostname: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid -proxy address or hostname: '%s'"), +QT_TRANSLATE_NOOP("bitcoin-core", "Invalid P2P permission: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -%s=<amount>: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -discardfee=<amount>: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -fallbackfee=<amount>: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid netmask specified in -whitelist: '%s'"), -QT_TRANSLATE_NOOP("bitcoin-core", "Keypool ran out, please call keypoolrefill first"), QT_TRANSLATE_NOOP("bitcoin-core", "Loading P2P addresses..."), QT_TRANSLATE_NOOP("bitcoin-core", "Loading banlist..."), QT_TRANSLATE_NOOP("bitcoin-core", "Loading block index..."), @@ -150,6 +144,7 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Loading wallet..."), QT_TRANSLATE_NOOP("bitcoin-core", "Need to specify a port with -whitebind: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Not enough file descriptors available."), QT_TRANSLATE_NOOP("bitcoin-core", "Prune cannot be configured with a negative value."), +QT_TRANSLATE_NOOP("bitcoin-core", "Prune mode is incompatible with -blockfilterindex."), QT_TRANSLATE_NOOP("bitcoin-core", "Prune mode is incompatible with -txindex."), QT_TRANSLATE_NOOP("bitcoin-core", "Pruning blockstore..."), QT_TRANSLATE_NOOP("bitcoin-core", "Reducing -maxconnections from %d to %d, because of system limitations."), @@ -175,13 +170,14 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Transaction amounts must not be negative"), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction fee and change calculation failed"), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction has too long of a mempool chain"), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction must have at least one recipient"), -QT_TRANSLATE_NOOP("bitcoin-core", "Transaction too large for fee policy"), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction too large"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to bind to %s on this computer (bind returned error %s)"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to bind to %s on this computer. %s is probably already running."), +QT_TRANSLATE_NOOP("bitcoin-core", "Unable to create the PID file '%s': %s"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to generate initial keys"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to generate keys"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to start HTTP server. See debug log for details."), +QT_TRANSLATE_NOOP("bitcoin-core", "Unknown -blockfilterindex value %s."), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown network specified in -onlynet: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Unsupported logging category %s=%s."), QT_TRANSLATE_NOOP("bitcoin-core", "Upgrading UTXO database"), @@ -189,9 +185,7 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Upgrading txindex database"), QT_TRANSLATE_NOOP("bitcoin-core", "User Agent comment (%s) contains unsafe characters."), QT_TRANSLATE_NOOP("bitcoin-core", "Verifying blocks..."), QT_TRANSLATE_NOOP("bitcoin-core", "Verifying wallet(s)..."), -QT_TRANSLATE_NOOP("bitcoin-core", "Wallet %s resides outside wallet directory %s"), QT_TRANSLATE_NOOP("bitcoin-core", "Wallet needed to be rewritten: restart %s to complete"), -QT_TRANSLATE_NOOP("bitcoin-core", "Warning"), QT_TRANSLATE_NOOP("bitcoin-core", "Warning: unknown new rules activated (versionbit %i)"), QT_TRANSLATE_NOOP("bitcoin-core", "Zapping all transactions from wallet..."), }; diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index ce950150df..238be08480 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -184,34 +184,39 @@ void ClientModel::updateBanlist() static void ShowProgress(ClientModel *clientmodel, const std::string &title, int nProgress) { // emits signal "showProgress" - QMetaObject::invokeMethod(clientmodel, "showProgress", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(clientmodel, "showProgress", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(title)), Q_ARG(int, nProgress)); + assert(invoked); } static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConnections) { // Too noisy: qDebug() << "NotifyNumConnectionsChanged: " + QString::number(newNumConnections); - QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection, Q_ARG(int, newNumConnections)); + assert(invoked); } static void NotifyNetworkActiveChanged(ClientModel *clientmodel, bool networkActive) { - QMetaObject::invokeMethod(clientmodel, "updateNetworkActive", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(clientmodel, "updateNetworkActive", Qt::QueuedConnection, Q_ARG(bool, networkActive)); + assert(invoked); } static void NotifyAlertChanged(ClientModel *clientmodel) { qDebug() << "NotifyAlertChanged"; - QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection); + bool invoked = QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection); + assert(invoked); } static void BannedListChanged(ClientModel *clientmodel) { qDebug() << QString("%1: Requesting update for peer banlist").arg(__func__); - QMetaObject::invokeMethod(clientmodel, "updateBanlist", Qt::QueuedConnection); + bool invoked = QMetaObject::invokeMethod(clientmodel, "updateBanlist", Qt::QueuedConnection); + assert(invoked); } static void BlockTipChanged(ClientModel *clientmodel, bool initialSync, int height, int64_t blockTime, double verificationProgress, bool fHeader) @@ -233,11 +238,12 @@ static void BlockTipChanged(ClientModel *clientmodel, bool initialSync, int heig // if we are in-sync or if we notify a header update, update the UI regardless of last update time if (fHeader || !initialSync || now - nLastUpdateNotification > MODEL_UPDATE_DELAY) { //pass an async signal to the UI thread - QMetaObject::invokeMethod(clientmodel, "numBlocksChanged", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(clientmodel, "numBlocksChanged", Qt::QueuedConnection, Q_ARG(int, height), Q_ARG(QDateTime, QDateTime::fromTime_t(blockTime)), Q_ARG(double, verificationProgress), Q_ARG(bool, fHeader)); + assert(invoked); nLastUpdateNotification = now; } } diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 6b9f79aaf8..03d18d2845 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -418,7 +418,8 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) if (amount > 0) { - CTxOut txout(amount, static_cast<CScript>(std::vector<unsigned char>(24, 0))); + // Assumes a p2pkh script size + CTxOut txout(amount, CScript() << std::vector<unsigned char>(24, 0)); txDummy.vout.push_back(txout); fDust |= IsDust(txout, model->node().getDustRelayFee()); } @@ -509,7 +510,8 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) // Never create dust outputs; if we would, just add the dust to the fee. if (nChange > 0 && nChange < MIN_CHANGE) { - CTxOut txout(nChange, static_cast<CScript>(std::vector<unsigned char>(24, 0))); + // Assumes a p2pkh script size + CTxOut txout(nChange, CScript() << std::vector<unsigned char>(24, 0)); if (IsDust(txout, model->node().getDustRelayFee())) { nPayFee += nChange; diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp new file mode 100644 index 0000000000..10262c37c3 --- /dev/null +++ b/src/qt/createwalletdialog.cpp @@ -0,0 +1,61 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <qt/createwalletdialog.h> +#include <qt/forms/ui_createwalletdialog.h> + +#include <QPushButton> + +CreateWalletDialog::CreateWalletDialog(QWidget* parent) : + QDialog(parent), + ui(new Ui::CreateWalletDialog) +{ + ui->setupUi(this); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Create")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + ui->wallet_name_line_edit->setFocus(Qt::ActiveWindowFocusReason); + + connect(ui->wallet_name_line_edit, &QLineEdit::textEdited, [this](const QString& text) { + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty()); + }); + + connect(ui->encrypt_wallet_checkbox, &QCheckBox::toggled, [this](bool checked) { + // Disable disable_privkeys_checkbox when encrypt is set to true, enable it when encrypt is false + ui->disable_privkeys_checkbox->setEnabled(!checked); + + // When the disable_privkeys_checkbox is disabled, uncheck it. + if (!ui->disable_privkeys_checkbox->isEnabled()) { + ui->disable_privkeys_checkbox->setChecked(false); + } + }); +} + +CreateWalletDialog::~CreateWalletDialog() +{ + delete ui; +} + +QString CreateWalletDialog::walletName() const +{ + return ui->wallet_name_line_edit->text(); +} + +bool CreateWalletDialog::encrypt() const +{ + return ui->encrypt_wallet_checkbox->isChecked(); +} + +bool CreateWalletDialog::disablePrivateKeys() const +{ + return ui->disable_privkeys_checkbox->isChecked(); +} + +bool CreateWalletDialog::blank() const +{ + return ui->blank_wallet_checkbox->isChecked(); +} diff --git a/src/qt/createwalletdialog.h b/src/qt/createwalletdialog.h new file mode 100644 index 0000000000..a1365b5969 --- /dev/null +++ b/src/qt/createwalletdialog.h @@ -0,0 +1,35 @@ +// Copyright (c) 2019 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_CREATEWALLETDIALOG_H +#define BITCOIN_QT_CREATEWALLETDIALOG_H + +#include <QDialog> + +class WalletModel; + +namespace Ui { + class CreateWalletDialog; +} + +/** Dialog for creating wallets + */ +class CreateWalletDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CreateWalletDialog(QWidget* parent); + virtual ~CreateWalletDialog(); + + QString walletName() const; + bool encrypt() const; + bool disablePrivateKeys() const; + bool blank() const; + +private: + Ui::CreateWalletDialog *ui; +}; + +#endif // BITCOIN_QT_CREATEWALLETDIALOG_H diff --git a/src/qt/forms/createwalletdialog.ui b/src/qt/forms/createwalletdialog.ui new file mode 100644 index 0000000000..1fbaeeaaab --- /dev/null +++ b/src/qt/forms/createwalletdialog.ui @@ -0,0 +1,151 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CreateWalletDialog</class> + <widget class="QDialog" name="CreateWalletDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>364</width> + <height>185</height> + </rect> + </property> + <property name="windowTitle"> + <string>Create Wallet</string> + </property> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="geometry"> + <rect> + <x>10</x> + <y>140</y> + <width>341</width> + <height>32</height> + </rect> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + <widget class="QLineEdit" name="wallet_name_line_edit"> + <property name="geometry"> + <rect> + <x>120</x> + <y>20</y> + <width>231</width> + <height>24</height> + </rect> + </property> + </widget> + <widget class="QLabel" name="label"> + <property name="geometry"> + <rect> + <x>20</x> + <y>20</y> + <width>101</width> + <height>21</height> + </rect> + </property> + <property name="text"> + <string>Wallet Name</string> + </property> + </widget> + <widget class="QCheckBox" name="encrypt_wallet_checkbox"> + <property name="geometry"> + <rect> + <x>20</x> + <y>50</y> + <width>171</width> + <height>22</height> + </rect> + </property> + <property name="toolTip"> + <string>Encrypt the wallet. The wallet will be encrypted with a password of your choice.</string> + </property> + <property name="text"> + <string>Encrypt Wallet</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <widget class="QCheckBox" name="disable_privkeys_checkbox"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="geometry"> + <rect> + <x>20</x> + <y>80</y> + <width>171</width> + <height>22</height> + </rect> + </property> + <property name="toolTip"> + <string>Disable private keys for this wallet. Wallets with private keys disabled will have no private keys and cannot have an HD seed or imported private keys. This is ideal for watch-only wallets.</string> + </property> + <property name="text"> + <string>Disable Private Keys</string> + </property> + </widget> + <widget class="QCheckBox" name="blank_wallet_checkbox"> + <property name="geometry"> + <rect> + <x>20</x> + <y>110</y> + <width>171</width> + <height>22</height> + </rect> + </property> + <property name="toolTip"> + <string>Make a blank wallet. Blank wallets do not initially have private keys or scripts. Private keys and addresses can be imported, or an HD seed can be set, at a later time.</string> + </property> + <property name="text"> + <string>Make Blank Wallet</string> + </property> + </widget> + </widget> + <tabstops> + <tabstop>wallet_name_line_edit</tabstop> + <tabstop>encrypt_wallet_checkbox</tabstop> + <tabstop>disable_privkeys_checkbox</tabstop> + <tabstop>blank_wallet_checkbox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>CreateWalletDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>CreateWalletDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index 6e52c5e477..be807b20c0 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -15,6 +15,25 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> + <widget class="QLabel" name="label_alerts"> + <property name="visible"> + <bool>false</bool> + </property> + <property name="styleSheet"> + <string notr="true">QLabel { background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #F0D0A0, stop:1 #F8D488); color:#000000; }</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="margin"> + <number>3</number> + </property> + <property name="textInteractionFlags"> + <set>Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item> <widget class="QTabWidget" name="tabWidget"> <property name="currentIndex"> <number>0</number> diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index d8f5594983..dcdb247977 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_QT_GUICONSTANTS_H #define BITCOIN_QT_GUICONSTANTS_H +#include <cstdint> + /* Milliseconds between model updates */ static const int MODEL_UPDATE_DELAY = 250; diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 70e52c9f1d..070df31aa6 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2011-2018 The Bitcoin Core developers +// Copyright (c) 2011-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -11,10 +11,10 @@ #include <base58.h> #include <chainparams.h> -#include <primitives/transaction.h> -#include <key_io.h> #include <interfaces/node.h> +#include <key_io.h> #include <policy/policy.h> +#include <primitives/transaction.h> #include <protocol.h> #include <script/script.h> #include <script/standard.h> @@ -39,7 +39,6 @@ #include <QClipboard> #include <QDateTime> #include <QDesktopServices> -#include <QDesktopWidget> #include <QDoubleValidator> #include <QFileDialog> #include <QFont> @@ -58,9 +57,10 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#include <objc/objc-runtime.h> #include <CoreServices/CoreServices.h> #include <QProcess> + +void ForceActivation(); #endif namespace GUIUtil { @@ -360,10 +360,7 @@ bool isObscured(QWidget *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); + ForceActivation(); #endif if (w) { @@ -639,7 +636,7 @@ fs::path static GetAutostartFilePath() std::string chain = gArgs.GetChainName(); if (chain == CBaseChainParams::MAIN) return GetAutostartDir() / "bitcoin.desktop"; - return GetAutostartDir() / strprintf("bitcoin-%s.lnk", chain); + return GetAutostartDir() / strprintf("bitcoin-%s.desktop", chain); } bool GetStartOnSystemStartup() @@ -841,9 +838,6 @@ QString formatServicesStr(quint64 mask) case NODE_WITNESS: strList.append("WITNESS"); break; - case NODE_XTHIN: - strList.append("XTHIN"); - break; default: strList.append(QString("%1[%2]").arg("UNKNOWN").arg(check)); } @@ -919,7 +913,7 @@ qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal m while(font_size >= minPointSize) { font.setPointSizeF(font_size); QFontMetrics fm(font); - if (fm.width(text) < width) { + if (TextWidth(fm, text) < width) { break; } font_size -= 0.5; @@ -951,7 +945,7 @@ void PolishProgressDialog(QProgressDialog* dialog) { #ifdef Q_OS_MAC // Workaround for macOS-only Qt bug; see: QTBUG-65750, QTBUG-70357. - const int margin = dialog->fontMetrics().width("X"); + const int margin = TextWidth(dialog->fontMetrics(), ("X")); dialog->resize(dialog->width() + 2 * margin, dialog->height()); dialog->show(); #else @@ -959,4 +953,13 @@ void PolishProgressDialog(QProgressDialog* dialog) #endif } +int TextWidth(const QFontMetrics& fm, const QString& text) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) + return fm.horizontalAdvance(text); +#else + return fm.width(text); +#endif +} + } // namespace GUIUtil diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index bea4a83494..9db92f94d7 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -257,6 +257,14 @@ namespace GUIUtil // Fix known bugs in QProgressDialog class. void PolishProgressDialog(QProgressDialog* dialog); + + /** + * Returns the distance in pixels appropriate for drawing a subsequent character after text. + * + * In Qt 5.12 and before the QFontMetrics::width() is used and it is deprecated since Qt 13.0. + * In Qt 5.11 the QFontMetrics::horizontalAdvance() was introduced. + */ + int TextWidth(const QFontMetrics& fm, const QString& text); } // namespace GUIUtil #endif // BITCOIN_QT_GUIUTIL_H diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index c595361934..102e37e471 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -119,16 +119,16 @@ Intro::Intro(QWidget *parent, uint64_t blockchain_size, uint64_t chain_state_siz m_chain_state_size(chain_state_size) { ui->setupUi(this); - ui->welcomeLabel->setText(ui->welcomeLabel->text().arg(tr(PACKAGE_NAME))); - ui->storageLabel->setText(ui->storageLabel->text().arg(tr(PACKAGE_NAME))); + ui->welcomeLabel->setText(ui->welcomeLabel->text().arg(PACKAGE_NAME)); + ui->storageLabel->setText(ui->storageLabel->text().arg(PACKAGE_NAME)); ui->lblExplanation1->setText(ui->lblExplanation1->text() - .arg(tr(PACKAGE_NAME)) + .arg(PACKAGE_NAME) .arg(m_blockchain_size) .arg(2009) .arg(tr("Bitcoin")) ); - ui->lblExplanation2->setText(ui->lblExplanation2->text().arg(tr(PACKAGE_NAME))); + ui->lblExplanation2->setText(ui->lblExplanation2->text().arg(PACKAGE_NAME)); uint64_t pruneTarget = std::max<int64_t>(0, gArgs.GetArg("-prune", 0)); requiredSpace = m_blockchain_size; @@ -145,7 +145,7 @@ Intro::Intro(QWidget *parent, uint64_t blockchain_size, uint64_t chain_state_siz } requiredSpace += m_chain_state_size; ui->sizeWarningLabel->setText( - tr("%1 will download and store a copy of the Bitcoin block chain.").arg(tr(PACKAGE_NAME)) + " " + + tr("%1 will download and store a copy of the Bitcoin block chain.").arg(PACKAGE_NAME) + " " + storageRequiresMsg.arg(requiredSpace) + " " + tr("The wallet will also be stored in this directory.") ); @@ -221,7 +221,7 @@ bool Intro::pickDataDirectory(interfaces::Node& node) } break; } catch (const fs::filesystem_error&) { - QMessageBox::critical(nullptr, tr(PACKAGE_NAME), + QMessageBox::critical(nullptr, PACKAGE_NAME, tr("Error: Specified data directory \"%1\" cannot be created.").arg(dataDir)); /* fall through, back to choosing screen */ } diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts index c6ecbc3f87..7864f97f31 100644 --- a/src/qt/locale/bitcoin_en.ts +++ b/src/qt/locale/bitcoin_en.ts @@ -59,7 +59,7 @@ <translation>&Delete</translation> </message> <message> - <location filename="../addressbookpage.cpp" line="+85"/> + <location filename="../addressbookpage.cpp" line="+84"/> <source>Choose the address to send coins to</source> <translation type="unfinished"></translation> </message> @@ -90,7 +90,7 @@ </message> <message> <location line="+5"/> - <source>These are your Bitcoin addresses for receiving payments. It is recommended to use a new receiving address for each transaction.</source> + <source>These are your Bitcoin addresses for receiving payments. Use the 'Create new receiving address' button in the receive tab to create new addresses.</source> <translation type="unfinished"></translation> </message> <message> @@ -132,7 +132,7 @@ <context> <name>AddressTableModel</name> <message> - <location filename="../addresstablemodel.cpp" line="+164"/> + <location filename="../addresstablemodel.cpp" line="+165"/> <source>Label</source> <translation type="unfinished"></translation> </message> @@ -310,17 +310,17 @@ <context> <name>BitcoinGUI</name> <message> - <location filename="../bitcoingui.cpp" line="+318"/> + <location filename="../bitcoingui.cpp" line="+315"/> <source>Sign &message...</source> <translation>Sign &message...</translation> </message> <message> - <location line="+574"/> + <location line="+637"/> <source>Synchronizing with network...</source> <translation>Synchronizing with network...</translation> </message> <message> - <location line="-652"/> + <location line="-715"/> <source>&Overview</source> <translation>&Overview</translation> </message> @@ -400,12 +400,12 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+157"/> + <location line="+216"/> <source>Wallet:</source> <translation type="unfinished"></translation> </message> <message> - <location line="+330"/> + <location line="+334"/> <source>Click to disable network activity.</source> <translation type="unfinished"></translation> </message> @@ -430,12 +430,12 @@ <translation>Reindexing blocks on disk...</translation> </message> <message> - <location line="+315"/> + <location line="+317"/> <source>Proxy is <b>enabled</b>: %1</source> <translation type="unfinished"></translation> </message> <message> - <location line="-970"/> + <location line="-1035"/> <source>Send coins to a Bitcoin address</source> <translation>Send coins to a Bitcoin address</translation> </message> @@ -465,12 +465,7 @@ <translation>&Verify message...</translation> </message> <message> - <location line="+660"/> - <source>Bitcoin</source> - <translation>Bitcoin</translation> - </message> - <message> - <location line="-733"/> + <location line="-73"/> <source>&Send</source> <translation>&Send</translation> </message> @@ -505,12 +500,12 @@ <translation>Verify messages to ensure they were signed with specified Bitcoin addresses</translation> </message> <message> - <location line="+61"/> + <location line="+117"/> <source>&File</source> <translation>&File</translation> </message> <message> - <location line="+11"/> + <location line="+14"/> <source>&Settings</source> <translation>&Settings</translation> </message> @@ -525,7 +520,7 @@ <translation>Tabs toolbar</translation> </message> <message> - <location line="-211"/> + <location line="-270"/> <source>Request payments (generates QR codes and bitcoin: URIs)</source> <translation type="unfinished"></translation> </message> @@ -545,12 +540,12 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+2"/> + <location line="+10"/> <source>&Command-line options</source> <translation type="unfinished"></translation> </message> <message numerus="yes"> - <location line="+484"/> + <location line="+539"/> <source>%n active connection(s) to Bitcoin network</source> <translation> <numerusform>%n active connection to Bitcoin network</numerusform> @@ -591,27 +586,27 @@ <translation>Transactions after this will not yet be visible.</translation> </message> <message> - <location line="+27"/> + <location line="+28"/> <source>Error</source> <translation>Error</translation> </message> <message> - <location line="+3"/> + <location line="+4"/> <source>Warning</source> <translation>Warning</translation> </message> <message> - <location line="+3"/> + <location line="+4"/> <source>Information</source> <translation>Information</translation> </message> <message> - <location line="-78"/> + <location line="-81"/> <source>Up to date</source> <translation>Up to date</translation> </message> <message> - <location line="-593"/> + <location line="-656"/> <source>&Sending addresses</source> <translation type="unfinished"></translation> </message> @@ -621,12 +616,52 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+8"/> + <location line="+6"/> + <source>Open Wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+2"/> + <source>Open a wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>Close Wallet...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Close wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+4"/> <source>Show the %1 help message to get a list with possible Bitcoin command-line options</source> <translation type="unfinished"></translation> </message> <message> - <location line="+63"/> + <location line="+29"/> + <source>default wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+13"/> + <source>Opening Wallet <b>%1</b>...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+9"/> + <source>Open Wallet Failed</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+15"/> + <source>No wallets available</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+48"/> <source>&Window</source> <translation type="unfinished">&Window</translation> </message> @@ -651,7 +686,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+228"/> + <location line="+232"/> <source>%1 client</source> <translation type="unfinished"></translation> </message> @@ -666,7 +701,17 @@ <translation>Catching up...</translation> </message> <message> - <location line="+151"/> + <location line="+50"/> + <source>Error: %1</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+4"/> + <source>Warning: %1</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+99"/> <source>Date: %1 </source> <translation type="unfinished"></translation> @@ -737,7 +782,7 @@ <translation>Wallet is <b>encrypted</b> and currently <b>locked</b></translation> </message> <message> - <location filename="../bitcoin.cpp" line="+395"/> + <location filename="../bitcoin.cpp" line="+382"/> <source>A fatal error occurred. Bitcoin can no longer continue safely and will quit.</source> <translation type="unfinished"></translation> </message> @@ -830,7 +875,7 @@ <translation type="unfinished">Confirmed</translation> </message> <message> - <location filename="../coincontroldialog.cpp" line="+58"/> + <location filename="../coincontroldialog.cpp" line="+54"/> <source>Copy address</source> <translation type="unfinished"></translation> </message> @@ -896,7 +941,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+155"/> + <location line="+157"/> <source>yes</source> <translation type="unfinished"></translation> </message> @@ -1031,7 +1076,7 @@ <context> <name>HelpMessageDialog</name> <message> - <location filename="../utilitydialog.cpp" line="+44"/> + <location filename="../utilitydialog.cpp" line="+39"/> <source>version</source> <translation type="unfinished">version</translation> </message> @@ -1120,7 +1165,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+80"/> + <location line="+75"/> <source>Error: Specified data directory "%1" cannot be created.</source> <translation type="unfinished"></translation> </message> @@ -1171,7 +1216,7 @@ <message> <location line="+7"/> <location line="+26"/> - <location filename="../modaloverlay.cpp" line="+140"/> + <location filename="../modaloverlay.cpp" line="+141"/> <source>Unknown...</source> <translation type="unfinished"></translation> </message> @@ -1207,8 +1252,8 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../modaloverlay.cpp" line="-1"/> - <source>Unknown. Syncing Headers (%1)...</source> + <location filename="../modaloverlay.cpp" line="+6"/> + <source>Unknown. Syncing Headers (%1, %2%)...</source> <translation type="unfinished"></translation> </message> </context> @@ -1540,12 +1585,12 @@ <translation>default</translation> </message> <message> - <location line="+63"/> + <location line="+67"/> <source>none</source> <translation type="unfinished"></translation> </message> <message> - <location line="+93"/> + <location line="+89"/> <source>Confirm options reset</source> <translation>Confirm options reset</translation> </message> @@ -1689,16 +1734,16 @@ <name>PaymentServer</name> <message> <location filename="../paymentserver.cpp" line="+226"/> - <location line="+338"/> + <location line="+346"/> <location line="+42"/> - <location line="+110"/> + <location line="+108"/> <location line="+14"/> <location line="+18"/> <source>Payment request error</source> <translation type="unfinished"></translation> </message> <message> - <location line="-521"/> + <location line="-527"/> <source>Cannot start bitcoin: click-to-pay handler</source> <translation type="unfinished"></translation> </message> @@ -1706,14 +1751,14 @@ <location line="+62"/> <location line="+9"/> <location line="+16"/> + <location line="+16"/> <location line="+5"/> - <location line="+12"/> <location line="+7"/> <source>URI handling</source> <translation type="unfinished"></translation> </message> <message> - <location line="-49"/> + <location line="-53"/> <source>'bitcoin://' is not a valid URI. Use 'bitcoin:' instead.</source> <translation type="unfinished"></translation> </message> @@ -1728,12 +1773,13 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+5"/> + <location line="+16"/> + <location line="+36"/> <source>Cannot process payment request because BIP70 support was not compiled in.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+11"/> + <location line="-32"/> <source>Invalid payment address %1</source> <translation type="unfinished"></translation> </message> @@ -1744,26 +1790,27 @@ </message> <message> <location line="+14"/> + <location line="+9"/> <source>Payment request file handling</source> <translation type="unfinished"></translation> </message> <message> - <location line="+1"/> + <location line="-8"/> <source>Payment request file cannot be read! This can be caused by an invalid payment request file.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+195"/> + <location line="+199"/> <location line="+9"/> <location line="+31"/> <location line="+10"/> <location line="+17"/> - <location line="+85"/> + <location line="+83"/> <source>Payment request rejected</source> <translation type="unfinished"></translation> </message> <message> - <location line="-152"/> + <location line="-150"/> <source>Payment request network doesn't match client network.</source> <translation type="unfinished"></translation> </message> @@ -1794,7 +1841,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+65"/> + <location line="+63"/> <source>Refund from %1</source> <translation type="unfinished"></translation> </message> @@ -1832,7 +1879,7 @@ <context> <name>PeerTableModel</name> <message> - <location filename="../peertablemodel.cpp" line="+109"/> + <location filename="../peertablemodel.cpp" line="+110"/> <source>User Agent</source> <translation type="unfinished"></translation> </message> @@ -1865,17 +1912,17 @@ <context> <name>QObject</name> <message> - <location filename="../bitcoinunits.cpp" line="+197"/> + <location filename="../bitcoinunits.cpp" line="+195"/> <source>Amount</source> <translation type="unfinished">Amount</translation> </message> <message> - <location filename="../guiutil.cpp" line="+111"/> + <location filename="../guiutil.cpp" line="+108"/> <source>Enter a Bitcoin address (e.g. %1)</source> <translation type="unfinished"></translation> </message> <message> - <location line="+687"/> + <location line="+699"/> <source>%1 d</source> <translation type="unfinished"></translation> </message> @@ -1891,7 +1938,7 @@ </message> <message> <location line="+2"/> - <location line="+50"/> + <location line="+47"/> <source>%1 s</source> <translation type="unfinished"></translation> </message> @@ -1985,43 +2032,35 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../bitcoin.cpp" line="+185"/> - <source>%1 didn't yet exit safely...</source> + <location filename="../bitcoin.cpp" line="+116"/> + <source>Error: Specified data directory "%1" does not exist.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../modaloverlay.cpp" line="-29"/> - <source>unknown</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>QObject::QObject</name> - <message> - <location filename="../bitcoin.cpp" line="-113"/> - <source>Error parsing command line arguments: %1.</source> + <location line="+6"/> + <source>Error: Cannot parse configuration file: %1.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+37"/> - <source>Error: Specified data directory "%1" does not exist.</source> + <location line="+15"/> + <source>Error: %1</source> <translation type="unfinished"></translation> </message> <message> - <location line="+5"/> - <source>Error: Cannot parse configuration file: %1.</source> + <location line="+59"/> + <source>%1 didn't yet exit safely...</source> <translation type="unfinished"></translation> </message> <message> - <location line="+14"/> - <source>Error: %1</source> + <location filename="../modaloverlay.cpp" line="-36"/> + <source>unknown</source> <translation type="unfinished"></translation> </message> </context> <context> <name>QRImageWidget</name> <message> - <location filename="../receiverequestdialog.cpp" line="+32"/> + <location filename="../qrimagewidget.cpp" line="+29"/> <source>&Save Image...</source> <translation type="unfinished"></translation> </message> @@ -2031,6 +2070,21 @@ <translation type="unfinished"></translation> </message> <message> + <location line="+13"/> + <source>Resulting URI too long, try to reduce the text for label / message.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+7"/> + <source>Error encoding URI into QR Code.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+38"/> + <source>QR code support not available.</source> + <translation type="unfinished"></translation> + </message> + <message> <location line="+32"/> <source>Save QR Code</source> <translation type="unfinished"></translation> @@ -2044,7 +2098,7 @@ <context> <name>RPCConsole</name> <message> - <location filename="../forms/debugwindow.ui" line="+56"/> + <location filename="../forms/debugwindow.ui" line="+75"/> <location line="+26"/> <location line="+26"/> <location line="+26"/> @@ -2056,7 +2110,7 @@ <location line="+23"/> <location line="+36"/> <location line="+23"/> - <location line="+713"/> + <location line="+716"/> <location line="+23"/> <location line="+23"/> <location line="+23"/> @@ -2078,7 +2132,7 @@ <translation>N/A</translation> </message> <message> - <location line="-1427"/> + <location line="-1430"/> <source>Client version</source> <translation>Client version</translation> </message> @@ -2088,12 +2142,12 @@ <translation>&Information</translation> </message> <message> - <location line="-10"/> + <location line="-29"/> <source>Debug window</source> <translation type="unfinished"></translation> </message> <message> - <location line="+25"/> + <location line="+44"/> <source>General</source> <translation type="unfinished"></translation> </message> @@ -2178,7 +2232,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+238"/> + <location line="+241"/> <source>&Reset</source> <translation type="unfinished"></translation> </message> @@ -2206,8 +2260,8 @@ </message> <message> <location line="+65"/> - <location filename="../rpcconsole.cpp" line="+501"/> - <location line="+754"/> + <location filename="../rpcconsole.cpp" line="+497"/> + <location line="+759"/> <source>Select a peer to view detailed information.</source> <translation type="unfinished"></translation> </message> @@ -2242,13 +2296,13 @@ <translation type="unfinished"></translation> </message> <message> - <location line="-1161"/> - <location line="+1069"/> + <location line="-1164"/> + <location line="+1072"/> <source>User Agent</source> <translation type="unfinished"></translation> </message> <message> - <location line="-734"/> + <location line="-737"/> <source>Open the %1 debug log file from the current data directory. This can take a few seconds for large log files.</source> <translation type="unfinished"></translation> </message> @@ -2263,7 +2317,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+641"/> + <location line="+644"/> <source>Services</source> <translation type="unfinished"></translation> </message> @@ -2313,7 +2367,7 @@ <translation type="unfinished"></translation> </message> <message> - <location line="-1166"/> + <location line="-1169"/> <source>Last block time</source> <translation>Last block time</translation> </message> @@ -2328,7 +2382,7 @@ <translation>&Console</translation> </message> <message> - <location line="+214"/> + <location line="+217"/> <source>&Network Traffic</source> <translation type="unfinished"></translation> </message> @@ -2348,7 +2402,7 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../forms/debugwindow.ui" line="-318"/> + <location filename="../forms/debugwindow.ui" line="-321"/> <source>Debug log file</source> <translation>Debug log file</translation> </message> @@ -2358,7 +2412,7 @@ <translation>Clear console</translation> </message> <message> - <location filename="../rpcconsole.cpp" line="-249"/> + <location filename="../rpcconsole.cpp" line="-243"/> <source>1 &hour</source> <translation type="unfinished"></translation> </message> @@ -2391,12 +2445,12 @@ <translation type="unfinished"></translation> </message> <message> - <location line="+47"/> + <location line="+38"/> <source>&Unban</source> <translation type="unfinished"></translation> </message> <message> - <location line="+161"/> + <location line="+164"/> <source>Welcome to the %1 RPC console.</source> <translation type="unfinished"></translation> </message> @@ -2513,43 +2567,43 @@ <translation type="unfinished"></translation> </message> <message> - <location line="-39"/> - <location line="+153"/> - <source>An optional amount to request. Leave this empty or zero to not request a specific amount.</source> + <location line="+136"/> + <source>Native segwit addresses (aka Bech32 or BIP-173) reduce your transaction fees later on and offer better protection against typos, but old wallets don't support them. When checked, an address compatible with older wallets will be created instead.</source> <translation type="unfinished"></translation> </message> <message> - <location line="-59"/> - <source>Clear all fields of the form.</source> + <location line="+3"/> + <source>Generate legacy address</source> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> - <source>Clear</source> + <location line="-178"/> + <location line="+153"/> + <source>An optional amount to request. Leave this empty or zero to not request a specific amount.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+78"/> - <source>Native segwit addresses (aka Bech32 or BIP-173) reduce your transaction fees later on and offer better protection against typos, but old wallets don't support them. When unchecked, an address compatible with older wallets will be created instead.</source> + <location line="-76"/> + <source>&Create new receiving address</source> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> - <source>Generate native segwit (Bech32) address</source> + <location line="+17"/> + <source>Clear all fields of the form.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+61"/> - <source>Requested payments history</source> + <location line="+3"/> + <source>Clear</source> <translation type="unfinished"></translation> </message> <message> - <location line="-162"/> - <source>&Request payment</source> + <location line="+142"/> + <source>Requested payments history</source> <translation type="unfinished"></translation> </message> <message> - <location line="+187"/> + <location line="+25"/> <source>Show the selected request (does the same as double clicking an entry)</source> <translation type="unfinished"></translation> </message> @@ -2569,7 +2623,7 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../receivecoinsdialog.cpp" line="+47"/> + <location filename="../receivecoinsdialog.cpp" line="+46"/> <source>Copy URI</source> <translation type="unfinished"></translation> </message> @@ -2612,7 +2666,7 @@ <translation type="unfinished"></translation> </message> <message> - <location filename="../receiverequestdialog.cpp" line="+65"/> + <location filename="../receiverequestdialog.cpp" line="+63"/> <source>Request payment to %1</source> <translation type="unfinished"></translation> </message> @@ -2651,21 +2705,11 @@ <source>Wallet</source> <translation type="unfinished">Wallet</translation> </message> - <message> - <location line="+11"/> - <source>Resulting URI too long, try to reduce the text for label / message.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+5"/> - <source>Error encoding URI into QR Code.</source> - <translation type="unfinished"></translation> - </message> </context> <context> <name>RecentRequestsTableModel</name> <message> - <location filename="../recentrequeststablemodel.cpp" line="+25"/> + <location filename="../recentrequeststablemodel.cpp" line="+27"/> <source>Date</source> <translation type="unfinished">Date</translation> </message> @@ -2704,7 +2748,7 @@ <name>SendCoinsDialog</name> <message> <location filename="../forms/sendcoinsdialog.ui" line="+14"/> - <location filename="../sendcoinsdialog.cpp" line="+593"/> + <location filename="../sendcoinsdialog.cpp" line="+601"/> <source>Send Coins</source> <translation>Send Coins</translation> </message> @@ -2891,7 +2935,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation>S&end</translation> </message> <message> - <location filename="../sendcoinsdialog.cpp" line="-505"/> + <location filename="../sendcoinsdialog.cpp" line="-513"/> <source>Copy quantity</source> <translation type="unfinished"></translation> </message> @@ -2931,10 +2975,19 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+133"/> - <location line="+5"/> - <location line="+6"/> - <location line="+4"/> + <location line="+118"/> + <source> from wallet '%1'</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+14"/> + <location line="+11"/> + <source>%1 to '%2'</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="-6"/> + <location line="+10"/> <source>%1 to %2</source> <translation type="unfinished"></translation> </message> @@ -2954,12 +3007,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="-61"/> - <source>from wallet %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+40"/> + <location line="-21"/> <source>Please, review your transaction.</source> <translation type="unfinished"></translation> </message> @@ -2979,12 +3027,17 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+5"/> + <location line="+9"/> + <source>To review recipient list click "Show Details..."</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+6"/> <source>Confirm send coins</source> <translation type="unfinished"></translation> </message> <message> - <location line="+191"/> + <location line="+190"/> <source>The recipient address is not valid. Please recheck.</source> <translation type="unfinished"></translation> </message> @@ -3179,7 +3232,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <context> <name>SendConfirmationDialog</name> <message> - <location filename="../sendcoinsdialog.cpp" line="+83"/> + <location filename="../sendcoinsdialog.cpp" line="+88"/> <location line="+5"/> <source>Yes</source> <translation type="unfinished"></translation> @@ -3399,7 +3452,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <context> <name>TransactionDesc</name> <message numerus="yes"> - <location filename="../transactiondesc.cpp" line="+35"/> + <location filename="../transactiondesc.cpp" line="+34"/> <source>Open for %n more block(s)</source> <translation> <numerusform>Open for %n more block</numerusform> @@ -3640,7 +3693,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <context> <name>TransactionTableModel</name> <message> - <location filename="../transactiontablemodel.cpp" line="+227"/> + <location filename="../transactiontablemodel.cpp" line="+225"/> <source>Date</source> <translation type="unfinished">Date</translation> </message> @@ -3776,7 +3829,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <context> <name>TransactionView</name> <message> - <location filename="../transactionview.cpp" line="+71"/> + <location filename="../transactionview.cpp" line="+69"/> <location line="+16"/> <source>All</source> <translation type="unfinished"></translation> @@ -3897,7 +3950,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+199"/> + <location line="+194"/> <source>Export Transaction History</source> <translation type="unfinished"></translation> </message> @@ -3975,12 +4028,30 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <context> <name>UnitDisplayStatusBarControl</name> <message> - <location filename="../bitcoingui.cpp" line="+154"/> + <location filename="../bitcoingui.cpp" line="+156"/> <source>Unit to show amounts in. Click to select another unit.</source> <translation type="unfinished"></translation> </message> </context> <context> + <name>WalletController</name> + <message> + <location filename="../walletcontroller.cpp" line="+73"/> + <source>Close wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Are you sure you wish to close wallet <i>%1</i>?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>WalletFrame</name> <message> <location filename="../walletframe.cpp" line="+29"/> @@ -3996,7 +4067,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished">Send Coins</translation> </message> <message> - <location line="+301"/> + <location line="+309"/> <location line="+39"/> <location line="+5"/> <source>Fee bump error</source> @@ -4043,7 +4114,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+35"/> + <location line="+30"/> <source>default wallet</source> <translation type="unfinished"></translation> </message> @@ -4099,7 +4170,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <context> <name>bitcoin-core</name> <message> - <location filename="../bitcoinstrings.cpp" line="+29"/> + <location filename="../bitcoinstrings.cpp" line="+28"/> <source>Distributed under the MIT software license, see the accompanying file %s or %s</source> <translation type="unfinished"></translation> </message> @@ -4119,7 +4190,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+74"/> + <location line="+70"/> <source>Error: A fatal internal error occurred, see debug.log for details</source> <translation type="unfinished"></translation> </message> @@ -4134,17 +4205,17 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="-172"/> - <source>Bitcoin Core</source> - <translation type="unfinished">Bitcoin Core</translation> + <location line="-167"/> + <source>The %s developers</source> + <translation type="unfinished"></translation> </message> <message> - <location line="+1"/> - <source>The %s developers</source> + <location line="+4"/> + <source>Can't generate a change-address key. No keys in the internal keypool and can't generate any keys.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+7"/> + <location line="+3"/> <source>Cannot obtain a lock on data directory %s. %s is probably already running.</source> <translation type="unfinished"></translation> </message> @@ -4199,7 +4270,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+10"/> + <location line="+7"/> <source>Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade.</source> <translation type="unfinished"></translation> </message> @@ -4290,11 +4361,6 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos </message> <message> <location line="+6"/> - <source>Error: Disk space is low!</source> - <translation>Error: Disk space is low!</translation> - </message> - <message> - <location line="+1"/> <source>Failed to listen on any port. Use -listen=0 if you want this.</source> <translation>Failed to listen on any port. Use -listen=0 if you want this.</translation> </message> @@ -4314,12 +4380,17 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation>Incorrect or no genesis block found. Wrong datadir for network?</translation> </message> <message> - <location line="+2"/> + <location line="+1"/> <source>Initialization sanity check failed. %s is shutting down.</source> <translation type="unfinished"></translation> </message> <message> <location line="+4"/> + <source>Invalid P2P permission: '%s'</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Invalid amount for -%s=<amount>: '%s'</source> <translation type="unfinished"></translation> </message> @@ -4339,17 +4410,22 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+24"/> + <location line="+25"/> <source>Upgrading txindex database</source> <translation type="unfinished"></translation> </message> <message> - <location line="-42"/> + <location line="-44"/> <source>Loading P2P addresses...</source> <translation type="unfinished"></translation> </message> <message> - <location line="+1"/> + <location line="-15"/> + <source>Error: Disk space is too low!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+16"/> <source>Loading banlist...</source> <translation type="unfinished"></translation> </message> @@ -4364,7 +4440,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+1"/> + <location line="+2"/> <source>Prune mode is incompatible with -txindex.</source> <translation type="unfinished"></translation> </message> @@ -4389,17 +4465,17 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> + <location line="+5"/> <source>Unable to bind to %s on this computer. %s is probably already running.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+2"/> + <location line="+3"/> <source>Unable to generate keys</source> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+4"/> <source>Unsupported logging category %s=%s.</source> <translation type="unfinished"></translation> </message> @@ -4419,12 +4495,12 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation>Verifying blocks...</translation> </message> <message> - <location line="+3"/> + <location line="+2"/> <source>Wallet needed to be rewritten: restart %s to complete</source> <translation type="unfinished"></translation> </message> <message> - <location line="-158"/> + <location line="-154"/> <source>Error: Listening for incoming connections failed (listen returned error %s)</source> <translation type="unfinished"></translation> </message> @@ -4439,7 +4515,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+38"/> + <location line="+35"/> <source>You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain</source> <translation type="unfinished"></translation> </message> @@ -4454,17 +4530,12 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+2"/> <source>Error: Disk space is low for %s</source> <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> - <source>Information</source> - <translation>Information</translation> - </message> - <message> - <location line="+3"/> + <location line="+8"/> <source>Invalid -onion address or hostname: '%s'</source> <translation type="unfinished"></translation> </message> @@ -4474,7 +4545,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+4"/> + <location line="+5"/> <source>Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)</source> <translation type="unfinished"></translation> </message> @@ -4484,12 +4555,17 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> + <location line="+5"/> <source>Need to specify a port with -whitebind: '%s'</source> <translation type="unfinished"></translation> </message> <message> - <location line="+5"/> + <location line="+3"/> + <source>Prune mode is incompatible with -blockfilterindex.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> <source>Reducing -maxconnections from %d to %d, because of system limitations.</source> <translation type="unfinished"></translation> </message> @@ -4541,11 +4617,6 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos </message> <message> <location line="+5"/> - <source>Transaction too large for fee policy</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+1"/> <source>Transaction too large</source> <translation>Transaction too large</translation> </message> @@ -4556,26 +4627,26 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos </message> <message> <location line="+2"/> - <source>Unable to generate initial keys</source> + <source>Unable to create the PID file '%s': %s</source> <translation type="unfinished"></translation> </message> <message> - <location line="+9"/> - <source>Verifying wallet(s)...</source> + <location line="+1"/> + <source>Unable to generate initial keys</source> <translation type="unfinished"></translation> </message> <message> - <location line="+1"/> - <source>Wallet %s resides outside wallet directory %s</source> + <location line="+3"/> + <source>Unknown -blockfilterindex value %s.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+2"/> - <source>Warning</source> - <translation>Warning</translation> + <location line="+7"/> + <source>Verifying wallet(s)...</source> + <translation type="unfinished"></translation> </message> <message> - <location line="+1"/> + <location line="+2"/> <source>Warning: unknown new rules activated (versionbit %i)</source> <translation type="unfinished"></translation> </message> @@ -4585,7 +4656,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="-182"/> + <location line="-177"/> <source>-maxtxfee is set very high! Fees this large could be paid on a single transaction.</source> <translation type="unfinished"></translation> </message> @@ -4606,11 +4677,6 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos </message> <message> <location line="+14"/> - <source>Warning: Unknown block versions being mined! It's possible unknown rules are in effect</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+3"/> <source>Warning: Wallet file corrupt, data salvaged! Original %s saved as %s in %s; if your balance or transactions are incorrect you should restore from a backup.</source> <translation type="unfinished"></translation> </message> @@ -4625,12 +4691,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+22"/> - <source>Keypool ran out, please call keypoolrefill first</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+20"/> + <location line="+41"/> <source>Starting network threads...</source> <translation type="unfinished"></translation> </message> @@ -4665,22 +4726,17 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+8"/> + <location line="+9"/> <source>Unknown network specified in -onlynet: '%s'</source> <translation>Unknown network specified in -onlynet: '%s'</translation> </message> <message> - <location line="-48"/> + <location line="-50"/> <source>Insufficient funds</source> <translation>Insufficient funds</translation> </message> <message> - <location line="-120"/> - <source>Can't generate a change-address key. Private keys are disabled for this wallet.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+8"/> + <location line="-107"/> <source>Cannot upgrade a non HD split wallet without upgrading to support pre split keypool. Please use -upgradewallet=169900 or -upgradewallet with no version specified.</source> <translation type="unfinished"></translation> </message> @@ -4695,12 +4751,12 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation type="unfinished"></translation> </message> <message> - <location line="+24"/> + <location line="+21"/> <source>Cannot write to data directory '%s'; check permissions.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+39"/> + <location line="+37"/> <source>Loading block index...</source> <translation>Loading block index...</translation> </message> @@ -4710,24 +4766,19 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satos <translation>Loading wallet...</translation> </message> <message> - <location line="-42"/> + <location line="-40"/> <source>Cannot downgrade wallet</source> <translation>Cannot downgrade wallet</translation> </message> <message> - <location line="+50"/> + <location line="+49"/> <source>Rescanning...</source> <translation>Rescanning...</translation> </message> <message> - <location line="-42"/> + <location line="-41"/> <source>Done loading</source> <translation>Done loading</translation> </message> - <message> - <location line="+12"/> - <source>Error</source> - <translation>Error</translation> - </message> </context> </TS> diff --git a/src/qt/macdockiconhandler.mm b/src/qt/macdockiconhandler.mm index 102adce6c5..5eb23c76e6 100644 --- a/src/qt/macdockiconhandler.mm +++ b/src/qt/macdockiconhandler.mm @@ -1,12 +1,11 @@ -// Copyright (c) 2011-2018 The Bitcoin Core developers +// Copyright (c) 2011-2019 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" -#undef slots -#include <objc/objc.h> -#include <objc/message.h> +#include <AppKit/AppKit.h> +#include <objc/runtime.h> static MacDockIconHandler *s_instance = nullptr; @@ -21,9 +20,7 @@ bool dockClickHandler(id self, SEL _cmd, ...) { } void setupDockClickHandler() { - 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")); + Class delClass = (Class)[[[NSApplication sharedApplication] delegate] class]; SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"); class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:"); } @@ -44,3 +41,13 @@ void MacDockIconHandler::cleanup() { delete s_instance; } + +/** + * Force application activation on macOS. With Qt 5.5.1 this is required when + * an action in the Dock menu is triggered. + * TODO: Define a Qt version where it's no-longer necessary. + */ +void ForceActivation() +{ + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; +} diff --git a/src/qt/main.cpp b/src/qt/main.cpp index 6a3c2249d1..999c434d23 100644 --- a/src/qt/main.cpp +++ b/src/qt/main.cpp @@ -4,6 +4,8 @@ #include <qt/bitcoin.h> +#include <util/translation.h> + #include <QCoreApplication> #include <functional> diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 40dc7bf400..57cafaaac0 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -87,12 +87,12 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : /* Display elements init */ QDir translations(":translations"); - ui->bitcoinAtStartup->setToolTip(ui->bitcoinAtStartup->toolTip().arg(tr(PACKAGE_NAME))); - ui->bitcoinAtStartup->setText(ui->bitcoinAtStartup->text().arg(tr(PACKAGE_NAME))); + ui->bitcoinAtStartup->setToolTip(ui->bitcoinAtStartup->toolTip().arg(PACKAGE_NAME)); + ui->bitcoinAtStartup->setText(ui->bitcoinAtStartup->text().arg(PACKAGE_NAME)); - ui->openBitcoinConfButton->setToolTip(ui->openBitcoinConfButton->toolTip().arg(tr(PACKAGE_NAME))); + ui->openBitcoinConfButton->setToolTip(ui->openBitcoinConfButton->toolTip().arg(PACKAGE_NAME)); - ui->lang->setToolTip(ui->lang->toolTip().arg(tr(PACKAGE_NAME))); + ui->lang->setToolTip(ui->lang->toolTip().arg(PACKAGE_NAME)); ui->lang->addItem(QString("(") + tr("default") + QString(")"), QVariant("")); for (const QString &langStr : translations.entryList()) { diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index d8e48f350a..07ffff0126 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -204,9 +204,8 @@ void OverviewPage::updateWatchOnlyLabels(bool showWatchOnly) void OverviewPage::setClientModel(ClientModel *model) { this->clientModel = model; - if(model) - { - // Show warning if this is a prerelease version + if (model) { + // Show warning, for example if this is a prerelease version connect(model, &ClientModel::alertsChanged, this, &OverviewPage::updateAlerts); updateAlerts(model->getStatusBarWarnings()); } diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index c99515fe1c..0bb87742e9 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -41,8 +41,8 @@ #include <QNetworkReply> #include <QNetworkRequest> #include <QSslCertificate> +#include <QSslConfiguration> #include <QSslError> -#include <QSslSocket> #include <QStringList> #include <QTextDocument> #include <QUrlQuery> @@ -448,9 +448,9 @@ void PaymentServer::LoadRootCAs(X509_STORE* _store) certList = QSslCertificate::fromPath(certFile); // Use those certificates when fetching payment requests, too: - QSslSocket::setDefaultCaCertificates(certList); + QSslConfiguration::defaultConfiguration().setCaCertificates(certList); } else - certList = QSslSocket::systemCaCertificates(); + certList = QSslConfiguration::systemCaCertificates(); int nRootCerts = 0; const QDateTime currentTime = QDateTime::currentDateTime(); @@ -666,16 +666,14 @@ void PaymentServer::fetchPaymentACK(WalletModel* walletModel, const SendCoinsRec payment.add_transactions(transaction.data(), transaction.size()); // Create a new refund address, or re-use: - CPubKey newKey; - if (walletModel->wallet().getKeyFromPool(false /* internal */, newKey)) { + CTxDestination dest; + const OutputType change_type = walletModel->wallet().getDefaultChangeType() != OutputType::CHANGE_AUTO ? walletModel->wallet().getDefaultChangeType() : walletModel->wallet().getDefaultAddressType(); + if (walletModel->wallet().getNewDestination(change_type, "", dest)) { // BIP70 requests encode the scriptPubKey directly, so we are not restricted to address // types supported by the receiver. As a result, we choose the address format we also // use for change. Despite an actual payment and not change, this is a close match: // it's the output type we use subject to privacy issues, but not restricted by what // other software supports. - const OutputType change_type = walletModel->wallet().getDefaultChangeType() != OutputType::CHANGE_AUTO ? walletModel->wallet().getDefaultChangeType() : walletModel->wallet().getDefaultAddressType(); - walletModel->wallet().learnRelatedScripts(newKey, change_type); - CTxDestination dest = GetDestinationForKey(newKey, change_type); std::string label = tr("Refund from %1").arg(recipient.authenticatedMerchant).toStdString(); walletModel->wallet().setAddressBook(dest, label, "refund"); diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index 85b691c470..99a9a12fe2 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -11,6 +11,8 @@ #include <interfaces/node.h> #include <sync.h> +#include <algorithm> + #include <QDebug> #include <QList> #include <QTimer> @@ -76,7 +78,7 @@ public: if (sortColumn >= 0) // sort cacheNodeStats (use stable sort to prevent rows jumping around unnecessarily) - qStableSort(cachedNodeStats.begin(), cachedNodeStats.end(), NodeLessThan(sortColumn, sortOrder)); + std::stable_sort(cachedNodeStats.begin(), cachedNodeStats.end(), NodeLessThan(sortColumn, sortOrder)); // build index map mapNodeRows.clear(); diff --git a/src/qt/platformstyle.cpp b/src/qt/platformstyle.cpp index fca2a4e8c5..08d692e44c 100644 --- a/src/qt/platformstyle.cpp +++ b/src/qt/platformstyle.cpp @@ -114,11 +114,6 @@ QIcon PlatformStyle::SingleColorIcon(const QIcon& icon) const return ColorizeIcon(icon, SingleColor()); } -QIcon PlatformStyle::TextColorIcon(const QString& filename) const -{ - return ColorizeIcon(filename, TextColor()); -} - QIcon PlatformStyle::TextColorIcon(const QIcon& icon) const { return ColorizeIcon(icon, TextColor()); diff --git a/src/qt/platformstyle.h b/src/qt/platformstyle.h index 4e763e760e..635aec4c93 100644 --- a/src/qt/platformstyle.h +++ b/src/qt/platformstyle.h @@ -33,9 +33,6 @@ public: /** Colorize an icon (given object) with the icon color */ QIcon SingleColorIcon(const QIcon& icon) const; - /** Colorize an icon (given filename) with the text color */ - QIcon TextColorIcon(const QString& filename) const; - /** Colorize an icon (given object) with the text color */ QIcon TextColorIcon(const QIcon& icon) const; diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index c58717e21e..e8cf432131 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -7,6 +7,7 @@ #include <qt/receivecoinsdialog.h> #include <qt/forms/ui_receivecoinsdialog.h> +#include <interfaces/node.h> #include <qt/addresstablemodel.h> #include <qt/optionsmodel.h> #include <qt/platformstyle.h> @@ -92,10 +93,16 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model) // Last 2 columns are set by the columnResizingFixer, when the table geometry is ready. columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(tableView, AMOUNT_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this); - if (model->wallet().getDefaultAddressType() == OutputType::BECH32) { - ui->useLegacyAddress->setCheckState(Qt::Unchecked); + if (model->node().isAddressTypeSet()) { + // user explicitly set the type, use it + if (model->wallet().getDefaultAddressType() == OutputType::BECH32) { + ui->useLegacyAddress->setCheckState(Qt::Unchecked); + } else { + ui->useLegacyAddress->setCheckState(Qt::Checked); + } } else { - ui->useLegacyAddress->setCheckState(Qt::Checked); + // Always fall back to bech32 in the gui + ui->useLegacyAddress->setCheckState(Qt::Unchecked); } // Set the button to be enabled or disabled based on whether the wallet can give out new addresses. @@ -254,7 +261,7 @@ void ReceiveCoinsDialog::copyColumnToClipboard(int column) if (!firstIndex.isValid()) { return; } - GUIUtil::setClipboard(model->getRecentRequestsTableModel()->data(firstIndex.child(firstIndex.row(), column), Qt::EditRole).toString()); + GUIUtil::setClipboard(model->getRecentRequestsTableModel()->index(firstIndex.row(), column).data(Qt::EditRole).toString()); } // context menu diff --git a/src/qt/recentrequeststablemodel.cpp b/src/qt/recentrequeststablemodel.cpp index aa746017f3..1611ec823c 100644 --- a/src/qt/recentrequeststablemodel.cpp +++ b/src/qt/recentrequeststablemodel.cpp @@ -11,6 +11,8 @@ #include <clientversion.h> #include <streams.h> +#include <algorithm> + RecentRequestsTableModel::RecentRequestsTableModel(WalletModel *parent) : QAbstractTableModel(parent), walletModel(parent) @@ -202,7 +204,7 @@ void RecentRequestsTableModel::addNewRequest(RecentRequestEntry &recipient) void RecentRequestsTableModel::sort(int column, Qt::SortOrder order) { - qSort(list.begin(), list.end(), RecentRequestEntryLessThan(column, order)); + std::sort(list.begin(), list.end(), RecentRequestEntryLessThan(column, order)); Q_EMIT dataChanged(index(0, 0, QModelIndex()), index(list.size() - 1, NUMBER_OF_COLUMNS - 1, QModelIndex())); } diff --git a/src/qt/recentrequeststablemodel.h b/src/qt/recentrequeststablemodel.h index 8a1140e952..130b709d46 100644 --- a/src/qt/recentrequeststablemodel.h +++ b/src/qt/recentrequeststablemodel.h @@ -76,7 +76,7 @@ public: QVariant data(const QModelIndex &index, int role) const; bool setData(const QModelIndex &index, const QVariant &value, int role); QVariant headerData(int section, Qt::Orientation orientation, int role) const; - QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); Qt::ItemFlags flags(const QModelIndex &index) const; /*@}*/ diff --git a/src/qt/res/icons/about.png b/src/qt/res/icons/about.png Binary files differdeleted file mode 100644 index 4143be8bac..0000000000 --- a/src/qt/res/icons/about.png +++ /dev/null diff --git a/src/qt/res/icons/about_qt.png b/src/qt/res/icons/about_qt.png Binary files differdeleted file mode 100644 index c40abfd3a6..0000000000 --- a/src/qt/res/icons/about_qt.png +++ /dev/null diff --git a/src/qt/res/icons/configure.png b/src/qt/res/icons/configure.png Binary files differdeleted file mode 100644 index 5333c83d5e..0000000000 --- a/src/qt/res/icons/configure.png +++ /dev/null diff --git a/src/qt/res/icons/debugwindow.png b/src/qt/res/icons/debugwindow.png Binary files differdeleted file mode 100644 index 290fe60864..0000000000 --- a/src/qt/res/icons/debugwindow.png +++ /dev/null diff --git a/src/qt/res/icons/filesave.png b/src/qt/res/icons/filesave.png Binary files differdeleted file mode 100644 index 779cca1d52..0000000000 --- a/src/qt/res/icons/filesave.png +++ /dev/null diff --git a/src/qt/res/icons/info.png b/src/qt/res/icons/info.png Binary files differdeleted file mode 100644 index 692b50c2a9..0000000000 --- a/src/qt/res/icons/info.png +++ /dev/null diff --git a/src/qt/res/icons/key.png b/src/qt/res/icons/key.png Binary files differdeleted file mode 100644 index f301c4f38c..0000000000 --- a/src/qt/res/icons/key.png +++ /dev/null diff --git a/src/qt/res/icons/open.png b/src/qt/res/icons/open.png Binary files differdeleted file mode 100644 index 4d958f0e18..0000000000 --- a/src/qt/res/icons/open.png +++ /dev/null diff --git a/src/qt/res/icons/quit.png b/src/qt/res/icons/quit.png Binary files differdeleted file mode 100644 index 55e34de4b8..0000000000 --- a/src/qt/res/icons/quit.png +++ /dev/null diff --git a/src/qt/res/icons/verify.png b/src/qt/res/icons/verify.png Binary files differdeleted file mode 100644 index 8e2cb2cc14..0000000000 --- a/src/qt/res/icons/verify.png +++ /dev/null diff --git a/src/qt/res/movies/makespinner.sh b/src/qt/res/movies/makespinner.sh index f47c66e02c..3507837da9 100755 --- a/src/qt/res/movies/makespinner.sh +++ b/src/qt/res/movies/makespinner.sh @@ -9,6 +9,6 @@ FRAMEDIR=$(dirname $0) for i in {0..35} do frame=$(printf "%03d" $i) - angle=$(($i * 10)) + angle=$((i * 10)) convert $FRAMEDIR/../src/spinner.png -background "rgba(0,0,0,0.0)" -distort SRT $angle $FRAMEDIR/spinner-$frame.png done diff --git a/src/qt/res/src/verify.svg b/src/qt/res/src/verify.svg deleted file mode 100644 index 1ff11b7f5e..0000000000 --- a/src/qt/res/src/verify.svg +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 841.9 595.3" enable-background="new 0 0 841.9 595.3" xml:space="preserve">
-<path d="M654.1,317.5c-14.9-9.9-37.2-2.5-44.6,12.4l-62,111.6l-34.7-34.7c-12.4-12.4-34.7-12.4-47.1,0c-12.4,12.4-12.4,34.7,0,47.1
- l67,67c7.4,7.4,14.9,9.9,22.3,9.9h5c9.9-2.5,19.8-7.4,24.8-17.4l81.9-148.8C676.4,347.2,671.5,327.4,654.1,317.5z"/>
-<path d="M326.7,471.3H177.9V362.1l94.3-94.3c-5-14.9-7.4-29.8-7.4-44.6c0-81.9,67-148.8,148.8-148.8s148.8,67,148.8,148.8
- s-67,148.8-148.8,148.8h-37.2v49.6h-49.6L326.7,471.3L326.7,471.3z M227.5,421.7h49.6v-49.6h49.6v-49.6h86.8
- c54.6,0,99.2-44.6,99.2-99.2S468.1,124,413.5,124s-99.2,44.6-99.2,99.2c0,14.9,2.5,27.3,9.9,39.7l7.4,14.9L230,379.5v42.2H227.5z
- M413.5,198.4c14.9,0,24.8,9.9,24.8,24.8c0,14.9-9.9,24.8-24.8,24.8c-14.9,0-24.8-9.9-24.8-24.8
- C388.7,208.3,401.1,198.4,413.5,198.4 M413.5,173.6c-27.3,0-49.6,22.3-49.6,49.6c0,27.3,22.3,49.6,49.6,49.6
- c27.3,0,49.6-22.3,49.6-49.6C463.1,195.9,443.3,173.6,413.5,173.6z"/>
-</svg>
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 071a197c3c..eccc34e12f 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -28,13 +28,12 @@ #include <wallet/wallet.h> #endif -#include <QDesktopWidget> #include <QKeyEvent> #include <QMenu> #include <QMessageBox> #include <QScrollBar> +#include <QScreen> #include <QSettings> -#include <QSignalMapper> #include <QTime> #include <QTimer> #include <QStringList> @@ -451,13 +450,13 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty QSettings settings; if (!restoreGeometry(settings.value("RPCConsoleWindowGeometry").toByteArray())) { // Restore failed (perhaps missing setting), center the window - move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center()); + move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center()); } QChar nonbreaking_hyphen(8209); ui->dataDir->setToolTip(ui->dataDir->toolTip().arg(QString(nonbreaking_hyphen) + "datadir")); ui->blocksDir->setToolTip(ui->blocksDir->toolTip().arg(QString(nonbreaking_hyphen) + "blocksdir")); - ui->openDebugLogfileButton->setToolTip(ui->openDebugLogfileButton->toolTip().arg(tr(PACKAGE_NAME))); + ui->openDebugLogfileButton->setToolTip(ui->openDebugLogfileButton->toolTip().arg(PACKAGE_NAME)); if (platformStyle->getImagesOnButtons()) { ui->openDebugLogfileButton->setIcon(platformStyle->SingleColorIcon(":/icons/export")); @@ -558,6 +557,17 @@ bool RPCConsole::eventFilter(QObject* obj, QEvent *event) void RPCConsole::setClientModel(ClientModel *model) { clientModel = model; + + bool wallet_enabled{false}; +#ifdef ENABLE_WALLET + wallet_enabled = WalletModel::isWalletEnabled(); +#endif // ENABLE_WALLET + if (model && !wallet_enabled) { + // Show warning, for example if this is a prerelease version + connect(model, &ClientModel::alertsChanged, this, &RPCConsole::updateAlerts); + updateAlerts(model->getStatusBarWarnings()); + } + ui->trafficGraph->setClientModel(model); if (model && clientModel->getPeerTableModel() && clientModel->getBanTableModel()) { // Keep up to date with client @@ -603,19 +613,10 @@ void RPCConsole::setClientModel(ClientModel *model) peersTableContextMenu->addAction(banAction7d); peersTableContextMenu->addAction(banAction365d); - // Add a signal mapping to allow dynamic context menu arguments. - // We need to use int (instead of int64_t), because signal mapper only supports - // int or objects, which is okay because max bantime (1 year) is < int_max. - QSignalMapper* signalMapper = new QSignalMapper(this); - signalMapper->setMapping(banAction1h, 60*60); - signalMapper->setMapping(banAction24h, 60*60*24); - signalMapper->setMapping(banAction7d, 60*60*24*7); - signalMapper->setMapping(banAction365d, 60*60*24*365); - connect(banAction1h, &QAction::triggered, signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map)); - connect(banAction24h, &QAction::triggered, signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map)); - connect(banAction7d, &QAction::triggered, signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map)); - connect(banAction365d, &QAction::triggered, signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map)); - connect(signalMapper, static_cast<void (QSignalMapper::*)(int)>(&QSignalMapper::mapped), this, &RPCConsole::banSelectedNode); + connect(banAction1h, &QAction::triggered, [this] { banSelectedNode(60 * 60); }); + connect(banAction24h, &QAction::triggered, [this] { banSelectedNode(60 * 60 * 24); }); + connect(banAction7d, &QAction::triggered, [this] { banSelectedNode(60 * 60 * 24 * 7); }); + connect(banAction365d, &QAction::triggered, [this] { banSelectedNode(60 * 60 * 24 * 365); }); // peer table context menu signals connect(ui->peerWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showPeersTableContextMenu); @@ -804,7 +805,7 @@ void RPCConsole::clear(bool clearHistory) QString clsKey = "Ctrl-L"; #endif - message(CMD_REPLY, (tr("Welcome to the %1 RPC console.").arg(tr(PACKAGE_NAME)) + "<br>" + + message(CMD_REPLY, (tr("Welcome to the %1 RPC console.").arg(PACKAGE_NAME) + "<br>" + tr("Use up and down arrows to navigate history, and %1 to clear screen.").arg("<b>"+clsKey+"</b>") + "<br>" + tr("Type %1 for an overview of available commands.").arg("<b>help</b>") + "<br>" + tr("For more information on using this console type %1.").arg("<b>help-console</b>") + @@ -1120,7 +1121,7 @@ void RPCConsole::updateNodeDetail(const CNodeCombinedStats *stats) ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer)); ui->peerDirection->setText(stats->nodeStats.fInbound ? tr("Inbound") : tr("Outbound")); ui->peerHeight->setText(QString("%1").arg(QString::number(stats->nodeStats.nStartingHeight))); - ui->peerWhitelisted->setText(stats->nodeStats.fWhitelisted ? tr("Yes") : tr("No")); + ui->peerWhitelisted->setText(stats->nodeStats.m_legacyWhitelisted ? tr("Yes") : tr("No")); // This check fails for example if the lock was busy and // nodeStateStats couldn't be fetched. @@ -1265,11 +1266,6 @@ void RPCConsole::showOrHideBanTableIfRequired() ui->banHeading->setVisible(visible); } -RPCConsole::TabTypes RPCConsole::tabFocus() const -{ - return (TabTypes) ui->tabWidget->currentIndex(); -} - void RPCConsole::setTabFocus(enum TabTypes tabType) { ui->tabWidget->setCurrentIndex(tabType); @@ -1279,3 +1275,9 @@ QString RPCConsole::tabTitle(TabTypes tab_type) const { return ui->tabWidget->tabText(tab_type); } + +void RPCConsole::updateAlerts(const QString& warnings) +{ + this->ui->label_alerts->setVisible(!warnings.isEmpty()); + this->ui->label_alerts->setText(warnings); +} diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 79b0f3b19c..3f7a74ba03 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -67,7 +67,6 @@ public: std::vector<TabTypes> tabs() const { return {TAB_INFO, TAB_CONSOLE, TAB_GRAPH, TAB_PEERS}; } - TabTypes tabFocus() const; QString tabTitle(TabTypes tab_type) const; protected: @@ -168,6 +167,9 @@ private: /** Update UI with latest network info from model. */ void updateNetworkState(); + +private Q_SLOTS: + void updateAlerts(const QString& warnings); }; #endif // BITCOIN_QT_RPCCONSOLE_H diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index cb9efe9319..f23c47736f 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -230,8 +230,9 @@ void SendCoinsDialog::on_sendButton_clicked() { recipients.append(entry->getValue()); } - else + else if (valid) { + ui->scrollArea->ensureWidgetVisible(entry); valid = false; } } @@ -392,7 +393,7 @@ void SendCoinsDialog::on_sendButton_clicked() accept(); CoinControlDialog::coinControl()->UnSelectAll(); coinControlUpdateLabels(); - Q_EMIT coinsSent(currentTransaction.getWtx()->get().GetHash()); + Q_EMIT coinsSent(currentTransaction.getWtx()->GetHash()); } fNewRecipientAllowed = true; } @@ -703,7 +704,7 @@ void SendCoinsDialog::updateSmartFeeLabel() int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness(); QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14)); ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }"); - ui->fallbackFeeWarningLabel->setIndent(QFontMetrics(ui->fallbackFeeWarningLabel->font()).width("x")); + ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x")); } else { diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index 0126a2920e..0e5abb89f3 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -8,21 +8,22 @@ #include <qt/splashscreen.h> -#include <qt/networkstyle.h> - #include <clientversion.h> #include <interfaces/handler.h> #include <interfaces/node.h> #include <interfaces/wallet.h> +#include <qt/guiutil.h> +#include <qt/networkstyle.h> #include <ui_interface.h> #include <util/system.h> +#include <util/translation.h> #include <version.h> #include <QApplication> #include <QCloseEvent> -#include <QDesktopWidget> #include <QPainter> #include <QRadialGradient> +#include <QScreen> SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const NetworkStyle *networkStyle) : @@ -39,7 +40,7 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw devicePixelRatio = static_cast<QGuiApplication*>(QCoreApplication::instance())->devicePixelRatio(); // define text to place - QString titleText = tr(PACKAGE_NAME); + QString titleText = PACKAGE_NAME; QString versionText = QString("Version %1").arg(QString::fromStdString(FormatFullVersion())); QString copyrightText = QString::fromUtf8(CopyrightHolders(strprintf("\xc2\xA9 %u-%u ", 2009, COPYRIGHT_YEAR)).c_str()); QString titleAddText = networkStyle->getTitleAddText(); @@ -74,21 +75,21 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw // check font size and drawing with pixPaint.setFont(QFont(font, 33*fontFactor)); QFontMetrics fm = pixPaint.fontMetrics(); - int titleTextWidth = fm.width(titleText); + int titleTextWidth = GUIUtil::TextWidth(fm, titleText); if (titleTextWidth > 176) { fontFactor = fontFactor * 176 / titleTextWidth; } pixPaint.setFont(QFont(font, 33*fontFactor)); fm = pixPaint.fontMetrics(); - titleTextWidth = fm.width(titleText); + titleTextWidth = GUIUtil::TextWidth(fm, titleText); pixPaint.drawText(pixmap.width()/devicePixelRatio-titleTextWidth-paddingRight,paddingTop,titleText); pixPaint.setFont(QFont(font, 15*fontFactor)); // if the version string is too long, reduce size fm = pixPaint.fontMetrics(); - int versionTextWidth = fm.width(versionText); + int versionTextWidth = GUIUtil::TextWidth(fm, versionText); if(versionTextWidth > titleTextWidth+paddingRight-10) { pixPaint.setFont(QFont(font, 10*fontFactor)); titleVersionVSpace -= 5; @@ -110,7 +111,7 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw boldFont.setWeight(QFont::Bold); pixPaint.setFont(boldFont); fm = pixPaint.fontMetrics(); - int titleAddTextWidth = fm.width(titleAddText); + int titleAddTextWidth = GUIUtil::TextWidth(fm, titleAddText); pixPaint.drawText(pixmap.width()/devicePixelRatio-titleAddTextWidth-10,15,titleAddText); } @@ -123,7 +124,7 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw QRect r(QPoint(), QSize(pixmap.size().width()/devicePixelRatio,pixmap.size().height()/devicePixelRatio)); resize(r.size()); setFixedSize(r.size()); - move(QApplication::desktop()->screenGeometry().center() - r.center()); + move(QGuiApplication::primaryScreen()->geometry().center() - r.center()); subscribeToCoreSignals(); installEventFilter(this); @@ -156,18 +157,19 @@ void SplashScreen::finish() static void InitMessage(SplashScreen *splash, const std::string &message) { - QMetaObject::invokeMethod(splash, "showMessage", + bool invoked = QMetaObject::invokeMethod(splash, "showMessage", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(message)), Q_ARG(int, Qt::AlignBottom|Qt::AlignHCenter), Q_ARG(QColor, QColor(55,55,55))); + assert(invoked); } static void ShowProgress(SplashScreen *splash, const std::string &title, int nProgress, bool resume_possible) { InitMessage(splash, title + std::string("\n") + - (resume_possible ? _("(press q to shutdown and continue later)") - : _("press q to shutdown")) + + (resume_possible ? _("(press q to shutdown and continue later)").translated + : _("press q to shutdown").translated) + strprintf("\n%d", nProgress) + "%"); } #ifdef ENABLE_WALLET diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index ea1019ad1d..11a518ebd2 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -12,7 +12,6 @@ #include <key.h> #include <key_io.h> -#include <pubkey.h> #include <wallet/wallet.h> #include <QApplication> diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index 22e49b06cb..49e9e072a8 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -11,6 +11,8 @@ #include <qt/networkstyle.h> #include <qt/rpcconsole.h> #include <shutdown.h> +#include <test/setup_common.h> +#include <univalue.h> #include <validation.h> #if defined(HAVE_CONFIG_H) @@ -26,9 +28,6 @@ #include <QtGlobal> #include <QtTest/QtTestWidgets> #include <QtTest/QtTestGui> -#include <new> -#include <string> -#include <univalue.h> namespace { //! Call getblockchaininfo RPC and check first field of JSON output. @@ -63,6 +62,7 @@ void AppTests::appTests() } #endif + BasicTestingSetup test{CBaseChainParams::REGTEST}; // Create a temp data directory to backup the gui settings to ECC_Stop(); // Already started by the common test setup, so stop it to avoid interference LogInstance().DisconnectTestLogger(); diff --git a/src/qt/test/paymentservertests.cpp b/src/qt/test/paymentservertests.cpp index f0eca899fc..eca468a6ab 100644 --- a/src/qt/test/paymentservertests.cpp +++ b/src/qt/test/paymentservertests.cpp @@ -13,9 +13,10 @@ #include <random.h> #include <script/script.h> #include <script/standard.h> -#include <util/system.h> +#include <test/setup_common.h> #include <util/strencodings.h> +#include <openssl/ssl.h> #include <openssl/x509.h> #include <openssl/x509_vfy.h> @@ -66,7 +67,8 @@ static SendCoinsRecipient handleRequest(PaymentServer* server, std::vector<unsig void PaymentServerTests::paymentServerTests() { - SelectParams(CBaseChainParams::MAIN); + SSL_library_init(); + BasicTestingSetup testing_setup(CBaseChainParams::MAIN); auto node = interfaces::MakeNode(); OptionsModel optionsModel(*node); PaymentServer* server = new PaymentServer(nullptr, false); diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp index 86e5632e2c..3c2ffa6c00 100644 --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -4,7 +4,6 @@ #include <qt/test/rpcnestedtests.h> -#include <fs.h> #include <interfaces/node.h> #include <rpc/server.h> #include <qt/rpcconsole.h> @@ -35,7 +34,6 @@ void RPCNestedTests::rpcNestedTests() tableRPC.appendCommand("rpcNestedTest", &vRPCCommands[0]); //mempool.setSanityCheck(1.0); - LogInstance().DisconnectTestLogger(); // Already started by the common test setup, so stop it to avoid interference TestingSetup test; if (RPCIsInWarmup(nullptr)) SetRPCWarmupFinished(); diff --git a/src/qt/test/rpcnestedtests.h b/src/qt/test/rpcnestedtests.h index e33f4e3da1..97143ff78a 100644 --- a/src/qt/test/rpcnestedtests.h +++ b/src/qt/test/rpcnestedtests.h @@ -8,9 +8,6 @@ #include <QObject> #include <QTest> -#include <txdb.h> -#include <txmempool.h> - class RPCNestedTests : public QObject { Q_OBJECT diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index 9f66c3d3a9..796cf24b36 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -6,7 +6,6 @@ #include <config/bitcoin-config.h> #endif -#include <chainparams.h> #include <interfaces/node.h> #include <qt/bitcoin.h> #include <qt/test/apptests.h> @@ -27,8 +26,6 @@ #include <QObject> #include <QTest> -#include <openssl/ssl.h> - #if defined(QT_STATICPLUGIN) #include <QtPlugin> #if defined(QT_QPA_PLATFORM_MINIMAL) @@ -43,12 +40,18 @@ Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); #endif #endif -extern void noui_connect(); - // This is all you need to run all the tests int main(int argc, char *argv[]) { - BasicTestingSetup test{CBaseChainParams::REGTEST}; + // Initialize persistent globals with the testing setup state for sanity. + // E.g. -datadir in gArgs is set to a temp directory dummy value (instead + // of defaulting to the default datadir), or globalChainParams is set to + // regtest params. + // + // All tests must use their own testing setup (if needed). + { + BasicTestingSetup dummy{CBaseChainParams::REGTEST}; + } auto node = interfaces::MakeNode(); @@ -65,11 +68,9 @@ int main(int argc, char *argv[]) // Don't remove this, it's needed to access // QApplication:: and QCoreApplication:: in the tests - BitcoinApplication app(*node, argc, argv); + BitcoinApplication app(*node); app.setApplicationName("Bitcoin-Qt-test"); - SSL_library_init(); - AppTests app_tests(app); if (QTest::qExec(&app_tests) != 0) { fInvalid = true; diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index e54915ec75..120dff95c0 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -68,7 +68,8 @@ uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDe if (status == CT_NEW) txid = hash; })); ConfirmSend(); - QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked"); + bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked"); + assert(invoked); return txid; } diff --git a/src/qt/trafficgraphwidget.cpp b/src/qt/trafficgraphwidget.cpp index 1588be8da3..006007be63 100644 --- a/src/qt/trafficgraphwidget.cpp +++ b/src/qt/trafficgraphwidget.cpp @@ -104,6 +104,7 @@ void TrafficGraphWidget::paintEvent(QPaintEvent *) } } + painter.setRenderHint(QPainter::Antialiasing); if(!vSamplesIn.empty()) { QPainterPath p; paintPath(p, vSamplesIn); diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 6fe35b13cf..8d0cb54151 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -17,6 +17,8 @@ #include <interfaces/handler.h> #include <uint256.h> +#include <algorithm> + #include <QColor> #include <QDateTime> #include <QDebug> @@ -93,9 +95,9 @@ public: qDebug() << "TransactionTablePriv::updateWallet: " + QString::fromStdString(hash.ToString()) + " " + QString::number(status); // Find bounds of this transaction in model - QList<TransactionRecord>::iterator lower = qLowerBound( + QList<TransactionRecord>::iterator lower = std::lower_bound( cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); - QList<TransactionRecord>::iterator upper = qUpperBound( + QList<TransactionRecord>::iterator upper = std::upper_bound( cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); int lowerIndex = (lower - cachedWallet.begin()); int upperIndex = (upper - cachedWallet.begin()); @@ -687,10 +689,11 @@ public: { QString strHash = QString::fromStdString(hash.GetHex()); qDebug() << "NotifyTransactionChanged: " + strHash + " status= " + QString::number(status); - QMetaObject::invokeMethod(ttm, "updateTransaction", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(ttm, "updateTransaction", Qt::QueuedConnection, Q_ARG(QString, strHash), Q_ARG(int, status), Q_ARG(bool, showTransaction)); + assert(invoked); } private: uint256 hash; @@ -725,12 +728,16 @@ static void ShowProgress(TransactionTableModel *ttm, const std::string &title, i if (nProgress == 100) { fQueueNotifications = false; - if (vQueueNotifications.size() > 10) // prevent balloon spam, show maximum 10 balloons - QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true)); + if (vQueueNotifications.size() > 10) { // prevent balloon spam, show maximum 10 balloons + bool invoked = QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true)); + assert(invoked); + } for (unsigned int i = 0; i < vQueueNotifications.size(); ++i) { - if (vQueueNotifications.size() - i <= 10) - QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false)); + if (vQueueNotifications.size() - i <= 10) { + bool invoked = QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false)); + assert(invoked); + } vQueueNotifications[i].invoke(ttm); } diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 17e174e57a..cbc4ab49f5 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -30,7 +30,6 @@ #include <QMenu> #include <QPoint> #include <QScrollBar> -#include <QSignalMapper> #include <QTableView> #include <QTimer> #include <QUrl> @@ -176,11 +175,6 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa contextMenu->addAction(abandonAction); contextMenu->addAction(editLabelAction); - mapperThirdPartyTxUrls = new QSignalMapper(this); - - // Connect actions - connect(mapperThirdPartyTxUrls, static_cast<void (QSignalMapper::*)(const QString&)>(&QSignalMapper::mapped), this, &TransactionView::openThirdPartyTxUrl); - connect(dateWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseDate); connect(typeWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseType); connect(watchOnlyWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseWatchonly); @@ -246,15 +240,15 @@ void TransactionView::setModel(WalletModel *_model) QStringList listUrls = _model->getOptionsModel()->getThirdPartyTxUrls().split("|", QString::SkipEmptyParts); for (int i = 0; i < listUrls.size(); ++i) { - QString host = QUrl(listUrls[i].trimmed(), QUrl::StrictMode).host(); + QString url = listUrls[i].trimmed(); + QString host = QUrl(url, QUrl::StrictMode).host(); if (!host.isEmpty()) { QAction *thirdPartyTxUrlAction = new QAction(host, this); // use host as menu item label if (i == 0) contextMenu->addSeparator(); contextMenu->addAction(thirdPartyTxUrlAction); - connect(thirdPartyTxUrlAction, &QAction::triggered, mapperThirdPartyTxUrls, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map)); - mapperThirdPartyTxUrls->setMapping(thirdPartyTxUrlAction, listUrls[i].trimmed()); + connect(thirdPartyTxUrlAction, &QAction::triggered, [this, url] { openThirdPartyTxUrl(url); }); } } } diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h index e07181d1c8..79347c371f 100644 --- a/src/qt/transactionview.h +++ b/src/qt/transactionview.h @@ -23,7 +23,6 @@ class QFrame; class QLineEdit; class QMenu; class QModelIndex; -class QSignalMapper; class QTableView; QT_END_NAMESPACE @@ -72,7 +71,6 @@ private: QLineEdit *amountWidget; QMenu *contextMenu; - QSignalMapper *mapperThirdPartyTxUrls; QFrame *dateRangeWidget; QDateTimeEdit *dateFrom; diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index aa810690c9..6509a701f3 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -36,7 +36,7 @@ HelpMessageDialog::HelpMessageDialog(interfaces::Node& node, QWidget *parent, bo { ui->setupUi(this); - QString version = tr(PACKAGE_NAME) + " " + tr("version") + " " + QString::fromStdString(FormatFullVersion()); + QString version = QString{PACKAGE_NAME} + " " + tr("version") + " " + QString::fromStdString(FormatFullVersion()); /* On x86 add a bit specifier to the version so that users can distinguish between * 32 and 64 bit builds. On other architectures, 32/64 bit may be more ambiguous. */ @@ -48,7 +48,7 @@ HelpMessageDialog::HelpMessageDialog(interfaces::Node& node, QWidget *parent, bo if (about) { - setWindowTitle(tr("About %1").arg(tr(PACKAGE_NAME))); + setWindowTitle(tr("About %1").arg(PACKAGE_NAME)); std::string licenseInfo = LicenseInfo(); /// HTML-format the license message from the core @@ -150,7 +150,7 @@ ShutdownWindow::ShutdownWindow(QWidget *parent, Qt::WindowFlags f): { QVBoxLayout *layout = new QVBoxLayout(); layout->addWidget(new QLabel( - tr("%1 is shutting down...").arg(tr(PACKAGE_NAME)) + "<br /><br />" + + tr("%1 is shutting down...").arg(PACKAGE_NAME) + "<br /><br />" + tr("Do not shut down the computer until this window disappears."))); setLayout(layout); } diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 019bd65823..1a4f51c066 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -2,8 +2,14 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <qt/askpassphrasedialog.h> +#include <qt/createwalletdialog.h> +#include <qt/guiconstants.h> +#include <qt/guiutil.h> #include <qt/walletcontroller.h> +#include <wallet/wallet.h> + #include <interfaces/handler.h> #include <interfaces/node.h> @@ -13,10 +19,13 @@ #include <QMessageBox> #include <QMutexLocker> #include <QThread> +#include <QTimer> #include <QWindow> WalletController::WalletController(interfaces::Node& node, const PlatformStyle* platform_style, OptionsModel* options_model, QObject* parent) : QObject(parent) + , m_activity_thread(new QThread(this)) + , m_activity_worker(new QObject) , m_node(node) , m_platform_style(platform_style) , m_options_model(options_model) @@ -29,41 +38,39 @@ WalletController::WalletController(interfaces::Node& node, const PlatformStyle* getOrCreateWallet(std::move(wallet)); } - m_activity_thread.start(); + m_activity_worker->moveToThread(m_activity_thread); + m_activity_thread->start(); } // Not using the default destructor because not all member types definitions are // available in the header, just forward declared. WalletController::~WalletController() { - m_activity_thread.quit(); - m_activity_thread.wait(); + m_activity_thread->quit(); + m_activity_thread->wait(); + delete m_activity_worker; } -std::vector<WalletModel*> WalletController::getWallets() const +std::vector<WalletModel*> WalletController::getOpenWallets() const { QMutexLocker locker(&m_mutex); return m_wallets; } -std::vector<std::string> WalletController::getWalletsAvailableToOpen() const +std::map<std::string, bool> WalletController::listWalletDir() const { QMutexLocker locker(&m_mutex); - std::vector<std::string> wallets = m_node.listWalletDir(); + std::map<std::string, bool> wallets; + for (const std::string& name : m_node.listWalletDir()) { + wallets[name] = false; + } for (WalletModel* wallet_model : m_wallets) { - auto it = std::remove(wallets.begin(), wallets.end(), wallet_model->wallet().getWalletName()); - if (it != wallets.end()) wallets.erase(it); + auto it = wallets.find(wallet_model->wallet().getWalletName()); + if (it != wallets.end()) it->second = true; } return wallets; } -OpenWalletActivity* WalletController::openWallet(const std::string& name, QWidget* parent) -{ - OpenWalletActivity* activity = new OpenWalletActivity(this, name); - activity->moveToThread(&m_activity_thread); - return activity; -} - void WalletController::closeWallet(WalletModel* wallet_model, QWidget* parent) { QMessageBox box(parent); @@ -96,6 +103,9 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wal // Instantiate model and register it. WalletModel* wallet_model = new WalletModel(std::move(wallet), m_node, m_platform_style, m_options_model, nullptr); + // Handler callback runs in a different thread so fix wallet model thread affinity. + wallet_model->moveToThread(thread()); + wallet_model->setParent(this); m_wallets.push_back(wallet_model); connect(wallet_model, &WalletModel::unload, [this, wallet_model] { @@ -116,24 +126,11 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wal connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent); // Notify walletAdded signal on the GUI thread. - if (QThread::currentThread() == thread()) { - addWallet(wallet_model); - } else { - // Handler callback runs in a different thread so fix wallet model thread affinity. - wallet_model->moveToThread(thread()); - QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, Q_ARG(WalletModel*, wallet_model)); - } + Q_EMIT walletAdded(wallet_model); return wallet_model; } -void WalletController::addWallet(WalletModel* wallet_model) -{ - // Take ownership of the wallet model and register it. - wallet_model->setParent(this); - Q_EMIT walletAdded(wallet_model); -} - void WalletController::removeAndDeleteWallet(WalletModel* wallet_model) { // Unregister wallet model. @@ -147,23 +144,147 @@ void WalletController::removeAndDeleteWallet(WalletModel* wallet_model) delete wallet_model; } +WalletControllerActivity::WalletControllerActivity(WalletController* wallet_controller, QWidget* parent_widget) + : QObject(wallet_controller) + , m_wallet_controller(wallet_controller) + , m_parent_widget(parent_widget) +{ +} + +WalletControllerActivity::~WalletControllerActivity() +{ + delete m_progress_dialog; +} + +void WalletControllerActivity::showProgressDialog(const QString& label_text) +{ + m_progress_dialog = new QProgressDialog(m_parent_widget); + + m_progress_dialog->setLabelText(label_text); + m_progress_dialog->setRange(0, 0); + m_progress_dialog->setCancelButton(nullptr); + m_progress_dialog->setWindowModality(Qt::ApplicationModal); + GUIUtil::PolishProgressDialog(m_progress_dialog); +} + +CreateWalletActivity::CreateWalletActivity(WalletController* wallet_controller, QWidget* parent_widget) + : WalletControllerActivity(wallet_controller, parent_widget) +{ + m_passphrase.reserve(MAX_PASSPHRASE_SIZE); +} + +CreateWalletActivity::~CreateWalletActivity() +{ + delete m_create_wallet_dialog; + delete m_passphrase_dialog; +} + +void CreateWalletActivity::askPasshprase() +{ + m_passphrase_dialog = new AskPassphraseDialog(AskPassphraseDialog::Encrypt, m_parent_widget, &m_passphrase); + m_passphrase_dialog->show(); + + connect(m_passphrase_dialog, &QObject::destroyed, [this] { + m_passphrase_dialog = nullptr; + }); + connect(m_passphrase_dialog, &QDialog::accepted, [this] { + createWallet(); + }); + connect(m_passphrase_dialog, &QDialog::rejected, [this] { + Q_EMIT finished(); + }); +} + +void CreateWalletActivity::createWallet() +{ + showProgressDialog(tr("Creating Wallet <b>%1</b>...").arg(m_create_wallet_dialog->walletName().toHtmlEscaped())); + + std::string name = m_create_wallet_dialog->walletName().toStdString(); + uint64_t flags = 0; + if (m_create_wallet_dialog->disablePrivateKeys()) { + flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; + } + if (m_create_wallet_dialog->blank()) { + flags |= WALLET_FLAG_BLANK_WALLET; + } + + QTimer::singleShot(500, worker(), [this, name, flags] { + std::unique_ptr<interfaces::Wallet> wallet; + WalletCreationStatus status = node().createWallet(m_passphrase, flags, name, m_error_message, m_warning_message, wallet); + + if (status == WalletCreationStatus::SUCCESS) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); -OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, const std::string& name) - : m_wallet_controller(wallet_controller) - , m_name(name) -{} + QTimer::singleShot(500, this, &CreateWalletActivity::finish); + }); +} -void OpenWalletActivity::open() +void CreateWalletActivity::finish() { - std::string error, warning; - std::unique_ptr<interfaces::Wallet> wallet = m_wallet_controller->m_node.loadWallet(m_name, error, warning); - if (!warning.empty()) { - Q_EMIT message(QMessageBox::Warning, QString::fromStdString(warning)); + m_progress_dialog->hide(); + + if (!m_error_message.empty()) { + QMessageBox::critical(m_parent_widget, tr("Create wallet failed"), QString::fromStdString(m_error_message)); + } else if (!m_warning_message.empty()) { + QMessageBox::warning(m_parent_widget, tr("Create wallet warning"), QString::fromStdString(m_warning_message)); } - if (wallet) { - Q_EMIT opened(m_wallet_controller->getOrCreateWallet(std::move(wallet))); - } else { - Q_EMIT message(QMessageBox::Critical, QString::fromStdString(error)); + + if (m_wallet_model) Q_EMIT created(m_wallet_model); + + Q_EMIT finished(); +} + +void CreateWalletActivity::create() +{ + m_create_wallet_dialog = new CreateWalletDialog(m_parent_widget); + m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal); + m_create_wallet_dialog->show(); + + connect(m_create_wallet_dialog, &QObject::destroyed, [this] { + m_create_wallet_dialog = nullptr; + }); + connect(m_create_wallet_dialog, &QDialog::rejected, [this] { + Q_EMIT finished(); + }); + connect(m_create_wallet_dialog, &QDialog::accepted, [this] { + if (m_create_wallet_dialog->encrypt()) { + askPasshprase(); + } else { + createWallet(); + } + }); +} + +OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, QWidget* parent_widget) + : WalletControllerActivity(wallet_controller, parent_widget) +{ +} + +void OpenWalletActivity::finish() +{ + m_progress_dialog->hide(); + + if (!m_error_message.empty()) { + QMessageBox::critical(m_parent_widget, tr("Open wallet failed"), QString::fromStdString(m_error_message)); + } else if (!m_warning_message.empty()) { + QMessageBox::warning(m_parent_widget, tr("Open wallet warning"), QString::fromStdString(m_warning_message)); } + + if (m_wallet_model) Q_EMIT opened(m_wallet_model); + Q_EMIT finished(); } + +void OpenWalletActivity::open(const std::string& path) +{ + QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path); + + showProgressDialog(tr("Opening Wallet <b>%1</b>...").arg(name.toHtmlEscaped())); + + QTimer::singleShot(0, worker(), [this, path] { + std::unique_ptr<interfaces::Wallet> wallet = node().loadWallet(path, m_error_message, m_warning_message); + + if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); + + QTimer::singleShot(0, this, &OpenWalletActivity::finish); + }); +} diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index 19b3a82253..4e1a772f3a 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -6,15 +6,20 @@ #define BITCOIN_QT_WALLETCONTROLLER_H #include <qt/walletmodel.h> +#include <support/allocators/secure.h> #include <sync.h> -#include <list> +#include <map> #include <memory> +#include <string> #include <vector> #include <QMessageBox> #include <QMutex> +#include <QProgressDialog> #include <QThread> +#include <QTimer> +#include <QString> class OptionsModel; class PlatformStyle; @@ -24,7 +29,11 @@ class Handler; class Node; } // namespace interfaces +class AskPassphraseDialog; +class CreateWalletActivity; +class CreateWalletDialog; class OpenWalletActivity; +class WalletControllerActivity; /** * Controller between interfaces::Node, WalletModel instances and the GUI. @@ -33,21 +42,22 @@ class WalletController : public QObject { Q_OBJECT - WalletModel* getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet); void removeAndDeleteWallet(WalletModel* wallet_model); public: WalletController(interfaces::Node& node, const PlatformStyle* platform_style, OptionsModel* options_model, QObject* parent); ~WalletController(); - std::vector<WalletModel*> getWallets() const; - std::vector<std::string> getWalletsAvailableToOpen() const; + //! Returns wallet models currently open. + std::vector<WalletModel*> getOpenWallets() const; - OpenWalletActivity* openWallet(const std::string& name, QWidget* parent = nullptr); - void closeWallet(WalletModel* wallet_model, QWidget* parent = nullptr); + WalletModel* getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet); -private Q_SLOTS: - void addWallet(WalletModel* wallet_model); + //! Returns all wallet names in the wallet dir mapped to whether the wallet + //! is loaded. + std::map<std::string, bool> listWalletDir() const; + + void closeWallet(WalletModel* wallet_model, QWidget* parent = nullptr); Q_SIGNALS: void walletAdded(WalletModel* wallet_model); @@ -56,7 +66,8 @@ Q_SIGNALS: void coinsSent(WalletModel* wallet_model, SendCoinsRecipient recipient, QByteArray transaction); private: - QThread m_activity_thread; + QThread* const m_activity_thread; + QObject* const m_activity_worker; interfaces::Node& m_node; const PlatformStyle* const m_platform_style; OptionsModel* const m_options_model; @@ -64,27 +75,72 @@ private: std::vector<WalletModel*> m_wallets; std::unique_ptr<interfaces::Handler> m_handler_load_wallet; - friend class OpenWalletActivity; + friend class WalletControllerActivity; }; -class OpenWalletActivity : public QObject +class WalletControllerActivity : public QObject { Q_OBJECT public: - OpenWalletActivity(WalletController* wallet_controller, const std::string& name); - -public Q_SLOTS: - void open(); + WalletControllerActivity(WalletController* wallet_controller, QWidget* parent_widget); + virtual ~WalletControllerActivity(); Q_SIGNALS: - void message(QMessageBox::Icon icon, const QString text); void finished(); + +protected: + interfaces::Node& node() const { return m_wallet_controller->m_node; } + QObject* worker() const { return m_wallet_controller->m_activity_worker; } + + void showProgressDialog(const QString& label_text); + + WalletController* const m_wallet_controller; + QWidget* const m_parent_widget; + QProgressDialog* m_progress_dialog{nullptr}; + WalletModel* m_wallet_model{nullptr}; + std::string m_error_message; + std::string m_warning_message; +}; + + +class CreateWalletActivity : public WalletControllerActivity +{ + Q_OBJECT + +public: + CreateWalletActivity(WalletController* wallet_controller, QWidget* parent_widget); + virtual ~CreateWalletActivity(); + + void create(); + +Q_SIGNALS: + void created(WalletModel* wallet_model); + +private: + void askPasshprase(); + void createWallet(); + void finish(); + + SecureString m_passphrase; + CreateWalletDialog* m_create_wallet_dialog{nullptr}; + AskPassphraseDialog* m_passphrase_dialog{nullptr}; +}; + +class OpenWalletActivity : public WalletControllerActivity +{ + Q_OBJECT + +public: + OpenWalletActivity(WalletController* wallet_controller, QWidget* parent_widget); + + void open(const std::string& path); + +Q_SIGNALS: void opened(WalletModel* wallet_model); private: - WalletController* const m_wallet_controller; - std::string const m_name; + void finish(); }; #endif // BITCOIN_QT_WALLETCONTROLLER_H diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index a2b295df21..49a13330ec 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -221,11 +221,12 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact return TransactionCreationFailed; } - // reject absurdly high fee. (This can never happen because the - // wallet caps the fee at m_default_max_tx_fee. This merely serves as a - // belt-and-suspenders check) - if (nFeeRequired > m_wallet->getDefaultMaxTxFee()) + // Reject absurdly high fee. (This can never happen because the + // wallet never creates transactions with fee greater than + // m_default_max_tx_fee. This merely a belt-and-suspenders check). + if (nFeeRequired > m_wallet->getDefaultMaxTxFee()) { return AbsurdFee; + } } return SendCoinsReturn(OK); @@ -260,11 +261,11 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran auto& newTx = transaction.getWtx(); std::string rejectReason; - if (!newTx->commit({} /* mapValue */, std::move(vOrderForm), rejectReason)) + if (!wallet().commitTransaction(newTx, {} /* mapValue */, std::move(vOrderForm), rejectReason)) return SendCoinsReturn(TransactionCommitFailed, QString::fromStdString(rejectReason)); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << newTx->get(); + ssTx << *newTx; transaction_array.append(&(ssTx[0]), ssTx.size()); } @@ -376,13 +377,15 @@ bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureStri static void NotifyUnload(WalletModel* walletModel) { qDebug() << "NotifyUnload"; - QMetaObject::invokeMethod(walletModel, "unload"); + bool invoked = QMetaObject::invokeMethod(walletModel, "unload"); + assert(invoked); } static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel) { qDebug() << "NotifyKeyStoreStatusChanged"; - QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); + bool invoked = QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); + assert(invoked); } static void NotifyAddressBookChanged(WalletModel *walletmodel, @@ -394,38 +397,43 @@ static void NotifyAddressBookChanged(WalletModel *walletmodel, QString strPurpose = QString::fromStdString(purpose); qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status); - QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, Q_ARG(QString, strAddress), Q_ARG(QString, strLabel), Q_ARG(bool, isMine), Q_ARG(QString, strPurpose), Q_ARG(int, status)); + assert(invoked); } static void NotifyTransactionChanged(WalletModel *walletmodel, const uint256 &hash, ChangeType status) { Q_UNUSED(hash); Q_UNUSED(status); - QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection); + bool invoked = QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection); + assert(invoked); } static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress) { // emits signal "showProgress" - QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(title)), Q_ARG(int, nProgress)); + assert(invoked); } static void NotifyWatchonlyChanged(WalletModel *walletmodel, bool fHaveWatchonly) { - QMetaObject::invokeMethod(walletmodel, "updateWatchOnlyFlag", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(walletmodel, "updateWatchOnlyFlag", Qt::QueuedConnection, Q_ARG(bool, fHaveWatchonly)); + assert(invoked); } static void NotifyCanGetAddressesChanged(WalletModel* walletmodel) { - QMetaObject::invokeMethod(walletmodel, "canGetAddressesChanged"); + bool invoked = QMetaObject::invokeMethod(walletmodel, "canGetAddressesChanged"); + assert(invoked); } void WalletModel::subscribeToCoreSignals() diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index 8c0dc276b0..d00ccf70d9 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -21,14 +21,14 @@ QList<SendCoinsRecipient> WalletModelTransaction::getRecipients() const return recipients; } -std::unique_ptr<interfaces::PendingWalletTx>& WalletModelTransaction::getWtx() +CTransactionRef& WalletModelTransaction::getWtx() { return wtx; } unsigned int WalletModelTransaction::getTransactionSize() { - return wtx ? GetVirtualTransactionSize(wtx->get()) : 0; + return wtx ? GetVirtualTransactionSize(*wtx) : 0; } CAmount WalletModelTransaction::getTransactionFee() const @@ -43,7 +43,7 @@ void WalletModelTransaction::setTransactionFee(const CAmount& newFee) void WalletModelTransaction::reassignAmounts(int nChangePosRet) { - const CTransaction* walletTransaction = &wtx->get(); + const CTransaction* walletTransaction = wtx.get(); int i = 0; for (QList<SendCoinsRecipient>::iterator it = recipients.begin(); it != recipients.end(); ++it) { diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index 289aee847b..a41d8f2457 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -16,7 +16,6 @@ class SendCoinsRecipient; namespace interfaces { class Node; -class PendingWalletTx; } /** Data model for a walletmodel transaction. */ @@ -27,7 +26,7 @@ public: QList<SendCoinsRecipient> getRecipients() const; - std::unique_ptr<interfaces::PendingWalletTx>& getWtx(); + CTransactionRef& getWtx(); unsigned int getTransactionSize(); void setTransactionFee(const CAmount& newFee); @@ -39,7 +38,7 @@ public: private: QList<SendCoinsRecipient> recipients; - std::unique_ptr<interfaces::PendingWalletTx> wtx; + CTransactionRef wtx; CAmount fee; }; diff --git a/src/random.cpp b/src/random.cpp index de26e6de1a..675b177af3 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -667,6 +667,11 @@ uint64_t GetRand(uint64_t nMax) noexcept return FastRandomContext(g_mock_deterministic_tests).randrange(nMax); } +std::chrono::microseconds GetRandMicros(std::chrono::microseconds duration_max) noexcept +{ + return std::chrono::microseconds{GetRand(duration_max.count())}; +} + int GetRandInt(int nMax) noexcept { return GetRand(nMax); diff --git a/src/random.h b/src/random.h index 75d037738d..22801ec155 100644 --- a/src/random.h +++ b/src/random.h @@ -10,7 +10,8 @@ #include <crypto/common.h> #include <uint256.h> -#include <stdint.h> +#include <chrono> // For std::chrono::microseconds +#include <cstdint> #include <limits> /** @@ -69,6 +70,7 @@ */ void GetRandBytes(unsigned char* buf, int num) noexcept; uint64_t GetRand(uint64_t nMax) noexcept; +std::chrono::microseconds GetRandMicros(std::chrono::microseconds duration_max) noexcept; int GetRandInt(int nMax) noexcept; uint256 GetRandHash() noexcept; diff --git a/src/rest.cpp b/src/rest.cpp index ab409947d3..2c4d475542 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -12,6 +12,7 @@ #include <primitives/block.h> #include <primitives/transaction.h> #include <rpc/blockchain.h> +#include <rpc/protocol.h> #include <rpc/server.h> #include <streams.h> #include <sync.h> @@ -502,12 +503,12 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart) if (fCheckMemPool) { // use db+mempool as cache backend in case user likes to query mempool LOCK2(cs_main, mempool.cs); - CCoinsViewCache& viewChain = *pcoinsTip; + CCoinsViewCache& viewChain = ::ChainstateActive().CoinsTip(); CCoinsViewMemPool viewMempool(&viewChain, mempool); process_utxos(viewMempool, mempool); } else { LOCK(cs_main); // no need to lock mempool! - process_utxos(*pcoinsTip, CTxMemPool()); + process_utxos(::ChainstateActive().CoinsTip(), CTxMemPool()); } for (size_t i = 0; i < hits.size(); ++i) { diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 8a541ecde1..9513c2b9ac 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -10,11 +10,11 @@ #include <chain.h> #include <chainparams.h> #include <coins.h> +#include <node/coinstats.h> #include <consensus/validation.h> #include <core_io.h> #include <hash.h> #include <index/blockfilterindex.h> -#include <key_io.h> #include <policy/feerate.h> #include <policy/policy.h> #include <policy/rbf.h> @@ -42,9 +42,9 @@ #include <boost/thread/thread.hpp> // boost::thread::interrupt +#include <condition_variable> #include <memory> #include <mutex> -#include <condition_variable> struct CUpdatedBlock { @@ -166,10 +166,9 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn static UniValue getblockcount(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getblockcount", - "\nReturns the number of blocks in the longest blockchain.\n", + "\nReturns the height of the most-work fully-validated chain.\n" + "The genesis block has height 0.\n", {}, RPCResult{ "n (numeric) The current block count\n" @@ -178,7 +177,7 @@ static UniValue getblockcount(const JSONRPCRequest& request) HelpExampleCli("getblockcount", "") + HelpExampleRpc("getblockcount", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); return ::ChainActive().Height(); @@ -186,10 +185,8 @@ static UniValue getblockcount(const JSONRPCRequest& request) static UniValue getbestblockhash(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getbestblockhash", - "\nReturns the hash of the best (tip) block in the longest blockchain.\n", + "\nReturns the hash of the best (tip) block in the most-work fully-validated chain.\n", {}, RPCResult{ "\"hex\" (string) the block hash, hex-encoded\n" @@ -198,7 +195,7 @@ static UniValue getbestblockhash(const JSONRPCRequest& request) HelpExampleCli("getbestblockhash", "") + HelpExampleRpc("getbestblockhash", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); return ::ChainActive().Tip()->GetBlockHash().GetHex(); @@ -216,8 +213,6 @@ void RPCNotifyBlockChange(bool ibd, const CBlockIndex * pindex) static UniValue waitfornewblock(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"waitfornewblock", "\nWaits for a specific new block and returns useful info about it.\n" "\nReturns the current block on timeout or exit.\n", @@ -234,7 +229,7 @@ static UniValue waitfornewblock(const JSONRPCRequest& request) HelpExampleCli("waitfornewblock", "1000") + HelpExampleRpc("waitfornewblock", "1000") }, - }.ToString()); + }.Check(request); int timeout = 0; if (!request.params[0].isNull()) timeout = request.params[0].get_int(); @@ -257,8 +252,6 @@ static UniValue waitfornewblock(const JSONRPCRequest& request) static UniValue waitforblock(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"waitforblock", "\nWaits for a specific new block and returns useful info about it.\n" "\nReturns the current block on timeout or exit.\n", @@ -276,7 +269,7 @@ static UniValue waitforblock(const JSONRPCRequest& request) HelpExampleCli("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\", 1000") + HelpExampleRpc("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\", 1000") }, - }.ToString()); + }.Check(request); int timeout = 0; uint256 hash(ParseHashV(request.params[0], "blockhash")); @@ -302,8 +295,6 @@ static UniValue waitforblock(const JSONRPCRequest& request) static UniValue waitforblockheight(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"waitforblockheight", "\nWaits for (at least) block height and returns the height and hash\n" "of the current tip.\n" @@ -322,7 +313,7 @@ static UniValue waitforblockheight(const JSONRPCRequest& request) HelpExampleCli("waitforblockheight", "\"100\", 1000") + HelpExampleRpc("waitforblockheight", "\"100\", 1000") }, - }.ToString()); + }.Check(request); int timeout = 0; int height = request.params[0].get_int(); @@ -347,8 +338,6 @@ static UniValue waitforblockheight(const JSONRPCRequest& request) static UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 0) { - throw std::runtime_error( RPCHelpMan{"syncwithvalidationinterfacequeue", "\nWaits for the validation interface queue to catch up on everything that was there when we entered this function.\n", {}, @@ -357,16 +346,14 @@ static UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) HelpExampleCli("syncwithvalidationinterfacequeue","") + HelpExampleRpc("syncwithvalidationinterfacequeue","") }, - }.ToString()); - } + }.Check(request); + SyncWithValidationInterfaceQueue(); return NullUniValue; } static UniValue getdifficulty(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getdifficulty", "\nReturns the proof-of-work difficulty as a multiple of the minimum difficulty.\n", {}, @@ -377,7 +364,7 @@ static UniValue getdifficulty(const JSONRPCRequest& request) HelpExampleCli("getdifficulty", "") + HelpExampleRpc("getdifficulty", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); return GetDifficulty(::ChainActive().Tip()); @@ -388,6 +375,7 @@ static std::string EntryDescriptionString() return " \"vsize\" : n, (numeric) virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted.\n" " \"size\" : n, (numeric) (DEPRECATED) same as vsize. Only returned if bitcoind is started with -deprecatedrpc=size\n" " size will be completely removed in v0.20.\n" + " \"weight\" : n, (numeric) transaction weight as defined in BIP 141.\n" " \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + " (DEPRECATED)\n" " \"modifiedfee\" : n, (numeric) transaction fee with fee deltas used for mining priority (DEPRECATED)\n" " \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n" @@ -427,6 +415,7 @@ static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPool info.pushKV("vsize", (int)e.GetTxSize()); if (IsDeprecatedRPCEnabled("size")) info.pushKV("size", (int)e.GetTxSize()); + info.pushKV("weight", (int)e.GetTxWeight()); info.pushKV("fee", ValueFromAmount(e.GetFee())); info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee())); info.pushKV("time", e.GetTime()); @@ -504,8 +493,6 @@ UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose) static UniValue getrawmempool(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"getrawmempool", "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n" "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n", @@ -528,7 +515,7 @@ static UniValue getrawmempool(const JSONRPCRequest& request) HelpExampleCli("getrawmempool", "true") + HelpExampleRpc("getrawmempool", "true") }, - }.ToString()); + }.Check(request); bool fVerbose = false; if (!request.params[0].isNull()) @@ -539,8 +526,6 @@ static UniValue getrawmempool(const JSONRPCRequest& request) static UniValue getmempoolancestors(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"getmempoolancestors", "\nIf txid is in the mempool, returns all in-mempool ancestors.\n", { @@ -566,8 +551,7 @@ static UniValue getmempoolancestors(const JSONRPCRequest& request) HelpExampleCli("getmempoolancestors", "\"mytxid\"") + HelpExampleRpc("getmempoolancestors", "\"mytxid\"") }, - }.ToString()); - } + }.Check(request); bool fVerbose = false; if (!request.params[1].isNull()) @@ -609,8 +593,6 @@ static UniValue getmempoolancestors(const JSONRPCRequest& request) static UniValue getmempooldescendants(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"getmempooldescendants", "\nIf txid is in the mempool, returns all in-mempool descendants.\n", { @@ -636,8 +618,7 @@ static UniValue getmempooldescendants(const JSONRPCRequest& request) HelpExampleCli("getmempooldescendants", "\"mytxid\"") + HelpExampleRpc("getmempooldescendants", "\"mytxid\"") }, - }.ToString()); - } + }.Check(request); bool fVerbose = false; if (!request.params[1].isNull()) @@ -679,8 +660,6 @@ static UniValue getmempooldescendants(const JSONRPCRequest& request) static UniValue getmempoolentry(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"getmempoolentry", "\nReturns mempool data for given transaction\n", { @@ -695,8 +674,7 @@ static UniValue getmempoolentry(const JSONRPCRequest& request) HelpExampleCli("getmempoolentry", "\"mytxid\"") + HelpExampleRpc("getmempoolentry", "\"mytxid\"") }, - }.ToString()); - } + }.Check(request); uint256 hash = ParseHashV(request.params[0], "parameter 1"); @@ -715,8 +693,6 @@ static UniValue getmempoolentry(const JSONRPCRequest& request) static UniValue getblockhash(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"getblockhash", "\nReturns hash of block in best-block-chain at height provided.\n", { @@ -729,7 +705,7 @@ static UniValue getblockhash(const JSONRPCRequest& request) HelpExampleCli("getblockhash", "1000") + HelpExampleRpc("getblockhash", "1000") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -743,8 +719,6 @@ static UniValue getblockhash(const JSONRPCRequest& request) static UniValue getblockheader(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"getblockheader", "\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", @@ -780,7 +754,7 @@ static UniValue getblockheader(const JSONRPCRequest& request) HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") }, - }.ToString()); + }.Check(request); uint256 hash(ParseHashV(request.params[0], "hash")); @@ -846,7 +820,7 @@ static CBlockUndo GetUndoChecked(const CBlockIndex* pblockindex) static UniValue getblock(const JSONRPCRequest& request) { - const RPCHelpMan help{"getblock", + RPCHelpMan{"getblock", "\nIf verbosity is 0, returns a string that is serialized, hex-encoded data for block 'hash'.\n" "If verbosity is 1, returns an Object with information about block <hash>.\n" "If verbosity is 2, returns an Object with information about block <hash> and information about each transaction. \n", @@ -898,11 +872,7 @@ static UniValue getblock(const JSONRPCRequest& request) HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); uint256 hash(ParseHashV(request.params[0], "blockhash")); @@ -940,81 +910,8 @@ static UniValue getblock(const JSONRPCRequest& request) return blockToJSON(block, tip, pblockindex, verbosity >= 2); } -struct CCoinsStats -{ - int nHeight; - uint256 hashBlock; - uint64_t nTransactions; - uint64_t nTransactionOutputs; - uint64_t nBogoSize; - uint256 hashSerialized; - uint64_t nDiskSize; - CAmount nTotalAmount; - - CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nBogoSize(0), nDiskSize(0), nTotalAmount(0) {} -}; - -static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs) -{ - assert(!outputs.empty()); - ss << hash; - ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase ? 1u : 0u); - stats.nTransactions++; - for (const auto& output : outputs) { - ss << VARINT(output.first + 1); - ss << output.second.out.scriptPubKey; - ss << VARINT(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED); - stats.nTransactionOutputs++; - stats.nTotalAmount += output.second.out.nValue; - stats.nBogoSize += 32 /* txid */ + 4 /* vout index */ + 4 /* height + coinbase */ + 8 /* amount */ + - 2 /* scriptPubKey len */ + output.second.out.scriptPubKey.size() /* scriptPubKey */; - } - ss << VARINT(0u); -} - -//! Calculate statistics about the unspent transaction output set -static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) -{ - std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor()); - assert(pcursor); - - CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); - stats.hashBlock = pcursor->GetBestBlock(); - { - LOCK(cs_main); - stats.nHeight = LookupBlockIndex(stats.hashBlock)->nHeight; - } - ss << stats.hashBlock; - uint256 prevkey; - std::map<uint32_t, Coin> outputs; - while (pcursor->Valid()) { - boost::this_thread::interruption_point(); - COutPoint key; - Coin coin; - if (pcursor->GetKey(key) && pcursor->GetValue(coin)) { - if (!outputs.empty() && key.hash != prevkey) { - ApplyStats(stats, ss, prevkey, outputs); - outputs.clear(); - } - prevkey = key.hash; - outputs[key.n] = std::move(coin); - } else { - return error("%s: unable to read value", __func__); - } - pcursor->Next(); - } - if (!outputs.empty()) { - ApplyStats(stats, ss, prevkey, outputs); - } - stats.hashSerialized = ss.GetHash(); - stats.nDiskSize = view->EstimateSize(); - return true; -} - static UniValue pruneblockchain(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"pruneblockchain", "", { {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block height to prune up to. May be set to a discrete height, or a unix timestamp\n" @@ -1027,7 +924,7 @@ static UniValue pruneblockchain(const JSONRPCRequest& request) HelpExampleCli("pruneblockchain", "1000") + HelpExampleRpc("pruneblockchain", "1000") }, - }.ToString()); + }.Check(request); if (!fPruneMode) throw JSONRPCError(RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode."); @@ -1071,8 +968,6 @@ static UniValue pruneblockchain(const JSONRPCRequest& request) static UniValue gettxoutsetinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"gettxoutsetinfo", "\nReturns statistics about the unspent transaction output set.\n" "Note this call may take some time.\n", @@ -1093,13 +988,15 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request) HelpExampleCli("gettxoutsetinfo", "") + HelpExampleRpc("gettxoutsetinfo", "") }, - }.ToString()); + }.Check(request); UniValue ret(UniValue::VOBJ); CCoinsStats stats; ::ChainstateActive().ForceFlushStateToDisk(); - if (GetUTXOStats(pcoinsdbview.get(), stats)) { + + CCoinsView* coins_view = WITH_LOCK(cs_main, return &ChainstateActive().CoinsDB()); + if (GetUTXOStats(coins_view, stats)) { ret.pushKV("height", (int64_t)stats.nHeight); ret.pushKV("bestblock", stats.hashBlock.GetHex()); ret.pushKV("transactions", (int64_t)stats.nTransactions); @@ -1116,8 +1013,6 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request) UniValue gettxout(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) - throw std::runtime_error( RPCHelpMan{"gettxout", "\nReturns details about an unspent transaction output.\n", { @@ -1151,7 +1046,7 @@ UniValue gettxout(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("gettxout", "\"txid\", 1") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -1165,19 +1060,21 @@ UniValue gettxout(const JSONRPCRequest& request) fMempool = request.params[2].get_bool(); Coin coin; + CCoinsViewCache* coins_view = &::ChainstateActive().CoinsTip(); + if (fMempool) { LOCK(mempool.cs); - CCoinsViewMemPool view(pcoinsTip.get(), mempool); + CCoinsViewMemPool view(coins_view, mempool); if (!view.GetCoin(out, coin) || mempool.isSpent(out)) { return NullUniValue; } } else { - if (!pcoinsTip->GetCoin(out, coin)) { + if (!coins_view->GetCoin(out, coin)) { return NullUniValue; } } - const CBlockIndex* pindex = LookupBlockIndex(pcoinsTip->GetBestBlock()); + const CBlockIndex* pindex = LookupBlockIndex(coins_view->GetBestBlock()); ret.pushKV("bestblock", pindex->GetBlockHash().GetHex()); if (coin.nHeight == MEMPOOL_HEIGHT) { ret.pushKV("confirmations", 0); @@ -1197,8 +1094,6 @@ static UniValue verifychain(const JSONRPCRequest& request) { int nCheckLevel = gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL); int nCheckDepth = gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS); - if (request.fHelp || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"verifychain", "\nVerifies blockchain database.\n", { @@ -1212,7 +1107,7 @@ static UniValue verifychain(const JSONRPCRequest& request) HelpExampleCli("verifychain", "") + HelpExampleRpc("verifychain", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -1221,57 +1116,53 @@ static UniValue verifychain(const JSONRPCRequest& request) if (!request.params[1].isNull()) nCheckDepth = request.params[1].get_int(); - return CVerifyDB().VerifyDB(Params(), pcoinsTip.get(), nCheckLevel, nCheckDepth); + return CVerifyDB().VerifyDB( + Params(), &::ChainstateActive().CoinsTip(), nCheckLevel, nCheckDepth); } -/** Implementation of IsSuperMajority with better feedback */ -static UniValue SoftForkMajorityDesc(int version, const CBlockIndex* pindex, const Consensus::Params& consensusParams) +static void BuriedForkDescPushBack(UniValue& softforks, const std::string &name, int height) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - UniValue rv(UniValue::VOBJ); - bool activated = false; - switch(version) - { - case 2: - activated = pindex->nHeight >= consensusParams.BIP34Height; - break; - case 3: - activated = pindex->nHeight >= consensusParams.BIP66Height; - break; - case 4: - activated = pindex->nHeight >= consensusParams.BIP65Height; - break; - } - rv.pushKV("status", activated); - return rv; -} + // For buried deployments. + // A buried deployment is one where the height of the activation has been hardcoded into + // the client implementation long after the consensus change has activated. See BIP 90. + // Buried deployments with activation height value of + // std::numeric_limits<int>::max() are disabled and thus hidden. + if (height == std::numeric_limits<int>::max()) return; -static UniValue SoftForkDesc(const std::string &name, int version, const CBlockIndex* pindex, const Consensus::Params& consensusParams) -{ UniValue rv(UniValue::VOBJ); - rv.pushKV("id", name); - rv.pushKV("version", version); - rv.pushKV("reject", SoftForkMajorityDesc(version, pindex, consensusParams)); - return rv; + rv.pushKV("type", "buried"); + // getblockchaininfo reports the softfork as active from when the chain height is + // one below the activation height + rv.pushKV("active", ::ChainActive().Tip()->nHeight + 1 >= height); + rv.pushKV("height", height); + softforks.pushKV(name, rv); } -static UniValue BIP9SoftForkDesc(const Consensus::Params& consensusParams, Consensus::DeploymentPos id) +static void BIP9SoftForkDescPushBack(UniValue& softforks, const std::string &name, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - UniValue rv(UniValue::VOBJ); + // For BIP9 deployments. + // Deployments (e.g. testdummy) with timeout value before Jan 1, 2009 are hidden. + // A timeout value of 0 guarantees a softfork will never be activated. + // This is used when merging logic to implement a proposed softfork without a specified deployment schedule. + if (consensusParams.vDeployments[id].nTimeout <= 1230768000) return; + + UniValue bip9(UniValue::VOBJ); const ThresholdState thresholdState = VersionBitsTipState(consensusParams, id); switch (thresholdState) { - case ThresholdState::DEFINED: rv.pushKV("status", "defined"); break; - case ThresholdState::STARTED: rv.pushKV("status", "started"); break; - case ThresholdState::LOCKED_IN: rv.pushKV("status", "locked_in"); break; - case ThresholdState::ACTIVE: rv.pushKV("status", "active"); break; - case ThresholdState::FAILED: rv.pushKV("status", "failed"); break; + case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break; + case ThresholdState::STARTED: bip9.pushKV("status", "started"); break; + case ThresholdState::LOCKED_IN: bip9.pushKV("status", "locked_in"); break; + case ThresholdState::ACTIVE: bip9.pushKV("status", "active"); break; + case ThresholdState::FAILED: bip9.pushKV("status", "failed"); break; } if (ThresholdState::STARTED == thresholdState) { - rv.pushKV("bit", consensusParams.vDeployments[id].bit); + bip9.pushKV("bit", consensusParams.vDeployments[id].bit); } - rv.pushKV("startTime", consensusParams.vDeployments[id].nStartTime); - rv.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); - rv.pushKV("since", VersionBitsTipStateSinceHeight(consensusParams, id)); + bip9.pushKV("startTime", consensusParams.vDeployments[id].nStartTime); + bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); + int64_t since_height = VersionBitsTipStateSinceHeight(consensusParams, id); + bip9.pushKV("since", since_height); if (ThresholdState::STARTED == thresholdState) { UniValue statsUV(UniValue::VOBJ); @@ -1281,31 +1172,29 @@ static UniValue BIP9SoftForkDesc(const Consensus::Params& consensusParams, Conse statsUV.pushKV("elapsed", statsStruct.elapsed); statsUV.pushKV("count", statsStruct.count); statsUV.pushKV("possible", statsStruct.possible); - rv.pushKV("statistics", statsUV); + bip9.pushKV("statistics", statsUV); } - return rv; -} -static void BIP9SoftForkDescPushBack(UniValue& bip9_softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) -{ - // Deployments with timeout value of 0 are hidden. - // A timeout value of 0 guarantees a softfork will never be activated. - // This is used when softfork codes are merged without specifying the deployment schedule. - if (consensusParams.vDeployments[id].nTimeout > 0) - bip9_softforks.pushKV(VersionBitsDeploymentInfo[id].name, BIP9SoftForkDesc(consensusParams, id)); + UniValue rv(UniValue::VOBJ); + rv.pushKV("type", "bip9"); + rv.pushKV("bip9", bip9); + if (ThresholdState::ACTIVE == thresholdState) { + rv.pushKV("height", since_height); + } + rv.pushKV("active", ThresholdState::ACTIVE == thresholdState); + + softforks.pushKV(name, rv); } UniValue getblockchaininfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getblockchaininfo", "Returns an object containing various state info regarding blockchain processing.\n", {}, RPCResult{ "{\n" " \"chain\": \"xxxx\", (string) current network name as defined in BIP70 (main, test, regtest)\n" - " \"blocks\": xxxxxx, (numeric) the current number of blocks processed in the server\n" + " \"blocks\": xxxxxx, (numeric) the height of the most-work fully-validated chain. The genesis block has height 0\n" " \"headers\": xxxxxx, (numeric) the current number of headers we have validated\n" " \"bestblockhash\": \"...\", (string) the hash of the currently best block\n" " \"difficulty\": xxxxxx, (numeric) the current difficulty\n" @@ -1318,29 +1207,25 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) " \"pruneheight\": xxxxxx, (numeric) lowest-height complete block stored (only present if pruning is enabled)\n" " \"automatic_pruning\": xx, (boolean) whether automatic pruning is enabled (only present if pruning is enabled)\n" " \"prune_target_size\": xxxxxx, (numeric) the target size used by pruning (only present if automatic pruning is enabled)\n" - " \"softforks\": [ (array) status of softforks in progress\n" - " {\n" - " \"id\": \"xxxx\", (string) name of softfork\n" - " \"version\": xx, (numeric) block version\n" - " \"reject\": { (object) progress toward rejecting pre-softfork blocks\n" - " \"status\": xx, (boolean) true if threshold reached\n" - " },\n" - " }, ...\n" - " ],\n" - " \"bip9_softforks\": { (object) status of BIP9 softforks in progress\n" + " \"softforks\": { (object) status of softforks\n" " \"xxxx\" : { (string) name of the softfork\n" - " \"status\": \"xxxx\", (string) one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\"\n" - " \"bit\": xx, (numeric) the bit (0-28) in the block version field used to signal this softfork (only for \"started\" status)\n" - " \"startTime\": xx, (numeric) the minimum median time past of a block at which the bit gains its meaning\n" - " \"timeout\": xx, (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in\n" - " \"since\": xx, (numeric) height of the first block to which the status applies\n" - " \"statistics\": { (object) numeric statistics about BIP9 signalling for a softfork (only for \"started\" status)\n" - " \"period\": xx, (numeric) the length in blocks of the BIP9 signalling period \n" - " \"threshold\": xx, (numeric) the number of blocks with the version bit set required to activate the feature \n" - " \"elapsed\": xx, (numeric) the number of blocks elapsed since the beginning of the current period \n" - " \"count\": xx, (numeric) the number of blocks with the version bit set in the current period \n" - " \"possible\": xx (boolean) returns false if there are not enough blocks left in this period to pass activation threshold \n" - " }\n" + " \"type\": \"xxxx\", (string) one of \"buried\", \"bip9\"\n" + " \"bip9\": { (object) status of bip9 softforks (only for \"bip9\" type)\n" + " \"status\": \"xxxx\", (string) one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\"\n" + " \"bit\": xx, (numeric) the bit (0-28) in the block version field used to signal this softfork (only for \"started\" status)\n" + " \"startTime\": xx, (numeric) the minimum median time past of a block at which the bit gains its meaning\n" + " \"timeout\": xx, (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in\n" + " \"since\": xx, (numeric) height of the first block to which the status applies\n" + " \"statistics\": { (object) numeric statistics about BIP9 signalling for a softfork\n" + " \"period\": xx, (numeric) the length in blocks of the BIP9 signalling period \n" + " \"threshold\": xx, (numeric) the number of blocks with the version bit set required to activate the feature \n" + " \"elapsed\": xx, (numeric) the number of blocks elapsed since the beginning of the current period \n" + " \"count\": xx, (numeric) the number of blocks with the version bit set in the current period \n" + " \"possible\": xx (boolean) returns false if there are not enough blocks left in this period to pass activation threshold \n" + " }\n" + " },\n" + " \"height\": \"xxxxxx\", (numeric) height of the first block which the rules are or will be enforced (only for \"buried\" type, or \"bip9\" type with \"active\" status)\n" + " \"active\": xx, (boolean) true if the rules are enforced for the mempool and the next block\n" " }\n" " }\n" " \"warnings\" : \"...\", (string) any network and blockchain warnings.\n" @@ -1350,7 +1235,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) HelpExampleCli("getblockchaininfo", "") + HelpExampleRpc("getblockchaininfo", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -1385,16 +1270,14 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) } const Consensus::Params& consensusParams = Params().GetConsensus(); - UniValue softforks(UniValue::VARR); - UniValue bip9_softforks(UniValue::VOBJ); - softforks.push_back(SoftForkDesc("bip34", 2, tip, consensusParams)); - softforks.push_back(SoftForkDesc("bip66", 3, tip, consensusParams)); - softforks.push_back(SoftForkDesc("bip65", 4, tip, consensusParams)); - for (int pos = Consensus::DEPLOYMENT_CSV; pos != Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++pos) { - BIP9SoftForkDescPushBack(bip9_softforks, consensusParams, static_cast<Consensus::DeploymentPos>(pos)); - } + UniValue softforks(UniValue::VOBJ); + BuriedForkDescPushBack(softforks, "bip34", consensusParams.BIP34Height); + BuriedForkDescPushBack(softforks, "bip66", consensusParams.BIP66Height); + BuriedForkDescPushBack(softforks, "bip65", consensusParams.BIP65Height); + BuriedForkDescPushBack(softforks, "csv", consensusParams.CSVHeight); + BuriedForkDescPushBack(softforks, "segwit", consensusParams.SegwitHeight); + BIP9SoftForkDescPushBack(softforks, "testdummy", consensusParams, Consensus::DEPLOYMENT_TESTDUMMY); obj.pushKV("softforks", softforks); - obj.pushKV("bip9_softforks", bip9_softforks); obj.pushKV("warnings", GetWarnings("statusbar")); return obj; @@ -1417,8 +1300,6 @@ struct CompareBlocksByHeight static UniValue getchaintips(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getchaintips", "Return information about all known tips in the block tree," " including the main chain as well as orphaned branches.\n", @@ -1449,14 +1330,14 @@ static UniValue getchaintips(const JSONRPCRequest& request) HelpExampleCli("getchaintips", "") + HelpExampleRpc("getchaintips", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); /* * Idea: the set of chain tips is ::ChainActive().tip, plus orphan blocks which do not have another orphan building off of them. * Algorithm: - * - Make one pass through mapBlockIndex, picking out the orphan blocks, and also storing a set of the orphan block's pprev pointers. + * - Make one pass through g_blockman.m_block_index, picking out the orphan blocks, and also storing a set of the orphan block's pprev pointers. * - Iterate through the orphan blocks. If the block isn't pointed to by another orphan, it is a chain tip. * - add ::ChainActive().Tip() */ @@ -1464,7 +1345,7 @@ static UniValue getchaintips(const JSONRPCRequest& request) std::set<const CBlockIndex*> setOrphans; std::set<const CBlockIndex*> setPrevs; - for (const std::pair<const uint256, CBlockIndex*>& item : mapBlockIndex) + for (const std::pair<const uint256, CBlockIndex*>& item : ::BlockIndex()) { if (!::ChainActive().Contains(item.second)) { setOrphans.insert(item.second); @@ -1540,8 +1421,6 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool) static UniValue getmempoolinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getmempoolinfo", "\nReturns details on the active state of the TX memory pool.\n", {}, @@ -1560,15 +1439,13 @@ static UniValue getmempoolinfo(const JSONRPCRequest& request) HelpExampleCli("getmempoolinfo", "") + HelpExampleRpc("getmempoolinfo", "") }, - }.ToString()); + }.Check(request); return MempoolInfoToJSON(::mempool); } static UniValue preciousblock(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"preciousblock", "\nTreats a block as if it were received before others with the same work.\n" "\nA later preciousblock call can override the effect of an earlier one.\n" @@ -1581,7 +1458,7 @@ static UniValue preciousblock(const JSONRPCRequest& request) HelpExampleCli("preciousblock", "\"blockhash\"") + HelpExampleRpc("preciousblock", "\"blockhash\"") }, - }.ToString()); + }.Check(request); uint256 hash(ParseHashV(request.params[0], "blockhash")); CBlockIndex* pblockindex; @@ -1606,8 +1483,6 @@ static UniValue preciousblock(const JSONRPCRequest& request) static UniValue invalidateblock(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"invalidateblock", "\nPermanently marks a block as invalid, as if it violated a consensus rule.\n", { @@ -1618,7 +1493,7 @@ static UniValue invalidateblock(const JSONRPCRequest& request) HelpExampleCli("invalidateblock", "\"blockhash\"") + HelpExampleRpc("invalidateblock", "\"blockhash\"") }, - }.ToString()); + }.Check(request); uint256 hash(ParseHashV(request.params[0], "blockhash")); CValidationState state; @@ -1646,8 +1521,6 @@ static UniValue invalidateblock(const JSONRPCRequest& request) static UniValue reconsiderblock(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"reconsiderblock", "\nRemoves invalidity status of a block, its ancestors and its descendants, reconsider them for activation.\n" "This can be used to undo the effects of invalidateblock.\n", @@ -1659,7 +1532,7 @@ static UniValue reconsiderblock(const JSONRPCRequest& request) HelpExampleCli("reconsiderblock", "\"blockhash\"") + HelpExampleRpc("reconsiderblock", "\"blockhash\"") }, - }.ToString()); + }.Check(request); uint256 hash(ParseHashV(request.params[0], "blockhash")); @@ -1685,8 +1558,6 @@ static UniValue reconsiderblock(const JSONRPCRequest& request) static UniValue getchaintxstats(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"getchaintxstats", "\nCompute statistics about the total number and rate of transactions in the chain.\n", { @@ -1698,6 +1569,7 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) " \"time\": xxxxx, (numeric) The timestamp for the final block in the window in UNIX format.\n" " \"txcount\": xxxxx, (numeric) The total number of transactions in the chain up to that point.\n" " \"window_final_block_hash\": \"...\", (string) The hash of the final block in the window.\n" + " \"window_final_block_height\": xxxxx, (numeric) The height of the final block in the window.\n" " \"window_block_count\": xxxxx, (numeric) Size of the window in number of blocks.\n" " \"window_tx_count\": xxxxx, (numeric) The number of transactions in the window. Only returned if \"window_block_count\" is > 0.\n" " \"window_interval\": xxxxx, (numeric) The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0.\n" @@ -1708,7 +1580,7 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) HelpExampleCli("getchaintxstats", "") + HelpExampleRpc("getchaintxstats", "2016") }, - }.ToString()); + }.Check(request); const CBlockIndex* pindex; int blockcount = 30 * 24 * 60 * 60 / Params().GetConsensus().nPowTargetSpacing; // By default: 1 month @@ -1748,6 +1620,7 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) ret.pushKV("time", (int64_t)pindex->nTime); ret.pushKV("txcount", (int64_t)pindex->nChainTx); ret.pushKV("window_final_block_hash", pindex->GetBlockHash().GetHex()); + ret.pushKV("window_final_block_height", pindex->nHeight); ret.pushKV("window_block_count", blockcount); if (blockcount > 0) { ret.pushKV("window_tx_count", nTxDiff); @@ -1818,7 +1691,7 @@ static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) static UniValue getblockstats(const JSONRPCRequest& request) { - const RPCHelpMan help{"getblockstats", + RPCHelpMan{"getblockstats", "\nCompute per block statistics for a given window. All amounts are in satoshis.\n" "It won't work for some heights with pruning.\n", { @@ -1873,10 +1746,7 @@ static UniValue getblockstats(const JSONRPCRequest& request) HelpExampleCli("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'") + HelpExampleRpc("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'") }, - }; - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); LOCK(cs_main); @@ -2077,8 +1947,6 @@ static UniValue getblockstats(const JSONRPCRequest& request) static UniValue savemempool(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) { - throw std::runtime_error( RPCHelpMan{"savemempool", "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n", {}, @@ -2087,8 +1955,7 @@ static UniValue savemempool(const JSONRPCRequest& request) HelpExampleCli("savemempool", "") + HelpExampleRpc("savemempool", "") }, - }.ToString()); - } + }.Check(request); if (!::mempool.IsLoaded()) { throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet"); @@ -2163,8 +2030,6 @@ public: UniValue scantxoutset(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"scantxoutset", "\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" @@ -2217,8 +2082,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) "]\n" }, RPCExamples{""}, - }.ToString() - ); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); @@ -2251,41 +2115,12 @@ UniValue scantxoutset(const JSONRPCRequest& request) // loop through the scan objects for (const UniValue& scanobject : request.params[1].get_array().getValues()) { - std::string desc_str; - std::pair<int64_t, int64_t> range = {0, 1000}; - if (scanobject.isStr()) { - desc_str = scanobject.get_str(); - } else if (scanobject.isObject()) { - UniValue desc_uni = find_value(scanobject, "desc"); - if (desc_uni.isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor needs to be provided in scan object"); - desc_str = desc_uni.get_str(); - UniValue range_uni = find_value(scanobject, "range"); - if (!range_uni.isNull()) { - range = ParseDescriptorRange(range_uni); - } - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan object needs to be either a string or an object"); - } - FlatSigningProvider provider; - auto desc = Parse(desc_str, provider); - if (!desc) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor '%s'", desc_str)); - } - if (!desc->IsRange()) { - range.first = 0; - range.second = 0; - } - for (int i = range.first; i <= range.second; ++i) { - std::vector<CScript> scripts; - if (!desc->Expand(i, provider, scripts, provider)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str)); - } - for (const auto& script : scripts) { - std::string inferred = InferDescriptor(script, provider)->ToString(); - needles.emplace(script); - descriptors.emplace(std::move(script), std::move(inferred)); - } + auto scripts = EvalDescriptorStringOrObject(scanobject, provider); + for (const auto& script : scripts) { + std::string inferred = InferDescriptor(script, provider)->ToString(); + needles.emplace(script); + descriptors.emplace(std::move(script), std::move(inferred)); } } @@ -2301,7 +2136,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) { LOCK(cs_main); ::ChainstateActive().ForceFlushStateToDisk(); - pcursor = std::unique_ptr<CCoinsViewCursor>(pcoinsdbview->Cursor()); + pcursor = std::unique_ptr<CCoinsViewCursor>(::ChainstateActive().CoinsDB().Cursor()); assert(pcursor); tip = ::ChainActive().Tip(); assert(tip); @@ -2339,8 +2174,6 @@ UniValue scantxoutset(const JSONRPCRequest& request) static UniValue getblockfilter(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"getblockfilter", "\nRetrieve a BIP 157 content filter for a particular block.\n", { @@ -2356,9 +2189,7 @@ static UniValue getblockfilter(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" \"basic\"") } - }.ToString() - ); - } + }.Check(request); uint256 block_hash = ParseHashV(request.params[0], "blockhash"); std::string filtertype_name = "basic"; diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 8f6bdc21aa..93fca5a6de 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -28,6 +28,7 @@ public: static const CRPCConvertParam vRPCConvertParams[] = { { "setmocktime", 0, "timestamp" }, + { "utxoupdatepsbt", 1, "descriptors" }, { "generatetoaddress", 0, "nblocks" }, { "generatetoaddress", 2, "maxtries" }, { "getnetworkhashps", 0, "nblocks" }, @@ -84,6 +85,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getblockheader", 1, "verbose" }, { "getchaintxstats", 0, "nblocks" }, { "gettransaction", 1, "include_watchonly" }, + { "gettransaction", 2, "decode" }, { "getrawtransaction", 1, "verbose" }, { "createrawtransaction", 0, "inputs" }, { "createrawtransaction", 1, "outputs" }, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 477f05f46c..07c2958635 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -78,8 +78,6 @@ static UniValue GetNetworkHashPS(int lookup, int height) { static UniValue getnetworkhashps(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"getnetworkhashps", "\nReturns the estimated network hashes per second based on the last n blocks.\n" "Pass in [blocks] to override # of blocks, -1 specifies since last difficulty change.\n" @@ -95,7 +93,7 @@ static UniValue getnetworkhashps(const JSONRPCRequest& request) HelpExampleCli("getnetworkhashps", "") + HelpExampleRpc("getnetworkhashps", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].get_int() : 120, !request.params[1].isNull() ? request.params[1].get_int() : -1); @@ -103,7 +101,6 @@ static UniValue getnetworkhashps(const JSONRPCRequest& request) static UniValue generateBlocks(const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries) { - static const int nInnerLoopCount = 0x10000; int nHeightEnd = 0; int nHeight = 0; @@ -124,14 +121,14 @@ static UniValue generateBlocks(const CScript& coinbase_script, int nGenerate, ui LOCK(cs_main); IncrementExtraNonce(pblock, ::ChainActive().Tip(), nExtraNonce); } - while (nMaxTries > 0 && pblock->nNonce < nInnerLoopCount && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) { + while (nMaxTries > 0 && pblock->nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus()) && !ShutdownRequested()) { ++pblock->nNonce; --nMaxTries; } - if (nMaxTries == 0) { + if (nMaxTries == 0 || ShutdownRequested()) { break; } - if (pblock->nNonce == nInnerLoopCount) { + if (pblock->nNonce == std::numeric_limits<uint32_t>::max()) { continue; } std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock); @@ -145,8 +142,6 @@ static UniValue generateBlocks(const CScript& coinbase_script, int nGenerate, ui static UniValue generatetoaddress(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) - throw std::runtime_error( RPCHelpMan{"generatetoaddress", "\nMine blocks immediately to a specified address (before the RPC call returns)\n", { @@ -163,7 +158,7 @@ static UniValue generatetoaddress(const JSONRPCRequest& request) + "If you are running the bitcoin core wallet, you can get a new address to send the newly generated bitcoin to with:\n" + HelpExampleCli("getnewaddress", "") }, - }.ToString()); + }.Check(request); int nGenerate = request.params[0].get_int(); uint64_t nMaxTries = 1000000; @@ -183,8 +178,6 @@ static UniValue generatetoaddress(const JSONRPCRequest& request) static UniValue getmininginfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) { - throw std::runtime_error( RPCHelpMan{"getmininginfo", "\nReturns a json object containing mining-related information.", {}, @@ -204,8 +197,7 @@ static UniValue getmininginfo(const JSONRPCRequest& request) HelpExampleCli("getmininginfo", "") + HelpExampleRpc("getmininginfo", "") }, - }.ToString()); - } + }.Check(request); LOCK(cs_main); @@ -225,8 +217,6 @@ static UniValue getmininginfo(const JSONRPCRequest& request) // NOTE: Unlike wallet RPC (which use BTC values), mining RPCs follow GBT (BIP 22) in using satoshi amounts static UniValue prioritisetransaction(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 3) - throw std::runtime_error( RPCHelpMan{"prioritisetransaction", "Accepts the transaction into mined blocks at a higher (or lower) priority\n", { @@ -245,7 +235,7 @@ static UniValue prioritisetransaction(const JSONRPCRequest& request) HelpExampleCli("prioritisetransaction", "\"txid\" 0.0 10000") + HelpExampleRpc("prioritisetransaction", "\"txid\", 0.0, 10000") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -291,8 +281,6 @@ static std::string gbt_vb_name(const Consensus::DeploymentPos pos) { static UniValue getblocktemplate(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"getblocktemplate", "\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" @@ -302,7 +290,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) " https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki#getblocktemplate_changes\n" " https://github.com/bitcoin/bips/blob/master/bip-0145.mediawiki\n", { - {"template_request", RPCArg::Type::OBJ, RPCArg::Optional::NO, "A json object in the following spec", + {"template_request", RPCArg::Type::OBJ, "{}", "A json object in the following spec", { {"mode", RPCArg::Type::STR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"}, {"capabilities", RPCArg::Type::ARR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "A list of strings", @@ -364,10 +352,10 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) "}\n" }, RPCExamples{ - HelpExampleCli("getblocktemplate", "{\"rules\": [\"segwit\"]}") + HelpExampleCli("getblocktemplate", "'{\"rules\": [\"segwit\"]}'") + HelpExampleRpc("getblocktemplate", "{\"rules\": [\"segwit\"]}") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -469,7 +457,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) nTransactionsUpdatedLastLP = nTransactionsUpdatedLast; } - // Release the wallet and main lock while waiting + // Release lock while waiting LEAVE_CRITICAL_SECTION(cs_main); { checktxtime = std::chrono::steady_clock::now() + std::chrono::minutes(1); @@ -480,6 +468,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) if (g_best_block_cv.wait_until(lock, checktxtime) == std::cv_status::timeout) { // Timeout: Check transactions for update + // without holding ::mempool.cs to avoid deadlocks if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP) break; checktxtime += std::chrono::seconds(10); @@ -493,9 +482,8 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) // TODO: Maybe recheck connections/IBD and (if something wrong) send an expires-immediately template to stop miners? } - const struct VBDeploymentInfo& segwit_info = VersionBitsDeploymentInfo[Consensus::DEPLOYMENT_SEGWIT]; // GBT must be called with 'segwit' set in the rules - if (setClientRules.count(segwit_info.name) != 1) { + if (setClientRules.count("segwit") != 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "getblocktemplate must be called with the segwit rule set (call with {\"rules\": [\"segwit\"]})"); } @@ -532,7 +520,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) pblock->nNonce = 0; // NOTE: If at some point we support pre-segwit miners post-segwit-activation, this needs to take segwit support into consideration - const bool fPreSegWit = (ThresholdState::ACTIVE != VersionBitsState(pindexPrev, consensusParams, Consensus::DEPLOYMENT_SEGWIT, versionbitscache)); + const bool fPreSegWit = (pindexPrev->nHeight + 1 < consensusParams.SegwitHeight); UniValue aCaps(UniValue::VARR); aCaps.push_back("proposal"); @@ -696,8 +684,6 @@ protected: static UniValue submitblock(const JSONRPCRequest& request) { // We allow 2 arguments for compliance with BIP22. Argument 2 is ignored. - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"submitblock", "\nAttempts to submit new block to network.\n" "See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n", @@ -710,8 +696,7 @@ static UniValue submitblock(const JSONRPCRequest& request) HelpExampleCli("submitblock", "\"mydata\"") + HelpExampleRpc("submitblock", "\"mydata\"") }, - }.ToString()); - } + }.Check(request); std::shared_ptr<CBlock> blockptr = std::make_shared<CBlock>(); CBlock& block = *blockptr; @@ -761,8 +746,6 @@ static UniValue submitblock(const JSONRPCRequest& request) static UniValue submitheader(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"submitheader", "\nDecode the given hexdata as a header and submit it as a candidate chain tip if valid." "\nThrows when the header is invalid.\n", @@ -776,8 +759,7 @@ static UniValue submitheader(const JSONRPCRequest& request) HelpExampleCli("submitheader", "\"aabbcc\"") + HelpExampleRpc("submitheader", "\"aabbcc\"") }, - }.ToString()); - } + }.Check(request); CBlockHeader h; if (!DecodeHexBlockHeader(h, request.params[0].get_str())) { @@ -801,8 +783,6 @@ static UniValue submitheader(const JSONRPCRequest& request) static UniValue estimatesmartfee(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"estimatesmartfee", "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" "confirmation within conf_target blocks if possible and return the number of blocks\n" @@ -835,7 +815,7 @@ static UniValue estimatesmartfee(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("estimatesmartfee", "6") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR}); RPCTypeCheckArgument(request.params[0], UniValue::VNUM); @@ -866,8 +846,6 @@ static UniValue estimatesmartfee(const JSONRPCRequest& request) static UniValue estimaterawfee(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"estimaterawfee", "\nWARNING: This interface is unstable and may disappear or change!\n" "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n" @@ -908,7 +886,7 @@ static UniValue estimaterawfee(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("estimaterawfee", "6 0.9") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true); RPCTypeCheckArgument(request.params[0], UniValue::VNUM); diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 7a1bdec7b9..1516007201 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -25,8 +25,6 @@ static UniValue validateaddress(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"validateaddress", "\nReturn information about the given bitcoin address.\n", { @@ -47,7 +45,7 @@ static UniValue validateaddress(const JSONRPCRequest& request) HelpExampleCli("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleRpc("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") }, - }.ToString()); + }.Check(request); CTxDestination dest = DecodeDestination(request.params[0].get_str()); bool isValid = IsValidDestination(dest); @@ -70,9 +68,6 @@ static UniValue validateaddress(const JSONRPCRequest& request) static UniValue createmultisig(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) - { - std::string msg = RPCHelpMan{"createmultisig", "\nCreates a multi-signature address with n signature of m keys required.\n" "It returns a json object with the address and redeemScript.\n", @@ -96,9 +91,7 @@ static UniValue createmultisig(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("createmultisig", "2, \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") }, - }.ToString(); - throw std::runtime_error(msg); - } + }.Check(request); int required = request.params[0].get_int(); @@ -122,7 +115,7 @@ static UniValue createmultisig(const JSONRPCRequest& request) } // Construct using pay-to-script-hash: - CBasicKeyStore keystore; + FillableSigningProvider keystore; CScript inner; const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner); @@ -135,8 +128,6 @@ static UniValue createmultisig(const JSONRPCRequest& request) UniValue getdescriptorinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"getdescriptorinfo", {"\nAnalyses a descriptor.\n"}, { @@ -145,6 +136,7 @@ UniValue getdescriptorinfo(const JSONRPCRequest& request) RPCResult{ "{\n" " \"descriptor\" : \"desc\", (string) The descriptor in canonical form, without private keys\n" + " \"checksum\" : \"chksum\", (string) The checksum for the input descriptor\n" " \"isrange\" : true|false, (boolean) Whether the descriptor is ranged\n" " \"issolvable\" : true|false, (boolean) Whether the descriptor is solvable\n" " \"hasprivatekeys\" : true|false, (boolean) Whether the input descriptor contained at least one private key\n" @@ -153,20 +145,20 @@ UniValue getdescriptorinfo(const JSONRPCRequest& request) RPCExamples{ "Analyse a descriptor\n" + HelpExampleCli("getdescriptorinfo", "\"wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)\"") - }}.ToString() - ); - } + }}.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR}); FlatSigningProvider provider; - auto desc = Parse(request.params[0].get_str(), provider); + std::string error; + auto desc = Parse(request.params[0].get_str(), provider, error); if (!desc) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor")); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } UniValue result(UniValue::VOBJ); result.pushKV("descriptor", desc->ToString()); + result.pushKV("checksum", GetDescriptorChecksum(request.params[0].get_str())); result.pushKV("isrange", desc->IsRange()); result.pushKV("issolvable", desc->IsSolvable()); result.pushKV("hasprivatekeys", provider.keys.size() > 0); @@ -175,8 +167,6 @@ UniValue getdescriptorinfo(const JSONRPCRequest& request) UniValue deriveaddresses(const JSONRPCRequest& request) { - if (request.fHelp || request.params.empty() || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"deriveaddresses", {"\nDerives one or more addresses corresponding to an output descriptor.\n" "Examples of output descriptors are:\n" @@ -197,9 +187,7 @@ UniValue deriveaddresses(const JSONRPCRequest& request) RPCExamples{ "First three native segwit receive addresses\n" + HelpExampleCli("deriveaddresses", "\"wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu\" \"[0,2]\"") - }}.ToString() - ); - } + }}.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later const std::string desc_str = request.params[0].get_str(); @@ -212,9 +200,10 @@ UniValue deriveaddresses(const JSONRPCRequest& request) } FlatSigningProvider key_provider; - auto desc = Parse(desc_str, key_provider, /* require_checksum = */ true); + std::string error; + auto desc = Parse(desc_str, key_provider, error, /* require_checksum = */ true); if (!desc) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor")); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } if (!desc->IsRange() && request.params.size() > 1) { @@ -254,8 +243,6 @@ UniValue deriveaddresses(const JSONRPCRequest& request) static UniValue verifymessage(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 3) - throw std::runtime_error( RPCHelpMan{"verifymessage", "\nVerify a signed message\n", { @@ -276,7 +263,7 @@ static UniValue verifymessage(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -313,8 +300,6 @@ static UniValue verifymessage(const JSONRPCRequest& request) static UniValue signmessagewithprivkey(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 2) - throw std::runtime_error( RPCHelpMan{"signmessagewithprivkey", "\nSign a message with the private key of an address\n", { @@ -332,7 +317,7 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"") }, - }.ToString()); + }.Check(request); std::string strPrivkey = request.params[0].get_str(); std::string strMessage = request.params[1].get_str(); @@ -355,8 +340,6 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request) static UniValue setmocktime(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"setmocktime", "\nSet the local time to given timestamp (-regtest only)\n", { @@ -365,8 +348,7 @@ static UniValue setmocktime(const JSONRPCRequest& request) }, RPCResults{}, RPCExamples{""}, - }.ToString() - ); + }.Check(request); if (!Params().MineBlocksOnDemand()) throw std::runtime_error("setmocktime for regression testing (-regtest mode) only"); @@ -421,8 +403,6 @@ static UniValue getmemoryinfo(const JSONRPCRequest& request) /* Please, avoid using the word "pool" here in the RPC interface or help, * as users will undoubtedly confuse it with the other "memory pool" */ - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"getmemoryinfo", "Returns an object containing information about memory usage.\n", { @@ -451,7 +431,7 @@ static UniValue getmemoryinfo(const JSONRPCRequest& request) HelpExampleCli("getmemoryinfo", "") + HelpExampleRpc("getmemoryinfo", "") }, - }.ToString()); + }.Check(request); std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str(); if (mode == "stats") { @@ -489,8 +469,6 @@ static void EnableOrDisableLogCategories(UniValue cats, bool enable) { UniValue logging(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"logging", "Gets and sets the logging configuration.\n" "When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n" @@ -522,8 +500,7 @@ UniValue logging(const JSONRPCRequest& request) HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"") + HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]") }, - }.ToString()); - } + }.Check(request); uint32_t original_log_categories = LogInstance().GetCategoryMask(); if (request.params[0].isArray()) { diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index d993a88458..25dda924a4 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -9,6 +9,7 @@ #include <core_io.h> #include <net.h> #include <net_processing.h> +#include <net_permissions.h> #include <netbase.h> #include <policy/policy.h> #include <policy/settings.h> @@ -26,8 +27,6 @@ static UniValue getconnectioncount(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getconnectioncount", "\nReturns the number of connections to other nodes.\n", {}, @@ -38,7 +37,7 @@ static UniValue getconnectioncount(const JSONRPCRequest& request) HelpExampleCli("getconnectioncount", "") + HelpExampleRpc("getconnectioncount", "") }, - }.ToString()); + }.Check(request); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -48,8 +47,6 @@ static UniValue getconnectioncount(const JSONRPCRequest& request) static UniValue ping(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"ping", "\nRequests that a ping be sent to all other nodes, to measure ping time.\n" "Results provided in getpeerinfo, pingtime and pingwait fields are decimal seconds.\n" @@ -60,7 +57,7 @@ static UniValue ping(const JSONRPCRequest& request) HelpExampleCli("ping", "") + HelpExampleRpc("ping", "") }, - }.ToString()); + }.Check(request); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -74,8 +71,6 @@ static UniValue ping(const JSONRPCRequest& request) static UniValue getpeerinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getpeerinfo", "\nReturns data about each connected network node as a json array of objects.\n", {}, @@ -131,7 +126,7 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) HelpExampleCli("getpeerinfo", "") + HelpExampleRpc("getpeerinfo", "") }, - }.ToString()); + }.Check(request); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -183,7 +178,12 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) } obj.pushKV("inflight", heights); } - obj.pushKV("whitelisted", stats.fWhitelisted); + obj.pushKV("whitelisted", stats.m_legacyWhitelisted); + UniValue permissions(UniValue::VARR); + for (const auto& permission : NetPermissions::ToStrings(stats.m_permissionFlags)) { + permissions.push_back(permission); + } + obj.pushKV("permissions", permissions); obj.pushKV("minfeefilter", ValueFromAmount(stats.minFeeFilter)); UniValue sendPerMsgCmd(UniValue::VOBJ); @@ -258,8 +258,6 @@ static UniValue addnode(const JSONRPCRequest& request) static UniValue disconnectnode(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() == 0 || request.params.size() >= 3) - throw std::runtime_error( RPCHelpMan{"disconnectnode", "\nImmediately disconnects from the specified peer node.\n" "\nStrictly one out of 'address' and 'nodeid' can be provided to identify the node.\n" @@ -275,7 +273,7 @@ static UniValue disconnectnode(const JSONRPCRequest& request) + HelpExampleRpc("disconnectnode", "\"192.168.0.6:8333\"") + HelpExampleRpc("disconnectnode", "\"\", 1") }, - }.ToString()); + }.Check(request); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -304,8 +302,6 @@ static UniValue disconnectnode(const JSONRPCRequest& request) static UniValue getaddednodeinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"getaddednodeinfo", "\nReturns information about the given added node, or all added nodes\n" "(note that onetry addnodes are not listed here)\n", @@ -331,7 +327,7 @@ static UniValue getaddednodeinfo(const JSONRPCRequest& request) HelpExampleCli("getaddednodeinfo", "\"192.168.0.201\"") + HelpExampleRpc("getaddednodeinfo", "\"192.168.0.201\"") }, - }.ToString()); + }.Check(request); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -374,8 +370,6 @@ static UniValue getaddednodeinfo(const JSONRPCRequest& request) static UniValue getnettotals(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 0) - throw std::runtime_error( RPCHelpMan{"getnettotals", "\nReturns information about network traffic, including bytes in, bytes out,\n" "and current time.\n", @@ -400,7 +394,7 @@ static UniValue getnettotals(const JSONRPCRequest& request) HelpExampleCli("getnettotals", "") + HelpExampleRpc("getnettotals", "") }, - }.ToString()); + }.Check(request); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -443,8 +437,6 @@ static UniValue GetNetworksInfo() static UniValue getnetworkinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getnetworkinfo", "Returns an object containing various state info regarding P2P networking.\n", {}, @@ -485,7 +477,7 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request) HelpExampleCli("getnetworkinfo", "") + HelpExampleRpc("getnetworkinfo", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); UniValue obj(UniValue::VOBJ); @@ -602,8 +594,6 @@ static UniValue setban(const JSONRPCRequest& request) static UniValue listbanned(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"listbanned", "\nList all banned IPs/Subnets.\n", {}, @@ -612,7 +602,7 @@ static UniValue listbanned(const JSONRPCRequest& request) HelpExampleCli("listbanned", "") + HelpExampleRpc("listbanned", "") }, - }.ToString()); + }.Check(request); if(!g_banman) { throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); @@ -639,8 +629,6 @@ static UniValue listbanned(const JSONRPCRequest& request) static UniValue clearbanned(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"clearbanned", "\nClear all banned IPs.\n", {}, @@ -649,7 +637,7 @@ static UniValue clearbanned(const JSONRPCRequest& request) HelpExampleCli("clearbanned", "") + HelpExampleRpc("clearbanned", "") }, - }.ToString()); + }.Check(request); if (!g_banman) { throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); } @@ -661,8 +649,6 @@ static UniValue clearbanned(const JSONRPCRequest& request) static UniValue setnetworkactive(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"setnetworkactive", "\nDisable/enable all p2p network activity.\n", { @@ -670,9 +656,7 @@ static UniValue setnetworkactive(const JSONRPCRequest& request) }, RPCResults{}, RPCExamples{""}, - }.ToString() - ); - } + }.Check(request); if (!g_connman) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -685,8 +669,6 @@ static UniValue setnetworkactive(const JSONRPCRequest& request) static UniValue getnodeaddresses(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 1) { - throw std::runtime_error( RPCHelpMan{"getnodeaddresses", "\nReturn known addresses which can potentially be used to find new nodes in the network\n", { @@ -707,8 +689,7 @@ static UniValue getnodeaddresses(const JSONRPCRequest& request) HelpExampleCli("getnodeaddresses", "8") + HelpExampleRpc("getnodeaddresses", "8") }, - }.ToString()); - } + }.Check(request); if (!g_connman) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index 6bcbccbd4f..ef6537e4ec 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -6,15 +6,6 @@ #ifndef BITCOIN_RPC_PROTOCOL_H #define BITCOIN_RPC_PROTOCOL_H -#include <fs.h> - -#include <list> -#include <map> -#include <stdint.h> -#include <string> - -#include <univalue.h> - //! HTTP status codes enum HTTPStatusCode { @@ -92,18 +83,4 @@ enum RPCErrorCode RPC_FORBIDDEN_BY_SAFE_MODE = -2, //!< Server is in safe mode, and command is not allowed in safe mode }; -UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); -UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id); -std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id); -UniValue JSONRPCError(int code, const std::string& message); - -/** Generate a new RPC authentication cookie and write it to disk */ -bool GenerateAuthCookie(std::string *cookie_out); -/** Read the RPC authentication cookie from disk */ -bool GetAuthCookie(std::string *cookie_out); -/** Delete RPC authentication cookie from disk */ -void DeleteAuthCookie(); -/** Parse JSON-RPC batch reply into a vector */ -std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num); - #endif // BITCOIN_RPC_PROTOCOL_H diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 9da24afe79..fb8ea8c227 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -10,7 +10,6 @@ #include <core_io.h> #include <index/txindex.h> #include <key_io.h> -#include <keystore.h> #include <merkleblock.h> #include <node/coin.h> #include <node/psbt.h> @@ -24,6 +23,7 @@ #include <script/script.h> #include <script/script_error.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <script/standard.h> #include <uint256.h> #include <util/moneystr.h> @@ -71,7 +71,7 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& static UniValue getrawtransaction(const JSONRPCRequest& request) { - const RPCHelpMan help{ + RPCHelpMan{ "getrawtransaction", "\nReturn the raw transaction data.\n" @@ -149,11 +149,7 @@ static UniValue getrawtransaction(const JSONRPCRequest& request) + HelpExampleCli("getrawtransaction", "\"mytxid\" false \"myblockhash\"") + HelpExampleCli("getrawtransaction", "\"mytxid\" true \"myblockhash\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); bool in_active_chain = true; uint256 hash = ParseHashV(request.params[0], "parameter 1"); @@ -217,8 +213,6 @@ static UniValue getrawtransaction(const JSONRPCRequest& request) static UniValue gettxoutproof(const JSONRPCRequest& request) { - if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2)) - throw std::runtime_error( RPCHelpMan{"gettxoutproof", "\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" @@ -237,8 +231,7 @@ static UniValue gettxoutproof(const JSONRPCRequest& request) "\"data\" (string) A string that is a serialized, hex-encoded data for the proof.\n" }, RPCExamples{""}, - }.ToString() - ); + }.Check(request); std::set<uint256> setTxids; uint256 oneTxid; @@ -266,7 +259,7 @@ static UniValue gettxoutproof(const JSONRPCRequest& request) // Loop through txids and try to find which block they're in. Exit loop once a block is found. for (const auto& tx : setTxids) { - const Coin& coin = AccessByTxid(*pcoinsTip, tx); + const Coin& coin = AccessByTxid(::ChainstateActive().CoinsTip(), tx); if (!coin.IsSpent()) { pblockindex = ::ChainActive()[coin.nHeight]; break; @@ -313,8 +306,6 @@ static UniValue gettxoutproof(const JSONRPCRequest& request) static UniValue verifytxoutproof(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"verifytxoutproof", "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n" "and throwing an RPC error if the block is not in our best chain\n", @@ -325,8 +316,7 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request) "[\"txid\"] (array, strings) The txid(s) which the proof commits to, or empty array if the proof can not be validated.\n" }, RPCExamples{""}, - }.ToString() - ); + }.Check(request); CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); CMerkleBlock merkleBlock; @@ -358,8 +348,6 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request) static UniValue createrawtransaction(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { - throw std::runtime_error( RPCHelpMan{"createrawtransaction", "\nCreate a transaction spending the given inputs and creating new outputs.\n" "Outputs can be addresses or data.\n" @@ -408,8 +396,7 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"") }, - }.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, { UniValue::VARR, @@ -419,14 +406,18 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) }, true ); - CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + bool rbf = false; + if (!request.params[3].isNull()) { + rbf = request.params[3].isTrue(); + } + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf); return EncodeHexTx(CTransaction(rawTx)); } static UniValue decoderawtransaction(const JSONRPCRequest& request) { - const RPCHelpMan help{"decoderawtransaction", + RPCHelpMan{"decoderawtransaction", "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"}, @@ -483,11 +474,7 @@ static UniValue decoderawtransaction(const JSONRPCRequest& request) HelpExampleCli("decoderawtransaction", "\"hexstring\"") + HelpExampleRpc("decoderawtransaction", "\"hexstring\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}); @@ -518,7 +505,7 @@ static std::string GetAllOutputTypes() static UniValue decodescript(const JSONRPCRequest& request) { - const RPCHelpMan help{"decodescript", + RPCHelpMan{"decodescript", "\nDecode a hex-encoded script.\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded script"}, @@ -549,11 +536,7 @@ static UniValue decodescript(const JSONRPCRequest& request) HelpExampleCli("decodescript", "\"hexstring\"") + HelpExampleRpc("decodescript", "\"hexstring\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR}); @@ -610,8 +593,6 @@ static UniValue decodescript(const JSONRPCRequest& request) static UniValue combinerawtransaction(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"combinerawtransaction", "\nCombine multiple partially signed transactions into one transaction.\n" "The combined transaction may be another partially signed transaction or a \n" @@ -629,7 +610,7 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("combinerawtransaction", "[\"myhex1\", \"myhex2\", \"myhex3\"]") }, - }.ToString()); + }.Check(request); UniValue txs = request.params[0].get_array(); @@ -655,7 +636,7 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request) { LOCK(cs_main); LOCK(mempool.cs); - CCoinsViewCache &viewChain = *pcoinsTip; + CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip(); CCoinsViewMemPool viewMempool(&viewChain, mempool); view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view @@ -694,8 +675,6 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request) static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"signrawtransactionwithkey", "\nSign inputs for raw transaction (serialized, hex-encoded).\n" "The second argument is an array of base58-encoded private\n" @@ -752,7 +731,7 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) HelpExampleCli("signrawtransactionwithkey", "\"myhex\" \"[\\\"key1\\\",\\\"key2\\\"]\"") + HelpExampleRpc("signrawtransactionwithkey", "\"myhex\", \"[\\\"key1\\\",\\\"key2\\\"]\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); @@ -761,7 +740,7 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); } - CBasicKeyStore keystore; + FillableSigningProvider keystore; const UniValue& keys = request.params[1].get_array(); for (unsigned int idx = 0; idx < keys.size(); ++idx) { UniValue k = keys[idx]; @@ -779,13 +758,19 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) } FindCoins(coins); - return SignTransaction(mtx, request.params[2], &keystore, coins, true, request.params[3]); + // Parse the prevtxs array + ParsePrevouts(request.params[2], &keystore, coins); + + return SignTransaction(mtx, &keystore, coins, request.params[3]); } static UniValue sendrawtransaction(const JSONRPCRequest& request) { - const RPCHelpMan help{"sendrawtransaction", - "\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n" + RPCHelpMan{"sendrawtransaction", + "\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n" + "\nNote that the transaction will be sent unconditionally to all peers, so using this\n" + "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n" + "nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n" "\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"}, @@ -806,11 +791,7 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("sendrawtransaction", "\"signedhex\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, { UniValue::VSTR, @@ -836,19 +817,19 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) max_raw_tx_fee = fr.GetFee((weight+3)/4); } - uint256 txid; std::string err_string; - const TransactionError err = BroadcastTransaction(tx, txid, err_string, max_raw_tx_fee); + AssertLockNotHeld(cs_main); + const TransactionError err = BroadcastTransaction(tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true); if (TransactionError::OK != err) { throw JSONRPCTransactionError(err, err_string); } - return txid.GetHex(); + return tx->GetHash().GetHex(); } static UniValue testmempoolaccept(const JSONRPCRequest& request) { - const RPCHelpMan help{"testmempoolaccept", + RPCHelpMan{"testmempoolaccept", "\nReturns result of mempool acceptance tests indicating if raw transaction (serialized, hex-encoded) would be accepted by mempool.\n" "\nThis checks if the transaction violates the consensus or policy rules.\n" "\nSee sendrawtransaction call.\n", @@ -881,11 +862,7 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, { UniValue::VARR, @@ -964,8 +941,6 @@ static std::string WriteHDKeypath(std::vector<uint32_t>& keypath) UniValue decodepsbt(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"decodepsbt", "\nReturn a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.\n", { @@ -1062,7 +1037,7 @@ UniValue decodepsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("decodepsbt", "\"psbt\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR}); @@ -1239,8 +1214,6 @@ UniValue decodepsbt(const JSONRPCRequest& request) UniValue combinepsbt(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"combinepsbt", "\nCombine multiple partially signed Bitcoin transactions into one transaction.\n" "Implements the Combiner role.\n", @@ -1257,7 +1230,7 @@ UniValue combinepsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("combinepsbt", "[\"mybase64_1\", \"mybase64_2\", \"mybase64_3\"]") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VARR}, true); @@ -1289,8 +1262,6 @@ UniValue combinepsbt(const JSONRPCRequest& request) UniValue finalizepsbt(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"finalizepsbt", "Finalize the inputs of a PSBT. If the transaction is fully signed, it will produce a\n" "network serialized transaction which can be broadcast with sendrawtransaction. Otherwise a PSBT will be\n" @@ -1312,7 +1283,7 @@ UniValue finalizepsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("finalizepsbt", "\"psbt\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); @@ -1348,8 +1319,6 @@ UniValue finalizepsbt(const JSONRPCRequest& request) UniValue createpsbt(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"createpsbt", "\nCreates a transaction in the Partially Signed Transaction format.\n" "Implements the Creator role.\n", @@ -1392,7 +1361,7 @@ UniValue createpsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, { @@ -1403,7 +1372,11 @@ UniValue createpsbt(const JSONRPCRequest& request) }, true ); - CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + bool rbf = false; + if (!request.params[3].isNull()) { + rbf = request.params[3].isTrue(); + } + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf); // Make a blank psbt PartiallySignedTransaction psbtx; @@ -1424,7 +1397,7 @@ UniValue createpsbt(const JSONRPCRequest& request) UniValue converttopsbt(const JSONRPCRequest& request) { - const RPCHelpMan help{"converttopsbt", + RPCHelpMan{"converttopsbt", "\nConverts a network serialized transaction to a PSBT. This should be used only with createrawtransaction and fundrawtransaction\n" "createpsbt and walletcreatefundedpsbt should be used for new applications.\n", { @@ -1448,11 +1421,7 @@ UniValue converttopsbt(const JSONRPCRequest& request) "\nConvert the transaction to a PSBT\n" + HelpExampleCli("converttopsbt", "\"rawtransaction\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true); @@ -1495,22 +1464,26 @@ UniValue converttopsbt(const JSONRPCRequest& request) UniValue utxoupdatepsbt(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"utxoupdatepsbt", - "\nUpdates a PSBT with witness UTXOs retrieved from the UTXO set or the mempool.\n", + "\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set or the mempool.\n", { - {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"} + {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"}, + {"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "An array of either strings or objects", { + {"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"}, + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with an output descriptor and extra information", { + {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"}, + {"range", RPCArg::Type::RANGE, "1000", "Up to what index HD chains should be explored (either end or [begin,end])"}, + }}, + }}, }, RPCResult { " \"psbt\" (string) The base64-encoded partially signed transaction with inputs updated\n" }, RPCExamples { HelpExampleCli("utxoupdatepsbt", "\"psbt\"") - }}.ToString()); - } + }}.Check(request); - RPCTypeCheck(request.params, {UniValue::VSTR}, true); + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true); // Unserialize the transactions PartiallySignedTransaction psbtx; @@ -1519,12 +1492,23 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); } + // Parse descriptors, if any. + FlatSigningProvider provider; + if (!request.params[1].isNull()) { + auto descs = request.params[1].get_array(); + for (size_t i = 0; i < descs.size(); ++i) { + EvalDescriptorStringOrObject(descs[i], provider); + } + } + // We don't actually need private keys further on; hide them as a precaution. + HidingSigningProvider public_provider(&provider, /* nosign */ true, /* nobip32derivs */ false); + // Fetch previous transactions (inputs): CCoinsView viewDummy; CCoinsViewCache view(&viewDummy); { LOCK2(cs_main, mempool.cs); - CCoinsViewCache &viewChain = *pcoinsTip; + CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip(); CCoinsViewMemPool viewMempool(&viewChain, mempool); view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view @@ -1545,11 +1529,19 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request) const Coin& coin = view.AccessCoin(psbtx.tx->vin[i].prevout); - std::vector<std::vector<unsigned char>> solutions_data; - txnouttype which_type = Solver(coin.out.scriptPubKey, solutions_data); - if (which_type == TX_WITNESS_V0_SCRIPTHASH || which_type == TX_WITNESS_V0_KEYHASH || which_type == TX_WITNESS_UNKNOWN) { + if (IsSegWitOutput(provider, coin.out.scriptPubKey)) { input.witness_utxo = coin.out; } + + // Update script/keypath information using descriptor data. + // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures + // we don't actually care about those here, in fact. + SignPSBTInput(public_provider, psbtx, i, /* sighash_type */ 1); + } + + // Update script/keypath information using descriptor data. + for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { + UpdatePSBTOutput(public_provider, psbtx, i); } CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); @@ -1559,8 +1551,6 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request) UniValue joinpsbts(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"joinpsbts", "\nJoins multiple distinct PSBTs with different inputs and outputs into one PSBT with inputs and outputs from all of the PSBTs\n" "No input in any of the PSBTs can be in more than one of the PSBTs.\n", @@ -1575,8 +1565,7 @@ UniValue joinpsbts(const JSONRPCRequest& request) }, RPCExamples { HelpExampleCli("joinpsbts", "\"psbt\"") - }}.ToString()); - } + }}.Check(request); RPCTypeCheck(request.params, {UniValue::VARR}, true); @@ -1633,8 +1622,6 @@ UniValue joinpsbts(const JSONRPCRequest& request) UniValue analyzepsbt(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"analyzepsbt", "\nAnalyzes and provides information about the current status of a PSBT and its inputs\n", { @@ -1668,8 +1655,7 @@ UniValue analyzepsbt(const JSONRPCRequest& request) }, RPCExamples { HelpExampleCli("analyzepsbt", "\"psbt\"") - }}.ToString()); - } + }}.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR}); @@ -1750,7 +1736,7 @@ static const CRPCCommand commands[] = { "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} }, { "rawtransactions", "createpsbt", &createpsbt, {"inputs","outputs","locktime","replaceable"} }, { "rawtransactions", "converttopsbt", &converttopsbt, {"hexstring","permitsigdata","iswitness"} }, - { "rawtransactions", "utxoupdatepsbt", &utxoupdatepsbt, {"psbt"} }, + { "rawtransactions", "utxoupdatepsbt", &utxoupdatepsbt, {"psbt", "descriptors"} }, { "rawtransactions", "joinpsbts", &joinpsbts, {"txs"} }, { "rawtransactions", "analyzepsbt", &analyzepsbt, {"psbt"} }, diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 9c4cdc3a90..697c6d45c4 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -8,17 +8,18 @@ #include <coins.h> #include <core_io.h> #include <key_io.h> -#include <keystore.h> #include <policy/policy.h> #include <primitives/transaction.h> -#include <rpc/protocol.h> +#include <rpc/request.h> #include <rpc/util.h> +#include <script/sign.h> +#include <script/signingprovider.h> #include <tinyformat.h> #include <univalue.h> #include <util/rbf.h> #include <util/strencodings.h> -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf) +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf) { if (inputs_in.isNull() || outputs_in.isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null"); @@ -36,8 +37,6 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal rawTx.nLockTime = nLockTime; } - bool rbfOptIn = rbf.isTrue(); - for (unsigned int idx = 0; idx < inputs.size(); idx++) { const UniValue& input = inputs[idx]; const UniValue& o = input.get_obj(); @@ -52,7 +51,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive"); uint32_t nSequence; - if (rbfOptIn) { + if (rbf) { nSequence = MAX_BIP125_RBF_SEQUENCE; /* CTxIn::SEQUENCE_FINAL - 2 */ } else if (rawTx.nLockTime) { nSequence = CTxIn::SEQUENCE_FINAL - 1; @@ -124,7 +123,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal } } - if (!rbf.isNull() && rawTx.vin.size() > 0 && rbfOptIn != SignalsOptInRBF(CTransaction(rawTx))) { + if (rbf && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); } @@ -148,9 +147,8 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std:: vErrorsRet.push_back(entry); } -UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore* keystore, std::map<COutPoint, Coin>& coins, bool is_temp_keystore, const UniValue& hashType) +void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins) { - // Add previous txouts given in the RPC call: if (!prevTxsUnival.isNull()) { UniValue prevTxs = prevTxsUnival.get_array(); for (unsigned int idx = 0; idx < prevTxs.size(); ++idx) { @@ -198,7 +196,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival } // if redeemScript and private keys were given, add redeemScript to the keystore so it can be signed - if (is_temp_keystore && (scriptPubKey.IsPayToScriptHash() || scriptPubKey.IsPayToWitnessScriptHash())) { + if (keystore && (scriptPubKey.IsPayToScriptHash() || scriptPubKey.IsPayToWitnessScriptHash())) { RPCTypeCheckObj(prevOut, { {"redeemScript", UniValueType(UniValue::VSTR)}, @@ -221,10 +219,16 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival // Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH). keystore->AddCScript(GetScriptForWitness(witnessScript)); } + if (rs.isNull() && ws.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing redeemScript/witnessScript"); + } } } } +} +UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, std::map<COutPoint, Coin>& coins, const UniValue& hashType) +{ int nHashType = ParseSighashString(hashType); bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index c115d33a77..b35e6da4ca 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -7,26 +7,34 @@ #include <map> -class CBasicKeyStore; +class FillableSigningProvider; class UniValue; struct CMutableTransaction; class Coin; class COutPoint; +class SigningProvider; /** * Sign a transaction with the given keystore and previous transactions * * @param mtx The transaction to-be-signed - * @param prevTxs Array of previous txns outputs that tx depends on but may not yet be in the block chain * @param keystore Temporary keystore containing signing keys * @param coins Map of unspent outputs - coins in mempool and current chain UTXO set, may be extended by previous txns outputs after call - * @param tempKeystore Whether to use temporary keystore * @param hashType The signature hash type * @returns JSON object with details of signed transaction */ -UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore* keystore, std::map<COutPoint, Coin>& coins, bool tempKeystore, const UniValue& hashType); +UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, std::map<COutPoint, Coin>& coins, const UniValue& hashType); + +/** + * Parse a prevtxs UniValue array and get the map of coins from it + * + * @param prevTxs Array of previous txns outputs that tx depends on but may not yet be in the block chain + * @param keystore A pointer to the temprorary keystore if there is one + * @param coins Map of unspent outputs - coins in mempool and current chain UTXO set, may be extended by previous txns outputs after call + */ +void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins); /** Create a transaction from univalue parameters */ -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf); +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf); #endif // BITCOIN_RPC_RAWTRANSACTION_UTIL_H diff --git a/src/rpc/protocol.cpp b/src/rpc/request.cpp index 33b0130a94..56cac6661e 100644 --- a/src/rpc/protocol.cpp +++ b/src/rpc/request.cpp @@ -1,15 +1,16 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 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 <rpc/protocol.h> +#include <rpc/request.h> + +#include <fs.h> #include <random.h> -#include <tinyformat.h> +#include <rpc/protocol.h> #include <util/system.h> #include <util/strencodings.h> -#include <util/time.h> /** * JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility, @@ -148,3 +149,36 @@ std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num) } return batch; } + +void JSONRPCRequest::parse(const UniValue& valRequest) +{ + // Parse request + if (!valRequest.isObject()) + throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object"); + const UniValue& request = valRequest.get_obj(); + + // Parse id now so errors from here on will have the id + id = find_value(request, "id"); + + // Parse method + UniValue valMethod = find_value(request, "method"); + if (valMethod.isNull()) + throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method"); + if (!valMethod.isStr()) + throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string"); + strMethod = valMethod.get_str(); + if (fLogIPs) + LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s peeraddr=%s\n", SanitizeString(strMethod), + this->authUser, this->peerAddr); + else + LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s\n", SanitizeString(strMethod), this->authUser); + + // Parse params + UniValue valParams = find_value(request, "params"); + if (valParams.isArray() || valParams.isObject()) + params = valParams; + else if (valParams.isNull()) + params = UniValue(UniValue::VARR); + else + throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object"); +} diff --git a/src/rpc/request.h b/src/rpc/request.h new file mode 100644 index 0000000000..99eb4f9354 --- /dev/null +++ b/src/rpc/request.h @@ -0,0 +1,42 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2019 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_RPC_REQUEST_H +#define BITCOIN_RPC_REQUEST_H + +#include <string> + +#include <univalue.h> + +UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); +UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id); +std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id); +UniValue JSONRPCError(int code, const std::string& message); + +/** Generate a new RPC authentication cookie and write it to disk */ +bool GenerateAuthCookie(std::string *cookie_out); +/** Read the RPC authentication cookie from disk */ +bool GetAuthCookie(std::string *cookie_out); +/** Delete RPC authentication cookie from disk */ +void DeleteAuthCookie(); +/** Parse JSON-RPC batch reply into a vector */ +std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num); + +class JSONRPCRequest +{ +public: + UniValue id; + std::string strMethod; + UniValue params; + bool fHelp; + std::string URI; + std::string authUser; + std::string peerAddr; + + JSONRPCRequest() : id(NullUniValue), params(NullUniValue), fHelp(false) {} + void parse(const UniValue& valRequest); +}; + +#endif // BITCOIN_RPC_REQUEST_H diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index ca17d379bc..18f7426bcf 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -178,8 +178,6 @@ UniValue stop(const JSONRPCRequest& jsonRequest) static UniValue uptime(const JSONRPCRequest& jsonRequest) { - if (jsonRequest.fHelp || jsonRequest.params.size() > 0) - throw std::runtime_error( RPCHelpMan{"uptime", "\nReturns the total uptime of the server.\n", {}, @@ -190,15 +188,13 @@ static UniValue uptime(const JSONRPCRequest& jsonRequest) HelpExampleCli("uptime", "") + HelpExampleRpc("uptime", "") }, - }.ToString()); + }.Check(jsonRequest); return GetTime() - GetStartupTime(); } static UniValue getrpcinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 0) { - throw std::runtime_error( RPCHelpMan{"getrpcinfo", "\nReturns details of the RPC server.\n", {}, @@ -210,15 +206,14 @@ static UniValue getrpcinfo(const JSONRPCRequest& request) " \"method\" (string) The name of the RPC command \n" " \"duration\" (numeric) The running time in microseconds\n" " },...\n" - " ]\n" + " ],\n" + " \"logpath\": \"xxx\" (string) The complete file path to the debug log\n" "}\n" }, RPCExamples{ HelpExampleCli("getrpcinfo", "") + HelpExampleRpc("getrpcinfo", "")}, - }.ToString() - ); - } + }.Check(request); LOCK(g_rpc_server_info.mutex); UniValue active_commands(UniValue::VARR); @@ -232,6 +227,10 @@ static UniValue getrpcinfo(const JSONRPCRequest& request) UniValue result(UniValue::VOBJ); result.pushKV("active_commands", active_commands); + const std::string path = LogInstance().m_file_path.string(); + UniValue log_path(UniValue::VSTR, path); + result.pushKV("logpath", log_path); + return result; } @@ -329,39 +328,6 @@ bool RPCIsInWarmup(std::string *outStatus) return fRPCInWarmup; } -void JSONRPCRequest::parse(const UniValue& valRequest) -{ - // Parse request - if (!valRequest.isObject()) - throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object"); - const UniValue& request = valRequest.get_obj(); - - // Parse id now so errors from here on will have the id - id = find_value(request, "id"); - - // Parse method - UniValue valMethod = find_value(request, "method"); - if (valMethod.isNull()) - throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method"); - if (!valMethod.isStr()) - throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string"); - strMethod = valMethod.get_str(); - if (fLogIPs) - LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s peeraddr=%s\n", SanitizeString(strMethod), - this->authUser, this->peerAddr); - else - LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s\n", SanitizeString(strMethod), this->authUser); - - // Parse params - UniValue valParams = find_value(request, "params"); - if (valParams.isArray() || valParams.isObject()) - params = valParams; - else if (valParams.isNull()) - params = UniValue(UniValue::VARR); - else - throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object"); -} - bool IsDeprecatedRPCEnabled(const std::string& method) { const std::vector<std::string> enabled_methods = gArgs.GetArgs("-deprecatedrpc"); diff --git a/src/rpc/server.h b/src/rpc/server.h index 431ff0bb7c..b060db5bf9 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -7,13 +7,14 @@ #define BITCOIN_RPC_SERVER_H #include <amount.h> -#include <rpc/protocol.h> +#include <rpc/request.h> #include <uint256.h> #include <list> #include <map> #include <stdint.h> #include <string> +#include <functional> #include <univalue.h> @@ -27,21 +28,6 @@ namespace RPCServer void OnStopped(std::function<void ()> slot); } -class JSONRPCRequest -{ -public: - UniValue id; - std::string strMethod; - UniValue params; - bool fHelp; - std::string URI; - std::string authUser; - std::string peerAddr; - - JSONRPCRequest() : id(NullUniValue), params(NullUniValue), fHelp(false) {} - void parse(const UniValue& valRequest); -}; - /** Query whether RPC is running */ bool IsRPCRunning(); diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 4642cf16b1..22d67c34da 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -3,11 +3,13 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <key_io.h> -#include <keystore.h> #include <outputtype.h> #include <rpc/util.h> +#include <script/descriptor.h> +#include <script/signingprovider.h> #include <tinyformat.h> #include <util/strencodings.h> +#include <util/string.h> #include <tuple> @@ -130,8 +132,8 @@ CPubKey HexToPubKey(const std::string& hex_in) return vchPubKey; } -// Retrieves a public key for an address from the given CKeyStore -CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in) +// Retrieves a public key for an address from the given FillableSigningProvider +CPubKey AddrToPubKey(FillableSigningProvider* const keystore, const std::string& addr_in) { CTxDestination dest = DecodeDestination(addr_in); if (!IsValidDestination(dest)) { @@ -152,7 +154,7 @@ CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in) } // Creates a multisig address from a given list of public keys, number of signatures required, and the address type -CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, CKeyStore& keystore, CScript& script_out) +CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FillableSigningProvider& keystore, CScript& script_out) { // Gather public keys if (required < 1) { @@ -644,11 +646,7 @@ std::string RPCArg::ToString(const bool oneline) const } 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(oneline); - if (++i < m_inner.size()) res += ","; - } + const std::string res = Join(m_inner, ",", [&](const RPCArg& i) { return i.ToStringObj(oneline); }); if (m_type == Type::OBJ) { return "{" + res + "}"; } else { @@ -697,3 +695,41 @@ std::pair<int64_t, int64_t> ParseDescriptorRange(const UniValue& value) } return {low, high}; } + +std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider) +{ + std::string desc_str; + std::pair<int64_t, int64_t> range = {0, 1000}; + if (scanobject.isStr()) { + desc_str = scanobject.get_str(); + } else if (scanobject.isObject()) { + UniValue desc_uni = find_value(scanobject, "desc"); + if (desc_uni.isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor needs to be provided in scan object"); + desc_str = desc_uni.get_str(); + UniValue range_uni = find_value(scanobject, "range"); + if (!range_uni.isNull()) { + range = ParseDescriptorRange(range_uni); + } + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan object needs to be either a string or an object"); + } + + std::string error; + auto desc = Parse(desc_str, provider, error); + if (!desc) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); + } + if (!desc->IsRange()) { + range.first = 0; + range.second = 0; + } + std::vector<CScript> ret; + for (int i = range.first; i <= range.second; ++i) { + std::vector<CScript> scripts; + if (!desc->Expand(i, provider, scripts, provider)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str)); + } + std::move(scripts.begin(), scripts.end(), std::back_inserter(ret)); + } + return ret; +} diff --git a/src/rpc/util.h b/src/rpc/util.h index 0eb2fef5c3..4c3322b879 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -9,6 +9,9 @@ #include <outputtype.h> #include <pubkey.h> #include <rpc/protocol.h> +#include <rpc/request.h> +#include <script/script.h> +#include <script/sign.h> #include <script/standard.h> #include <univalue.h> @@ -17,7 +20,7 @@ #include <boost/variant.hpp> -class CKeyStore; +class FillableSigningProvider; class CPubKey; class CScript; struct InitInterfaces; @@ -70,8 +73,8 @@ extern std::string HelpExampleCli(const std::string& methodname, const std::stri extern std::string HelpExampleRpc(const std::string& methodname, const std::string& args); CPubKey HexToPubKey(const std::string& hex_in); -CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in); -CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, CKeyStore& keystore, CScript& script_out); +CPubKey AddrToPubKey(FillableSigningProvider* const keystore, const std::string& addr_in); +CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FillableSigningProvider& keystore, CScript& script_out); UniValue DescribeAddress(const CTxDestination& dest); @@ -84,6 +87,9 @@ UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_s //! Parse a JSON range specified as int64, or [int64, int64] std::pair<int64_t, int64_t> ParseDescriptorRange(const UniValue& value); +/** Evaluate a descriptor given as a string, or as a {"desc":...,"range":...} object, with default range of 1000. */ +std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider); + struct RPCArg { enum class Type { OBJ, @@ -221,7 +227,7 @@ struct RPCResults { struct RPCExamples { const std::string m_examples; - RPCExamples( + explicit RPCExamples( std::string examples) : m_examples(std::move(examples)) { @@ -237,6 +243,15 @@ public: std::string ToString() const; /** If the supplied number of args is neither too small nor too high */ bool IsValidNumArgs(size_t num_args) const; + /** + * Check if the given request is valid according to this command or if + * the user is asking for help information, and throw help when appropriate. + */ + inline void Check(const JSONRPCRequest& request) const { + if (request.fHelp || !IsValidNumArgs(request.params.size())) { + throw std::runtime_error(ToString()); + } + } private: const std::string m_name; diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 50119ba184..b782ebbd1f 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -335,10 +335,12 @@ public: /** Base class for all Descriptor implementations. */ class DescriptorImpl : public Descriptor { - //! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size of Multisig). + //! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size for Multisig). const std::vector<std::unique_ptr<PubkeyProvider>> m_pubkey_args; //! The sub-descriptor argument (nullptr for everything but SH and WSH). - const std::unique_ptr<DescriptorImpl> m_script_arg; + //! In doc/descriptors.m this is referred to as SCRIPT expressions sh(SCRIPT) + //! and wsh(SCRIPT), and distinct from KEY expressions and ADDR expressions. + const std::unique_ptr<DescriptorImpl> m_subdescriptor_arg; //! The string name of the descriptor function. const std::string m_name; @@ -349,10 +351,10 @@ protected: /** A helper function to construct the scripts for this descriptor. * * This function is invoked once for every CScript produced by evaluating - * m_script_arg, or just once in case m_script_arg is nullptr. + * m_subdescriptor_arg, or just once in case m_subdescriptor_arg is nullptr. * @param pubkeys The evaluations of the m_pubkey_args field. - * @param script The evaluation of m_script_arg (or nullptr when m_script_arg is nullptr). + * @param script The evaluation of m_subdescriptor_arg (or nullptr when m_subdescriptor_arg is nullptr). * @param out A FlatSigningProvider to put scripts or public keys in that are necessary to the solver. * The script arguments to this function are automatically added, as is the origin info of the provided pubkeys. * @return A vector with scriptPubKeys for this descriptor. @@ -360,12 +362,12 @@ protected: virtual std::vector<CScript> MakeScripts(const std::vector<CPubKey>& pubkeys, const CScript* script, FlatSigningProvider& out) const = 0; public: - DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_script_arg(std::move(script)), m_name(name) {} + DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_subdescriptor_arg(std::move(script)), m_name(name) {} bool IsSolvable() const override { - if (m_script_arg) { - if (!m_script_arg->IsSolvable()) return false; + if (m_subdescriptor_arg) { + if (!m_subdescriptor_arg->IsSolvable()) return false; } return true; } @@ -375,8 +377,8 @@ public: for (const auto& pubkey : m_pubkey_args) { if (pubkey->IsRange()) return true; } - if (m_script_arg) { - if (m_script_arg->IsRange()) return true; + if (m_subdescriptor_arg) { + if (m_subdescriptor_arg->IsRange()) return true; } return false; } @@ -396,10 +398,10 @@ public: } ret += std::move(tmp); } - if (m_script_arg) { + if (m_subdescriptor_arg) { if (pos++) ret += ","; std::string tmp; - if (!m_script_arg->ToStringHelper(arg, tmp, priv)) return false; + if (!m_subdescriptor_arg->ToStringHelper(arg, tmp, priv)) return false; ret += std::move(tmp); } out = std::move(ret) + ")"; @@ -428,6 +430,8 @@ public: // Construct temporary data in `entries` and `subscripts`, to avoid producing output in case of failure. for (const auto& p : m_pubkey_args) { entries.emplace_back(); + // If we have a cache, we don't need GetPubKey to compute the public key. + // Pass in nullptr to signify only origin info is desired. if (!p->GetPubKey(pos, arg, cache_read ? nullptr : &entries.back().first, entries.back().second)) return false; if (cache_read) { // Cached expanded public key exists, use it. @@ -444,9 +448,9 @@ public: } } std::vector<CScript> subscripts; - if (m_script_arg) { + if (m_subdescriptor_arg) { FlatSigningProvider subprovider; - if (!m_script_arg->ExpandHelper(pos, arg, cache_read, subscripts, subprovider, cache_write)) return false; + if (!m_subdescriptor_arg->ExpandHelper(pos, arg, cache_read, subscripts, subprovider, cache_write)) return false; out = Merge(out, subprovider); } @@ -456,7 +460,7 @@ public: pubkeys.push_back(entry.first); out.origins.emplace(entry.first.GetID(), std::make_pair<CPubKey, KeyOriginInfo>(CPubKey(entry.first), std::move(entry.second))); } - if (m_script_arg) { + if (m_subdescriptor_arg) { for (const auto& subscript : subscripts) { out.scripts.emplace(CScriptID(subscript), subscript); std::vector<CScript> addscripts = MakeScripts(pubkeys, &subscript, out); @@ -488,9 +492,9 @@ public: if (!p->GetPrivKey(pos, provider, key)) continue; out.keys.emplace(key.GetPubKey().GetID(), key); } - if (m_script_arg) { + if (m_subdescriptor_arg) { FlatSigningProvider subprovider; - m_script_arg->ExpandPrivate(pos, provider, subprovider); + m_subdescriptor_arg->ExpandPrivate(pos, provider, subprovider); out = Merge(out, subprovider); } } @@ -686,7 +690,7 @@ std::vector<Span<const char>> Split(const Span<const char>& sp, char sep) } /** Parse a key path, being passed a split list of elements (the first element is ignored). */ -NODISCARD bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out) +NODISCARD bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out, std::string& error) { for (size_t i = 1; i < split.size(); ++i) { Span<const char> elem = split[i]; @@ -696,33 +700,60 @@ NODISCARD bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& hardened = true; } uint32_t p; - if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p) || p > 0x7FFFFFFFUL) return false; + if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p)) { + error = strprintf("Key path value '%s' is not a valid uint32", std::string(elem.begin(), elem.end()).c_str()); + return false; + } else if (p > 0x7FFFFFFFUL) { + error = strprintf("Key path value %u is out of range", p); + return false; + } out.push_back(p | (((uint32_t)hardened) << 31)); } return true; } /** Parse a public key that excludes origin information. */ -std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out) +std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out, std::string& error) { auto split = Split(sp, '/'); std::string str(split[0].begin(), split[0].end()); + if (str.size() == 0) { + error = "No key provided"; + return nullptr; + } if (split.size() == 1) { if (IsHex(str)) { std::vector<unsigned char> data = ParseHex(str); CPubKey pubkey(data); - if (pubkey.IsFullyValid() && (permit_uncompressed || pubkey.IsCompressed())) return MakeUnique<ConstPubkeyProvider>(pubkey); + if (pubkey.IsFullyValid()) { + if (permit_uncompressed || pubkey.IsCompressed()) { + return MakeUnique<ConstPubkeyProvider>(pubkey); + } else { + error = "Uncompressed keys are not allowed"; + return nullptr; + } + } + error = strprintf("Pubkey '%s' is invalid", str); + return nullptr; } CKey key = DecodeSecret(str); - if (key.IsValid() && (permit_uncompressed || key.IsCompressed())) { - CPubKey pubkey = key.GetPubKey(); - out.keys.emplace(pubkey.GetID(), key); - return MakeUnique<ConstPubkeyProvider>(pubkey); + if (key.IsValid()) { + if (permit_uncompressed || key.IsCompressed()) { + CPubKey pubkey = key.GetPubKey(); + out.keys.emplace(pubkey.GetID(), key); + return MakeUnique<ConstPubkeyProvider>(pubkey); + } else { + error = "Uncompressed keys are not allowed"; + return nullptr; + } } } CExtKey extkey = DecodeExtKey(str); CExtPubKey extpubkey = DecodeExtPubKey(str); - if (!extkey.key.IsValid() && !extpubkey.pubkey.IsValid()) return nullptr; + if (!extkey.key.IsValid() && !extpubkey.pubkey.IsValid()) { + error = strprintf("key '%s' is not valid", str); + return nullptr; + } KeyPath path; DeriveType type = DeriveType::NO; if (split.back() == MakeSpan("*").first(1)) { @@ -732,7 +763,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char>& sp, boo split.pop_back(); type = DeriveType::HARDENED; } - if (!ParseKeyPath(split, path)) return nullptr; + if (!ParseKeyPath(split, path, error)) return nullptr; if (extkey.key.IsValid()) { extpubkey = extkey.Neuter(); out.keys.emplace(extpubkey.pubkey.GetID(), extkey.key); @@ -741,95 +772,154 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char>& sp, boo } /** Parse a public key including origin information (if enabled). */ -std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out) +std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out, std::string& error) { auto origin_split = Split(sp, ']'); - if (origin_split.size() > 2) return nullptr; - if (origin_split.size() == 1) return ParsePubkeyInner(origin_split[0], permit_uncompressed, out); - if (origin_split[0].size() < 1 || origin_split[0][0] != '[') return nullptr; + if (origin_split.size() > 2) { + error = "Multiple ']' characters found for a single pubkey"; + return nullptr; + } + if (origin_split.size() == 1) return ParsePubkeyInner(origin_split[0], permit_uncompressed, out, error); + if (origin_split[0].size() < 1 || origin_split[0][0] != '[') { + error = strprintf("Key origin start '[ character expected but not found, got '%c' instead", origin_split[0][0]); + return nullptr; + } auto slash_split = Split(origin_split[0].subspan(1), '/'); - if (slash_split[0].size() != 8) return nullptr; + if (slash_split[0].size() != 8) { + error = strprintf("Fingerprint is not 4 bytes (%u characters instead of 8 characters)", slash_split[0].size()); + return nullptr; + } std::string fpr_hex = std::string(slash_split[0].begin(), slash_split[0].end()); - if (!IsHex(fpr_hex)) return nullptr; + if (!IsHex(fpr_hex)) { + error = strprintf("Fingerprint '%s' is not hex", fpr_hex); + return nullptr; + } auto fpr_bytes = ParseHex(fpr_hex); KeyOriginInfo info; static_assert(sizeof(info.fingerprint) == 4, "Fingerprint must be 4 bytes"); assert(fpr_bytes.size() == 4); std::copy(fpr_bytes.begin(), fpr_bytes.end(), info.fingerprint); - if (!ParseKeyPath(slash_split, info.path)) return nullptr; - auto provider = ParsePubkeyInner(origin_split[1], permit_uncompressed, out); + if (!ParseKeyPath(slash_split, info.path, error)) return nullptr; + auto provider = ParsePubkeyInner(origin_split[1], permit_uncompressed, out, error); if (!provider) return nullptr; return MakeUnique<OriginPubkeyProvider>(std::move(info), std::move(provider)); } /** Parse a script in a particular context. */ -std::unique_ptr<DescriptorImpl> ParseScript(Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out) +std::unique_ptr<DescriptorImpl> ParseScript(Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error) { auto expr = Expr(sp); if (Func("pk", expr)) { - auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2WSH, out); + auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2WSH, out, error); if (!pubkey) return nullptr; return MakeUnique<PKDescriptor>(std::move(pubkey)); } if (Func("pkh", expr)) { - auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2WSH, out); + auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2WSH, out, error); if (!pubkey) return nullptr; return MakeUnique<PKHDescriptor>(std::move(pubkey)); } if (ctx == ParseScriptContext::TOP && Func("combo", expr)) { - auto pubkey = ParsePubkey(expr, true, out); + auto pubkey = ParsePubkey(expr, true, out, error); if (!pubkey) return nullptr; return MakeUnique<ComboDescriptor>(std::move(pubkey)); + } else if (ctx != ParseScriptContext::TOP && Func("combo", expr)) { + error = "Cannot have combo in non-top level"; + return nullptr; } if (Func("multi", expr)) { auto threshold = Expr(expr); uint32_t thres; std::vector<std::unique_ptr<PubkeyProvider>> providers; - if (!ParseUInt32(std::string(threshold.begin(), threshold.end()), &thres)) return nullptr; + if (!ParseUInt32(std::string(threshold.begin(), threshold.end()), &thres)) { + error = strprintf("Multi threshold '%s' is not valid", std::string(threshold.begin(), threshold.end()).c_str()); + return nullptr; + } size_t script_size = 0; while (expr.size()) { - if (!Const(",", expr)) return nullptr; + if (!Const(",", expr)) { + error = strprintf("Multi: expected ',', got '%c'", expr[0]); + return nullptr; + } auto arg = Expr(expr); - auto pk = ParsePubkey(arg, ctx != ParseScriptContext::P2WSH, out); + auto pk = ParsePubkey(arg, ctx != ParseScriptContext::P2WSH, out, error); if (!pk) return nullptr; script_size += pk->GetSize() + 1; providers.emplace_back(std::move(pk)); } - if (providers.size() < 1 || providers.size() > 16 || thres < 1 || thres > providers.size()) return nullptr; + if (providers.size() < 1 || providers.size() > 16) { + error = strprintf("Cannot have %u keys in multisig; must have between 1 and 16 keys, inclusive", providers.size()); + return nullptr; + } else if (thres < 1) { + error = strprintf("Multisig threshold cannot be %d, must be at least 1", thres); + return nullptr; + } else if (thres > providers.size()) { + error = strprintf("Multisig threshold cannot be larger than the number of keys; threshold is %d but only %u keys specified", thres, providers.size()); + return nullptr; + } if (ctx == ParseScriptContext::TOP) { - if (providers.size() > 3) return nullptr; // Not more than 3 pubkeys for raw multisig + if (providers.size() > 3) { + error = strprintf("Cannot have %u pubkeys in bare multisig; only at most 3 pubkeys", providers.size()); + return nullptr; + } } if (ctx == ParseScriptContext::P2SH) { - if (script_size + 3 > 520) return nullptr; // Enforce P2SH script size limit + if (script_size + 3 > 520) { + error = strprintf("P2SH script is too large, %d bytes is larger than 520 bytes", script_size + 3); + return nullptr; + } } return MakeUnique<MultisigDescriptor>(thres, std::move(providers)); } if (ctx != ParseScriptContext::P2WSH && Func("wpkh", expr)) { - auto pubkey = ParsePubkey(expr, false, out); + auto pubkey = ParsePubkey(expr, false, out, error); if (!pubkey) return nullptr; return MakeUnique<WPKHDescriptor>(std::move(pubkey)); + } else if (ctx == ParseScriptContext::P2WSH && Func("wpkh", expr)) { + error = "Cannot have wpkh within wsh"; + return nullptr; } if (ctx == ParseScriptContext::TOP && Func("sh", expr)) { - auto desc = ParseScript(expr, ParseScriptContext::P2SH, out); + auto desc = ParseScript(expr, ParseScriptContext::P2SH, out, error); if (!desc || expr.size()) return nullptr; return MakeUnique<SHDescriptor>(std::move(desc)); + } else if (ctx != ParseScriptContext::TOP && Func("sh", expr)) { + error = "Cannot have sh in non-top level"; + return nullptr; } if (ctx != ParseScriptContext::P2WSH && Func("wsh", expr)) { - auto desc = ParseScript(expr, ParseScriptContext::P2WSH, out); + auto desc = ParseScript(expr, ParseScriptContext::P2WSH, out, error); if (!desc || expr.size()) return nullptr; return MakeUnique<WSHDescriptor>(std::move(desc)); + } else if (ctx == ParseScriptContext::P2WSH && Func("wsh", expr)) { + error = "Cannot have wsh within wsh"; + return nullptr; } if (ctx == ParseScriptContext::TOP && Func("addr", expr)) { CTxDestination dest = DecodeDestination(std::string(expr.begin(), expr.end())); - if (!IsValidDestination(dest)) return nullptr; + if (!IsValidDestination(dest)) { + error = "Address is not valid"; + return nullptr; + } return MakeUnique<AddressDescriptor>(std::move(dest)); } if (ctx == ParseScriptContext::TOP && Func("raw", expr)) { std::string str(expr.begin(), expr.end()); - if (!IsHex(str)) return nullptr; + if (!IsHex(str)) { + error = "Raw script is not hex"; + return nullptr; + } auto bytes = ParseHex(str); return MakeUnique<RawDescriptor>(CScript(bytes.begin(), bytes.end())); } + if (ctx == ParseScriptContext::P2SH) { + error = "A function is needed within P2SH"; + return nullptr; + } else if (ctx == ParseScriptContext::P2WSH) { + error = "A function is needed within P2WSH"; + return nullptr; + } + error = strprintf("%s is not a valid descriptor function", std::string(expr.begin(), expr.end())); return nullptr; } @@ -910,27 +1000,58 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo } // namespace -std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out, bool require_checksum) +/** Check a descriptor checksum, and update desc to be the checksum-less part. */ +bool CheckChecksum(Span<const char>& sp, bool require_checksum, std::string& error, std::string* out_checksum = nullptr) { - Span<const char> sp(descriptor.data(), descriptor.size()); - - // Checksum checks auto check_split = Split(sp, '#'); - if (check_split.size() > 2) return nullptr; // Multiple '#' symbols - if (check_split.size() == 1 && require_checksum) return nullptr; // Missing checksum + if (check_split.size() > 2) { + error = "Multiple '#' symbols"; + return false; + } + if (check_split.size() == 1 && require_checksum){ + error = "Missing checksum"; + return false; + } + if (check_split.size() == 2) { + if (check_split[1].size() != 8) { + error = strprintf("Expected 8 character checksum, not %u characters", check_split[1].size()); + return false; + } + } + auto checksum = DescriptorChecksum(check_split[0]); + if (checksum.empty()) { + error = "Invalid characters in payload"; + return false; + } if (check_split.size() == 2) { - if (check_split[1].size() != 8) return nullptr; // Unexpected length for checksum - auto checksum = DescriptorChecksum(check_split[0]); - if (checksum.empty()) return nullptr; // Invalid characters in payload - if (!std::equal(checksum.begin(), checksum.end(), check_split[1].begin())) return nullptr; // Checksum mismatch + if (!std::equal(checksum.begin(), checksum.end(), check_split[1].begin())) { + error = strprintf("Provided checksum '%s' does not match computed checksum '%s'", std::string(check_split[1].begin(), check_split[1].end()), checksum); + return false; + } } + if (out_checksum) *out_checksum = std::move(checksum); sp = check_split[0]; + return true; +} - auto ret = ParseScript(sp, ParseScriptContext::TOP, out); +std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out, std::string& error, bool require_checksum) +{ + Span<const char> sp(descriptor.data(), descriptor.size()); + if (!CheckChecksum(sp, require_checksum, error)) return nullptr; + auto ret = ParseScript(sp, ParseScriptContext::TOP, out, error); if (sp.size() == 0 && ret) return std::unique_ptr<Descriptor>(std::move(ret)); return nullptr; } +std::string GetDescriptorChecksum(const std::string& descriptor) +{ + std::string ret; + std::string error; + Span<const char> sp(descriptor.data(), descriptor.size()); + if (!CheckChecksum(sp, false, error, &ret)) return ""; + return ret; +} + std::unique_ptr<Descriptor> InferDescriptor(const CScript& script, const SigningProvider& provider) { return InferScript(script, ParseScriptContext::TOP, provider); diff --git a/src/script/descriptor.h b/src/script/descriptor.h index af7ae229ca..0195ca0939 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -7,6 +7,7 @@ #include <script/script.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <vector> @@ -46,9 +47,9 @@ struct Descriptor { * * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored. * provider: the provider to query for private keys in case of hardened derivation. - * output_script: the expanded scriptPubKeys will be put here. + * output_scripts: the expanded scriptPubKeys will be put here. * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider). - * cache: vector which will be overwritten with cache data necessary to-evaluate the descriptor at this point without access to private keys. + * cache: vector which will be overwritten with cache data necessary to evaluate the descriptor at this point without access to private keys. */ virtual bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, std::vector<unsigned char>* cache = nullptr) const = 0; @@ -56,7 +57,7 @@ struct Descriptor { * * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored. * cache: vector from which cached expansion data will be read. - * output_script: the expanded scriptPubKeys will be put here. + * output_scripts: the expanded scriptPubKeys will be put here. * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider). */ virtual bool ExpandFromCache(int pos, const std::vector<unsigned char>& cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0; @@ -78,7 +79,15 @@ struct Descriptor { * If a parse error occurs, or the checksum is missing/invalid, or anything * else is wrong, nullptr is returned. */ -std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out, bool require_checksum = false); +std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out, std::string& error, bool require_checksum = false); + +/** Get the checksum for a descriptor. + * + * If it already has one, and it is correct, return the checksum in the input. + * If it already has one that is wrong, return "". + * If it does not already have one, return the checksum that would need to be added. + */ +std::string GetDescriptorChecksum(const std::string& descriptor); /** Find a descriptor for the specified script, using information from provider where possible. * diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 95b25b4911..f8701b6d01 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -926,7 +926,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& // Drop the signature in pre-segwit scripts but not segwit scripts if (sigversion == SigVersion::BASE) { - int found = FindAndDelete(scriptCode, CScript(vchSig)); + int found = FindAndDelete(scriptCode, CScript() << vchSig); if (found > 0 && (flags & SCRIPT_VERIFY_CONST_SCRIPTCODE)) return set_error(serror, SCRIPT_ERR_SIG_FINDANDDELETE); } @@ -992,7 +992,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& { valtype& vchSig = stacktop(-isig-k); if (sigversion == SigVersion::BASE) { - int found = FindAndDelete(scriptCode, CScript(vchSig)); + int found = FindAndDelete(scriptCode, CScript() << vchSig); if (found > 0 && (flags & SCRIPT_VERIFY_CONST_SCRIPTCODE)) return set_error(serror, SCRIPT_ERR_SIG_FINDANDDELETE); } diff --git a/src/script/keyorigin.h b/src/script/keyorigin.h new file mode 100644 index 0000000000..610f233500 --- /dev/null +++ b/src/script/keyorigin.h @@ -0,0 +1,37 @@ +// Copyright (c) 2019 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_SCRIPT_KEYORIGIN_H +#define BITCOIN_SCRIPT_KEYORIGIN_H + +#include <serialize.h> +#include <streams.h> +#include <vector> + +struct KeyOriginInfo +{ + unsigned char fingerprint[4]; //!< First 32 bits of the Hash160 of the public key at the root of the path + std::vector<uint32_t> path; + + friend bool operator==(const KeyOriginInfo& a, const KeyOriginInfo& b) + { + return std::equal(std::begin(a.fingerprint), std::end(a.fingerprint), std::begin(b.fingerprint)) && a.path == b.path; + } + + ADD_SERIALIZE_METHODS; + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(fingerprint); + READWRITE(path); + } + + void clear() + { + memset(fingerprint, 0, 4); + path.clear(); + } +}; + +#endif // BITCOIN_SCRIPT_KEYORIGIN_H diff --git a/src/script/script.h b/src/script/script.h index 11e8661a5b..6355b8a704 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -437,7 +437,9 @@ public: explicit CScript(opcodetype b) { operator<<(b); } explicit CScript(const CScriptNum& b) { operator<<(b); } - explicit CScript(const std::vector<unsigned char>& b) { operator<<(b); } + // delete non-existent constructor to defend against future introduction + // e.g. via prevector + explicit CScript(const std::vector<unsigned char>& b) = delete; CScript& operator<<(int64_t b) { return push_int64(b); } diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 36dd68a3d8..13481af9c5 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -8,6 +8,7 @@ #include <key.h> #include <policy/policy.h> #include <primitives/transaction.h> +#include <script/signingprovider.h> #include <script/standard.h> #include <uint256.h> @@ -423,22 +424,10 @@ public: } }; -template<typename M, typename K, typename V> -bool LookupHelper(const M& map, const K& key, V& value) -{ - auto it = map.find(key); - if (it != map.end()) { - value = it->second; - return true; - } - return false; -} - } const BaseSignatureCreator& DUMMY_SIGNATURE_CREATOR = DummySignatureCreator(32, 32); const BaseSignatureCreator& DUMMY_MAXIMUM_SIGNATURE_CREATOR = DummySignatureCreator(33, 32); -const SigningProvider& DUMMY_SIGNING_PROVIDER = SigningProvider(); bool IsSolvable(const SigningProvider& provider, const CScript& script) { @@ -459,49 +448,18 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script) return false; } -bool HidingSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const -{ - return m_provider->GetCScript(scriptid, script); -} - -bool HidingSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const -{ - return m_provider->GetPubKey(keyid, pubkey); -} - -bool HidingSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const -{ - if (m_hide_secret) return false; - return m_provider->GetKey(keyid, key); -} - -bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const +bool IsSegWitOutput(const SigningProvider& provider, const CScript& script) { - if (m_hide_origin) return false; - return m_provider->GetKeyOrigin(keyid, info); -} - -bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); } -bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); } -bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const -{ - std::pair<CPubKey, KeyOriginInfo> out; - bool ret = LookupHelper(origins, keyid, out); - if (ret) info = std::move(out.second); - return ret; -} -bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); } - -FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b) -{ - FlatSigningProvider ret; - ret.scripts = a.scripts; - ret.scripts.insert(b.scripts.begin(), b.scripts.end()); - ret.pubkeys = a.pubkeys; - ret.pubkeys.insert(b.pubkeys.begin(), b.pubkeys.end()); - ret.keys = a.keys; - ret.keys.insert(b.keys.begin(), b.keys.end()); - ret.origins = a.origins; - ret.origins.insert(b.origins.begin(), b.origins.end()); - return ret; + std::vector<valtype> solutions; + auto whichtype = Solver(script, solutions); + if (whichtype == TX_WITNESS_V0_SCRIPTHASH || whichtype == TX_WITNESS_V0_KEYHASH || whichtype == TX_WITNESS_UNKNOWN) return true; + if (whichtype == TX_SCRIPTHASH) { + auto h160 = uint160(solutions[0]); + CScript subscript; + if (provider.GetCScript(h160, subscript)) { + whichtype = Solver(subscript, solutions); + if (whichtype == TX_WITNESS_V0_SCRIPTHASH || whichtype == TX_WITNESS_V0_KEYHASH || whichtype == TX_WITNESS_UNKNOWN) return true; + } + } + return false; } diff --git a/src/script/sign.h b/src/script/sign.h index f746325b90..0e751afd3b 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -10,6 +10,7 @@ #include <hash.h> #include <pubkey.h> #include <script/interpreter.h> +#include <script/keyorigin.h> #include <streams.h> class CKey; @@ -17,77 +18,10 @@ class CKeyID; class CScript; class CScriptID; class CTransaction; +class SigningProvider; struct CMutableTransaction; -struct KeyOriginInfo -{ - unsigned char fingerprint[4]; //!< First 32 bits of the Hash160 of the public key at the root of the path - std::vector<uint32_t> path; - - friend bool operator==(const KeyOriginInfo& a, const KeyOriginInfo& b) - { - return std::equal(std::begin(a.fingerprint), std::end(a.fingerprint), std::begin(b.fingerprint)) && a.path == b.path; - } - - ADD_SERIALIZE_METHODS; - template <typename Stream, typename Operation> - inline void SerializationOp(Stream& s, Operation ser_action) - { - READWRITE(fingerprint); - READWRITE(path); - } - - void clear() - { - memset(fingerprint, 0, 4); - path.clear(); - } -}; - -/** An interface to be implemented by keystores that support signing. */ -class SigningProvider -{ -public: - virtual ~SigningProvider() {} - virtual bool GetCScript(const CScriptID &scriptid, CScript& script) const { return false; } - virtual bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const { return false; } - virtual bool GetKey(const CKeyID &address, CKey& key) const { return false; } - virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; } -}; - -extern const SigningProvider& DUMMY_SIGNING_PROVIDER; - -class HidingSigningProvider : public SigningProvider -{ -private: - const bool m_hide_secret; - const bool m_hide_origin; - const SigningProvider* m_provider; - -public: - HidingSigningProvider(const SigningProvider* provider, bool hide_secret, bool hide_origin) : m_hide_secret(hide_secret), m_hide_origin(hide_origin), m_provider(provider) {} - bool GetCScript(const CScriptID& scriptid, CScript& script) const override; - bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; - bool GetKey(const CKeyID& keyid, CKey& key) const override; - bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; -}; - -struct FlatSigningProvider final : public SigningProvider -{ - std::map<CScriptID, CScript> scripts; - std::map<CKeyID, CPubKey> pubkeys; - std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins; - std::map<CKeyID, CKey> keys; - - bool GetCScript(const CScriptID& scriptid, CScript& script) const override; - bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; - bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; - bool GetKey(const CKeyID& keyid, CKey& key) const override; -}; - -FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b); - /** Interface for signature creators. */ class BaseSignatureCreator { public: @@ -232,4 +166,7 @@ void UpdateInput(CTxIn& input, const SignatureData& data); * Solvability is unrelated to whether we consider this output to be ours. */ bool IsSolvable(const SigningProvider& provider, const CScript& script); +/** Check whether a scriptPubKey is known to be segwit. */ +bool IsSegWitOutput(const SigningProvider& provider, const CScript& script); + #endif // BITCOIN_SCRIPT_SIGN_H diff --git a/src/keystore.cpp b/src/script/signingprovider.cpp index f6d19416ce..01757e2f65 100644 --- a/src/keystore.cpp +++ b/src/script/signingprovider.cpp @@ -1,18 +1,78 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 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 <keystore.h> +#include <script/keyorigin.h> +#include <script/signingprovider.h> +#include <script/standard.h> #include <util/system.h> -void CBasicKeyStore::ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) +const SigningProvider& DUMMY_SIGNING_PROVIDER = SigningProvider(); + +template<typename M, typename K, typename V> +bool LookupHelper(const M& map, const K& key, V& value) +{ + auto it = map.find(key); + if (it != map.end()) { + value = it->second; + return true; + } + return false; +} + +bool HidingSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const +{ + return m_provider->GetCScript(scriptid, script); +} + +bool HidingSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const +{ + return m_provider->GetPubKey(keyid, pubkey); +} + +bool HidingSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const +{ + if (m_hide_secret) return false; + return m_provider->GetKey(keyid, key); +} + +bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const +{ + if (m_hide_origin) return false; + return m_provider->GetKeyOrigin(keyid, info); +} + +bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); } +bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); } +bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const +{ + std::pair<CPubKey, KeyOriginInfo> out; + bool ret = LookupHelper(origins, keyid, out); + if (ret) info = std::move(out.second); + return ret; +} +bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); } + +FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b) +{ + FlatSigningProvider ret; + ret.scripts = a.scripts; + ret.scripts.insert(b.scripts.begin(), b.scripts.end()); + ret.pubkeys = a.pubkeys; + ret.pubkeys.insert(b.pubkeys.begin(), b.pubkeys.end()); + ret.keys = a.keys; + ret.keys.insert(b.keys.begin(), b.keys.end()); + ret.origins = a.origins; + ret.origins.insert(b.origins.begin(), b.origins.end()); + return ret; +} + +void FillableSigningProvider::ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) { AssertLockHeld(cs_KeyStore); CKeyID key_id = pubkey.GetID(); - // We must actually know about this key already. - assert(HaveKey(key_id) || mapWatchKeys.count(key_id)); // This adds the redeemscripts necessary to detect P2WPKH and P2SH-P2WPKH // outputs. Technically P2WPKH outputs don't have a redeemscript to be // spent. However, our current IsMine logic requires the corresponding @@ -32,23 +92,17 @@ void CBasicKeyStore::ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) } } -bool CBasicKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const +bool FillableSigningProvider::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const { CKey key; if (!GetKey(address, key)) { - LOCK(cs_KeyStore); - WatchKeyMap::const_iterator it = mapWatchKeys.find(address); - if (it != mapWatchKeys.end()) { - vchPubKeyOut = it->second; - return true; - } return false; } vchPubKeyOut = key.GetPubKey(); return true; } -bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) +bool FillableSigningProvider::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) { LOCK(cs_KeyStore); mapKeys[pubkey.GetID()] = key; @@ -56,13 +110,13 @@ bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) return true; } -bool CBasicKeyStore::HaveKey(const CKeyID &address) const +bool FillableSigningProvider::HaveKey(const CKeyID &address) const { LOCK(cs_KeyStore); return mapKeys.count(address) > 0; } -std::set<CKeyID> CBasicKeyStore::GetKeys() const +std::set<CKeyID> FillableSigningProvider::GetKeys() const { LOCK(cs_KeyStore); std::set<CKeyID> set_address; @@ -72,7 +126,7 @@ std::set<CKeyID> CBasicKeyStore::GetKeys() const return set_address; } -bool CBasicKeyStore::GetKey(const CKeyID &address, CKey &keyOut) const +bool FillableSigningProvider::GetKey(const CKeyID &address, CKey &keyOut) const { LOCK(cs_KeyStore); KeyMap::const_iterator mi = mapKeys.find(address); @@ -83,23 +137,23 @@ bool CBasicKeyStore::GetKey(const CKeyID &address, CKey &keyOut) const return false; } -bool CBasicKeyStore::AddCScript(const CScript& redeemScript) +bool FillableSigningProvider::AddCScript(const CScript& redeemScript) { if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) - return error("CBasicKeyStore::AddCScript(): redeemScripts > %i bytes are invalid", MAX_SCRIPT_ELEMENT_SIZE); + return error("FillableSigningProvider::AddCScript(): redeemScripts > %i bytes are invalid", MAX_SCRIPT_ELEMENT_SIZE); LOCK(cs_KeyStore); mapScripts[CScriptID(redeemScript)] = redeemScript; return true; } -bool CBasicKeyStore::HaveCScript(const CScriptID& hash) const +bool FillableSigningProvider::HaveCScript(const CScriptID& hash) const { LOCK(cs_KeyStore); return mapScripts.count(hash) > 0; } -std::set<CScriptID> CBasicKeyStore::GetCScripts() const +std::set<CScriptID> FillableSigningProvider::GetCScripts() const { LOCK(cs_KeyStore); std::set<CScriptID> set_script; @@ -109,7 +163,7 @@ std::set<CScriptID> CBasicKeyStore::GetCScripts() const return set_script; } -bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const +bool FillableSigningProvider::GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const { LOCK(cs_KeyStore); ScriptMap::const_iterator mi = mapScripts.find(hash); @@ -121,60 +175,7 @@ bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut) return false; } -static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut) -{ - //TODO: Use Solver to extract this? - CScript::const_iterator pc = dest.begin(); - opcodetype opcode; - std::vector<unsigned char> vch; - if (!dest.GetOp(pc, opcode, vch) || !CPubKey::ValidSize(vch)) - return false; - pubKeyOut = CPubKey(vch); - if (!pubKeyOut.IsFullyValid()) - return false; - if (!dest.GetOp(pc, opcode, vch) || opcode != OP_CHECKSIG || dest.GetOp(pc, opcode, vch)) - return false; - return true; -} - -bool CBasicKeyStore::AddWatchOnly(const CScript &dest) -{ - LOCK(cs_KeyStore); - setWatchOnly.insert(dest); - CPubKey pubKey; - if (ExtractPubKey(dest, pubKey)) { - mapWatchKeys[pubKey.GetID()] = pubKey; - ImplicitlyLearnRelatedKeyScripts(pubKey); - } - return true; -} - -bool CBasicKeyStore::RemoveWatchOnly(const CScript &dest) -{ - LOCK(cs_KeyStore); - setWatchOnly.erase(dest); - CPubKey pubKey; - if (ExtractPubKey(dest, pubKey)) { - mapWatchKeys.erase(pubKey.GetID()); - } - // Related CScripts are not removed; having superfluous scripts around is - // harmless (see comment in ImplicitlyLearnRelatedKeyScripts). - return true; -} - -bool CBasicKeyStore::HaveWatchOnly(const CScript &dest) const -{ - LOCK(cs_KeyStore); - return setWatchOnly.count(dest) > 0; -} - -bool CBasicKeyStore::HaveWatchOnly() const -{ - LOCK(cs_KeyStore); - return (!setWatchOnly.empty()); -} - -CKeyID GetKeyForDestination(const CKeyStore& store, const CTxDestination& dest) +CKeyID GetKeyForDestination(const SigningProvider& store, const CTxDestination& dest) { // Only supports destinations which map to single public keys, i.e. P2PKH, // P2WPKH, and P2SH-P2WPKH. @@ -196,10 +197,3 @@ CKeyID GetKeyForDestination(const CKeyStore& store, const CTxDestination& dest) } return CKeyID(); } - -bool HaveKey(const CKeyStore& store, const CKey& key) -{ - CKey key2; - key2.Set(key.begin(), key.end(), !key.IsCompressed()); - return store.HaveKey(key.GetPubKey().GetID()) || store.HaveKey(key2.GetPubKey().GetID()); -} diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h new file mode 100644 index 0000000000..4eec2311d4 --- /dev/null +++ b/src/script/signingprovider.h @@ -0,0 +1,92 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2019 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_SCRIPT_SIGNINGPROVIDER_H +#define BITCOIN_SCRIPT_SIGNINGPROVIDER_H + +#include <key.h> +#include <pubkey.h> +#include <script/script.h> +#include <script/standard.h> +#include <sync.h> + +struct KeyOriginInfo; + +/** An interface to be implemented by keystores that support signing. */ +class SigningProvider +{ +public: + virtual ~SigningProvider() {} + virtual bool GetCScript(const CScriptID &scriptid, CScript& script) const { return false; } + virtual bool HaveCScript(const CScriptID &scriptid) const { return false; } + virtual bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const { return false; } + virtual bool GetKey(const CKeyID &address, CKey& key) const { return false; } + virtual bool HaveKey(const CKeyID &address) const { return false; } + virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; } +}; + +extern const SigningProvider& DUMMY_SIGNING_PROVIDER; + +class HidingSigningProvider : public SigningProvider +{ +private: + const bool m_hide_secret; + const bool m_hide_origin; + const SigningProvider* m_provider; + +public: + HidingSigningProvider(const SigningProvider* provider, bool hide_secret, bool hide_origin) : m_hide_secret(hide_secret), m_hide_origin(hide_origin), m_provider(provider) {} + bool GetCScript(const CScriptID& scriptid, CScript& script) const override; + bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; + bool GetKey(const CKeyID& keyid, CKey& key) const override; + bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; +}; + +struct FlatSigningProvider final : public SigningProvider +{ + std::map<CScriptID, CScript> scripts; + std::map<CKeyID, CPubKey> pubkeys; + std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins; + std::map<CKeyID, CKey> keys; + + bool GetCScript(const CScriptID& scriptid, CScript& script) const override; + bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; + bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; + bool GetKey(const CKeyID& keyid, CKey& key) const override; +}; + +FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b); + +/** Fillable signing provider that keeps keys in an address->secret map */ +class FillableSigningProvider : public SigningProvider +{ +protected: + mutable CCriticalSection cs_KeyStore; + + using KeyMap = std::map<CKeyID, CKey>; + using ScriptMap = std::map<CScriptID, CScript>; + + KeyMap mapKeys GUARDED_BY(cs_KeyStore); + ScriptMap mapScripts GUARDED_BY(cs_KeyStore); + + void ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); + +public: + virtual bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); + virtual bool AddKey(const CKey &key) { return AddKeyPubKey(key, key.GetPubKey()); } + virtual bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; + virtual bool HaveKey(const CKeyID &address) const override; + virtual std::set<CKeyID> GetKeys() const; + virtual bool GetKey(const CKeyID &address, CKey &keyOut) const override; + virtual bool AddCScript(const CScript& redeemScript); + virtual bool HaveCScript(const CScriptID &hash) const override; + virtual std::set<CScriptID> GetCScripts() const; + virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const override; +}; + +/** Return the CKeyID of the key involved in a script (if there is a unique one). */ +CKeyID GetKeyForDestination(const SigningProvider& store, const CTxDestination& dest); + +#endif // BITCOIN_SCRIPT_SIGNINGPROVIDER_H diff --git a/src/script/standard.cpp b/src/script/standard.cpp index b7d6cd925c..fc6898f444 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -9,7 +9,6 @@ #include <pubkey.h> #include <script/script.h> - typedef std::vector<unsigned char> valtype; bool fAcceptDatacarrier = DEFAULT_ACCEPT_DATACARRIER; diff --git a/src/serialize.h b/src/serialize.h index 1dc27d84eb..a38d76fc18 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -555,6 +555,7 @@ template<typename Stream, unsigned int N, typename T> inline void Unserialize(St * vectors of unsigned char are a special case and are intended to be serialized as a single opaque blob. */ template<typename Stream, typename T, typename A> void Serialize_impl(Stream& os, const std::vector<T, A>& v, const unsigned char&); +template<typename Stream, typename T, typename A> void Serialize_impl(Stream& os, const std::vector<T, A>& v, const bool&); template<typename Stream, typename T, typename A, typename V> void Serialize_impl(Stream& os, const std::vector<T, A>& v, const V&); template<typename Stream, typename T, typename A> inline void Serialize(Stream& os, const std::vector<T, A>& v); template<typename Stream, typename T, typename A> void Unserialize_impl(Stream& is, std::vector<T, A>& v, const unsigned char&); @@ -713,6 +714,18 @@ void Serialize_impl(Stream& os, const std::vector<T, A>& v, const unsigned char& os.write((char*)v.data(), v.size() * sizeof(T)); } +template<typename Stream, typename T, typename A> +void Serialize_impl(Stream& os, const std::vector<T, A>& v, const bool&) +{ + // A special case for std::vector<bool>, as dereferencing + // std::vector<bool>::const_iterator does not result in a const bool& + // due to std::vector's special casing for bool arguments. + WriteCompactSize(os, v.size()); + for (bool elem : v) { + ::Serialize(os, elem); + } +} + template<typename Stream, typename T, typename A, typename V> void Serialize_impl(Stream& os, const std::vector<T, A>& v, const V&) { diff --git a/src/support/cleanse.cpp b/src/support/cleanse.cpp index 17a4a4c2b2..ecb00510f7 100644 --- a/src/support/cleanse.cpp +++ b/src/support/cleanse.cpp @@ -11,33 +11,25 @@ #include <Windows.h> // For SecureZeroMemory. #endif -/* Compilers have a bad habit of removing "superfluous" memset calls that - * are trying to zero memory. For example, when memset()ing a buffer and - * then free()ing it, the compiler might decide that the memset is - * unobservable and thus can be removed. - * - * Previously we used OpenSSL which tried to stop this by a) implementing - * memset in assembly on x86 and b) putting the function in its own file - * for other platforms. - * - * This change removes those tricks in favour of using asm directives to - * scare the compiler away. As best as our compiler folks can tell, this is - * sufficient and will continue to be so. - * - * Adam Langley <agl@google.com> - * Commit: ad1907fe73334d6c696c8539646c21b11178f20f - * BoringSSL (LICENSE: ISC) - */ void memory_cleanse(void *ptr, size_t len) { - std::memset(ptr, 0, len); - - /* As best as we can tell, this is sufficient to break any optimisations that - might try to eliminate "superfluous" memsets. If there's an easy way to - detect memset_s, it would be better to use that. */ #if defined(_MSC_VER) + /* SecureZeroMemory is guaranteed not to be optimized out by MSVC. */ SecureZeroMemory(ptr, len); #else + std::memset(ptr, 0, len); + + /* Memory barrier that scares the compiler away from optimizing out the memset. + * + * Quoting Adam Langley <agl@google.com> in commit ad1907fe73334d6c696c8539646c21b11178f20f + * in BoringSSL (ISC License): + * As best as we can tell, this is sufficient to break any optimisations that + * might try to eliminate "superfluous" memsets. + * This method is used in memzero_explicit() the Linux kernel, too. Its advantage is that it + * is pretty efficient because the compiler can still implement the memset() efficiently, + * just not remove it entirely. See "Dead Store Elimination (Still) Considered Harmful" by + * Yang et al. (USENIX Security 2017) for more background. + */ __asm__ __volatile__("" : : "r"(ptr) : "memory"); #endif } diff --git a/src/support/cleanse.h b/src/support/cleanse.h index 5298214e44..b03520315d 100644 --- a/src/support/cleanse.h +++ b/src/support/cleanse.h @@ -8,7 +8,8 @@ #include <stdlib.h> -// Attempt to overwrite data in the specified memory span. +/** Secure overwrite a buffer (possibly containing secret data) with zero-bytes. The write + * operation will not be optimized out by the compiler. */ void memory_cleanse(void *ptr, size_t len); #endif // BITCOIN_SUPPORT_CLEANSE_H diff --git a/src/test/README.md b/src/test/README.md index 0017e3de26..8901fae7bd 100644 --- a/src/test/README.md +++ b/src/test/README.md @@ -49,7 +49,3 @@ unit tests. The file naming convention is `<source_filename>_tests.cpp` and such files should wrap their tests in a test suite called `<source_filename>_tests`. For an example of this pattern, examine `uint256_tests.cpp`. - -For further reading, I found the following website to be helpful in -explaining how the boost unit test framework works: -[http://www.alittlemadness.com/2009/03/31/c-unit-testing-with-boosttest/](http://archive.is/dRBGf). diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index eeb54b4cf0..da0abd495a 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -533,9 +533,6 @@ BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision) { CAddrManTest addrman; - // Set addrman addr placement to be deterministic. - addrman.MakeDeterministic(); - BOOST_CHECK(addrman.size() == 0); // Empty addrman should return blank addrman info. @@ -568,9 +565,6 @@ BOOST_AUTO_TEST_CASE(addrman_noevict) { CAddrManTest addrman; - // Set addrman addr placement to be deterministic. - addrman.MakeDeterministic(); - // Add twenty two addresses. CNetAddr source = ResolveIP("252.2.2.2"); for (unsigned int i = 1; i < 23; i++) { @@ -627,9 +621,6 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) { CAddrManTest addrman; - // Set addrman addr placement to be deterministic. - addrman.MakeDeterministic(); - BOOST_CHECK(addrman.size() == 0); // Empty addrman should return blank addrman info. diff --git a/src/test/allocator_tests.cpp b/src/test/allocator_tests.cpp index f255691704..e333763f27 100644 --- a/src/test/allocator_tests.cpp +++ b/src/test/allocator_tests.cpp @@ -2,9 +2,9 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <util/memory.h> #include <util/system.h> -#include <support/allocators/secure.h> #include <test/setup_common.h> #include <memory> diff --git a/src/test/arith_uint256_tests.cpp b/src/test/arith_uint256_tests.cpp index 809c627d27..9ac87261b6 100644 --- a/src/test/arith_uint256_tests.cpp +++ b/src/test/arith_uint256_tests.cpp @@ -11,7 +11,6 @@ #include <uint256.h> #include <arith_uint256.h> #include <string> -#include <version.h> #include <test/setup_common.h> BOOST_FIXTURE_TEST_SUITE(arith_uint256_tests, BasicTestingSetup) diff --git a/src/test/bip32_tests.cpp b/src/test/bip32_tests.cpp index 0c0423c0db..662878750e 100644 --- a/src/test/bip32_tests.cpp +++ b/src/test/bip32_tests.cpp @@ -4,9 +4,10 @@ #include <boost/test/unit_test.hpp> +#include <clientversion.h> #include <key.h> #include <key_io.h> -#include <uint256.h> +#include <streams.h> #include <util/system.h> #include <util/strencodings.h> #include <test/setup_common.h> diff --git a/src/test/blockchain_tests.cpp b/src/test/blockchain_tests.cpp index 13afcca375..ca75563ef0 100644 --- a/src/test/blockchain_tests.cpp +++ b/src/test/blockchain_tests.cpp @@ -2,6 +2,7 @@ #include <stdlib.h> +#include <chain.h> #include <rpc/blockchain.h> #include <test/setup_common.h> diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index f57e1a0ebd..5ce8e6feb0 100644 --- a/src/test/blockencodings_tests.cpp +++ b/src/test/blockencodings_tests.cpp @@ -6,7 +6,7 @@ #include <consensus/merkle.h> #include <chainparams.h> #include <pow.h> -#include <random.h> +#include <streams.h> #include <test/setup_common.h> @@ -85,7 +85,7 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest) BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 1); size_t poolSize = pool.size(); - pool.removeRecursive(*block.vtx[2]); + pool.removeRecursive(*block.vtx[2], MemPoolRemovalReason::REPLACED); BOOST_CHECK_EQUAL(pool.size(), poolSize - 1); CBlock block2; diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp index a9db405477..cf87aa9303 100644 --- a/src/test/blockfilter_index_tests.cpp +++ b/src/test/blockfilter_index_tests.cpp @@ -10,6 +10,7 @@ #include <pow.h> #include <test/setup_common.h> #include <script/standard.h> +#include <util/time.h> #include <validation.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/checkqueue_tests.cpp b/src/test/checkqueue_tests.cpp index 408a7fbda4..d796444419 100644 --- a/src/test/checkqueue_tests.cpp +++ b/src/test/checkqueue_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 <util/memory.h> #include <util/system.h> #include <util/time.h> #include <validation.h> @@ -17,8 +18,6 @@ #include <condition_variable> #include <unordered_set> -#include <memory> -#include <random.h> // BasicTestingSetup not sufficient because nScriptCheckThreads is not set // otherwise. diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 2c42596edc..948591196c 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -3,8 +3,10 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <attributes.h> +#include <clientversion.h> #include <coins.h> #include <script/standard.h> +#include <streams.h> #include <test/setup_common.h> #include <uint256.h> #include <undo.h> diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 4e2acca4c3..4ac12bf969 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -4,6 +4,7 @@ #include <crypto/aes.h> #include <crypto/chacha20.h> +#include <crypto/chacha_poly_aead.h> #include <crypto/poly1305.h> #include <crypto/hkdf_sha256_32.h> #include <crypto/hmac_sha256.h> @@ -585,6 +586,131 @@ BOOST_AUTO_TEST_CASE(hkdf_hmac_sha256_l32_tests) "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d"); } +static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aad_length, const std::string& hex_m, const std::string& hex_k1, const std::string& hex_k2, const std::string& hex_aad_keystream, const std::string& hex_encrypted_message, const std::string& hex_encrypted_message_seq_999) +{ + // we need two sequence numbers, one for the payload cipher instance... + uint32_t seqnr_payload = 0; + // ... and one for the AAD (length) cipher instance + uint32_t seqnr_aad = 0; + // we need to keep track of the position in the AAD cipher instance + // keystream since we use the same 64byte output 21 times + // (21 times 3 bytes length < 64) + int aad_pos = 0; + + std::vector<unsigned char> aead_K_1 = ParseHex(hex_k1); + std::vector<unsigned char> aead_K_2 = ParseHex(hex_k2); + std::vector<unsigned char> plaintext_buf = ParseHex(hex_m); + std::vector<unsigned char> expected_aad_keystream = ParseHex(hex_aad_keystream); + std::vector<unsigned char> expected_ciphertext_and_mac = ParseHex(hex_encrypted_message); + std::vector<unsigned char> expected_ciphertext_and_mac_sequence999 = ParseHex(hex_encrypted_message_seq_999); + + std::vector<unsigned char> ciphertext_buf(plaintext_buf.size() + POLY1305_TAGLEN, 0); + std::vector<unsigned char> plaintext_buf_new(plaintext_buf.size(), 0); + std::vector<unsigned char> cmp_ctx_buffer(64); + uint32_t out_len = 0; + + // create the AEAD instance + ChaCha20Poly1305AEAD aead(aead_K_1.data(), aead_K_1.size(), aead_K_2.data(), aead_K_2.size()); + + // create a chacha20 instance to compare against + ChaCha20 cmp_ctx(aead_K_2.data(), 32); + + // encipher + bool res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true); + // make sure the operation succeeded if expected to succeed + BOOST_CHECK_EQUAL(res, must_succeed); + if (!res) return; + + // verify ciphertext & mac against the test vector + BOOST_CHECK_EQUAL(expected_ciphertext_and_mac.size(), ciphertext_buf.size()); + BOOST_CHECK(memcmp(ciphertext_buf.data(), expected_ciphertext_and_mac.data(), ciphertext_buf.size()) == 0); + + // manually construct the AAD keystream + cmp_ctx.SetIV(seqnr_aad); + cmp_ctx.Seek(0); + cmp_ctx.Keystream(cmp_ctx_buffer.data(), 64); + BOOST_CHECK(memcmp(expected_aad_keystream.data(), cmp_ctx_buffer.data(), expected_aad_keystream.size()) == 0); + // crypt the 3 length bytes and compare the length + uint32_t len_cmp = 0; + len_cmp = (ciphertext_buf[0] ^ cmp_ctx_buffer[aad_pos + 0]) | + (ciphertext_buf[1] ^ cmp_ctx_buffer[aad_pos + 1]) << 8 | + (ciphertext_buf[2] ^ cmp_ctx_buffer[aad_pos + 2]) << 16; + BOOST_CHECK_EQUAL(len_cmp, expected_aad_length); + + // encrypt / decrypt 1000 packets + for (size_t i = 0; i < 1000; ++i) { + res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true); + BOOST_CHECK(res); + BOOST_CHECK(aead.GetLength(&out_len, seqnr_aad, aad_pos, ciphertext_buf.data())); + BOOST_CHECK_EQUAL(out_len, expected_aad_length); + res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, plaintext_buf_new.data(), plaintext_buf_new.size(), ciphertext_buf.data(), ciphertext_buf.size(), false); + BOOST_CHECK(res); + + // make sure we repetitive get the same plaintext + BOOST_CHECK(memcmp(plaintext_buf.data(), plaintext_buf_new.data(), plaintext_buf.size()) == 0); + + // compare sequence number 999 against the test vector + if (seqnr_payload == 999) { + BOOST_CHECK(memcmp(ciphertext_buf.data(), expected_ciphertext_and_mac_sequence999.data(), expected_ciphertext_and_mac_sequence999.size()) == 0); + } + // set nonce and block counter, output the keystream + cmp_ctx.SetIV(seqnr_aad); + cmp_ctx.Seek(0); + cmp_ctx.Keystream(cmp_ctx_buffer.data(), 64); + + // crypt the 3 length bytes and compare the length + len_cmp = 0; + len_cmp = (ciphertext_buf[0] ^ cmp_ctx_buffer[aad_pos + 0]) | + (ciphertext_buf[1] ^ cmp_ctx_buffer[aad_pos + 1]) << 8 | + (ciphertext_buf[2] ^ cmp_ctx_buffer[aad_pos + 2]) << 16; + BOOST_CHECK_EQUAL(len_cmp, expected_aad_length); + + // increment the sequence number(s) + // always increment the payload sequence number + // increment the AAD keystream position by its size (3) + // increment the AAD sequence number if we would hit the 64 byte limit + seqnr_payload++; + aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN; + if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) { + aad_pos = 0; + seqnr_aad++; + } + } +} + +BOOST_AUTO_TEST_CASE(chacha20_poly1305_aead_testvector) +{ + /* test chacha20poly1305@bitcoin AEAD */ + + // must fail with no message + TestChaCha20Poly1305AEAD(false, 0, + "", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", "", "", ""); + + TestChaCha20Poly1305AEAD(true, 0, + /* m */ "0000000000000000000000000000000000000000000000000000000000000000", + /* k1 (payload) */ "0000000000000000000000000000000000000000000000000000000000000000", + /* k2 (AAD) */ "0000000000000000000000000000000000000000000000000000000000000000", + /* AAD keystream */ "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586", + /* encrypted message & MAC */ "76b8e09f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32d2fc11829c1b6c1df1f551cd6131ff08", + /* encrypted message & MAC at sequence 999 */ "b0a03d5bd2855d60699e7d3a3133fa47be740fe4e4c1f967555e2d9271f31c3aaa7aa16ec62c5e24f040c08bb20c3598"); + TestChaCha20Poly1305AEAD(true, 1, + "0100000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586", + "77b8e09f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32baf0c85b6dff8602b06cf52a6aefc62e", + "b1a03d5bd2855d60699e7d3a3133fa47be740fe4e4c1f967555e2d9271f31c3a8bd94d54b5ecabbc41ffbb0c90924080"); + TestChaCha20Poly1305AEAD(true, 255, + "ff0000f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78fab78c9", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "c640c1711e3ee904ac35c57ab9791c8a1c408603a90b77a83b54f6c844cb4b06d94e7fc6c800e165acd66147e80ec45a567f6ce66d05ec0cae679dceeb890017", + "3940c1e92da4582ff6f92a776aeb14d014d384eeb30f660dacf70a14a23fd31e91212701334e2ce1acf5199dc84f4d61ddbe6571bca5af874b4c9226c26e650995d157644e1848b96ed6c2102d5489a050e71d29a5a66ece11de5fb5c9558d54da28fe45b0bc4db4e5b88030bfc4a352b4b7068eccf656bae7ad6a35615315fc7c49d4200388d5eca67c2e822e069336c69b40db67e0f3c81209c50f3216a4b89fb3ae1b984b7851a2ec6f68ab12b101ab120e1ea7313bb93b5a0f71185c7fea017ddb92769861c29dba4fbc432280d5dff21b36d1c4c790128b22699950bb18bf74c448cdfe547d8ed4f657d8005fdc0cd7a050c2d46050a44c4376355858981fbe8b184288276e7a93eabc899c4a", + "f039c6689eaeef0456685200feaab9d54bbd9acde4410a3b6f4321296f4a8ca2604b49727d8892c57e005d799b2a38e85e809f20146e08eec75169691c8d4f54a0d51a1e1c7b381e0474eb02f994be9415ef3ffcbd2343f0601e1f3b172a1d494f838824e4df570f8e3b0c04e27966e36c82abd352d07054ef7bd36b84c63f9369afe7ed79b94f953873006b920c3fa251a771de1b63da927058ade119aa898b8c97e42a606b2f6df1e2d957c22f7593c1e2002f4252f4c9ae4bf773499e5cfcfe14dfc1ede26508953f88553bf4a76a802f6a0068d59295b01503fd9a600067624203e880fdf53933b96e1f4d9eb3f4e363dd8165a278ff667a41ee42b9892b077cefff92b93441f7be74cf10e6cd"); +} + BOOST_AUTO_TEST_CASE(countbits_tests) { FastRandomContext ctx; diff --git a/src/test/dbwrapper_tests.cpp b/src/test/dbwrapper_tests.cpp index a518dbaf55..efcadd51fc 100644 --- a/src/test/dbwrapper_tests.cpp +++ b/src/test/dbwrapper_tests.cpp @@ -4,8 +4,8 @@ #include <dbwrapper.h> #include <uint256.h> -#include <random.h> #include <test/setup_common.h> +#include <util/memory.h> #include <memory> diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 3a2844861b..b0a613372f 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -6,12 +6,15 @@ #include <banman.h> #include <chainparams.h> -#include <keystore.h> #include <net.h> #include <net_processing.h> #include <script/sign.h> +#include <script/signingprovider.h> +#include <script/standard.h> #include <serialize.h> +#include <util/memory.h> #include <util/system.h> +#include <util/time.h> #include <validation.h> #include <test/setup_common.h> @@ -148,17 +151,17 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, scheduler, false); const Consensus::Params& consensusParams = Params().GetConsensus(); - constexpr int nMaxOutbound = 8; + constexpr int max_outbound_full_relay = 8; CConnman::Options options; options.nMaxConnections = 125; - options.nMaxOutbound = nMaxOutbound; + options.m_max_outbound_full_relay = max_outbound_full_relay; options.nMaxFeeler = 1; connman->Init(options); std::vector<CNode *> vNodes; // Mock some outbound peers - for (int i=0; i<nMaxOutbound; ++i) { + for (int i=0; i<max_outbound_full_relay; ++i) { AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); } @@ -187,7 +190,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); peerLogic->CheckForStaleTipAndEvictPeers(consensusParams); - for (int i=0; i<nMaxOutbound; ++i) { + for (int i=0; i<max_outbound_full_relay; ++i) { BOOST_CHECK(vNodes[i]->fDisconnect == false); } // Last added node should get marked for eviction @@ -200,10 +203,10 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), GetTime()); peerLogic->CheckForStaleTipAndEvictPeers(consensusParams); - for (int i=0; i<nMaxOutbound-1; ++i) { + for (int i=0; i<max_outbound_full_relay-1; ++i) { BOOST_CHECK(vNodes[i]->fDisconnect == false); } - BOOST_CHECK(vNodes[nMaxOutbound-1]->fDisconnect == true); + BOOST_CHECK(vNodes[max_outbound_full_relay-1]->fDisconnect == true); BOOST_CHECK(vNodes.back()->fDisconnect == false); bool dummy; @@ -367,7 +370,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) { CKey key; key.MakeNewKey(true); - CBasicKeyStore keystore; + FillableSigningProvider keystore; BOOST_CHECK(keystore.AddKey(key)); // 50 orphan transactions: diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index f5bda7d5e6..50ac0bd7b8 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -13,13 +13,15 @@ namespace { -void CheckUnparsable(const std::string& prv, const std::string& pub) +void CheckUnparsable(const std::string& prv, const std::string& pub, const std::string& expected_error) { FlatSigningProvider keys_priv, keys_pub; - auto parse_priv = Parse(prv, keys_priv); - auto parse_pub = Parse(pub, keys_pub); + std::string error; + auto parse_priv = Parse(prv, keys_priv, error); + auto parse_pub = Parse(pub, keys_pub, error); BOOST_CHECK_MESSAGE(!parse_priv, prv); BOOST_CHECK_MESSAGE(!parse_pub, pub); + BOOST_CHECK(error == expected_error); } constexpr int DEFAULT = 0; @@ -40,32 +42,47 @@ bool EqualDescriptor(std::string a, std::string b) return a == b; } -std::string MaybeUseHInsteadOfApostrophy(std::string ret) +std::string UseHInsteadOfApostrophe(const std::string& desc) { - if (InsecureRandBool()) { - while (true) { - auto it = ret.find("'"); - if (it != std::string::npos) { - ret[it] = 'h'; - if (ret.size() > 9 && ret[ret.size() - 9] == '#') ret = ret.substr(0, ret.size() - 9); // Changing apostrophe to h breaks the checksum - } else { - break; - } - } + std::string ret = desc; + while (true) { + auto it = ret.find('\''); + if (it == std::string::npos) break; + ret[it] = 'h'; + } + + // GetDescriptorChecksum returns "" if the checksum exists but is bad. + // Switching apostrophes with 'h' breaks the checksum if it exists - recalculate it and replace the broken one. + if (GetDescriptorChecksum(ret) == "") { + ret = ret.substr(0, desc.size() - 9); + ret += std::string("#") + GetDescriptorChecksum(ret); } return ret; } const std::set<std::vector<uint32_t>> ONLY_EMPTY{{}}; -void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY) +void DoCheck(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY, + bool replace_apostrophe_with_h_in_prv=false, bool replace_apostrophe_with_h_in_pub=false) { FlatSigningProvider keys_priv, keys_pub; std::set<std::vector<uint32_t>> left_paths = paths; + std::string error; + std::unique_ptr<Descriptor> parse_priv; + std::unique_ptr<Descriptor> parse_pub; // Check that parsing succeeds. - auto parse_priv = Parse(MaybeUseHInsteadOfApostrophy(prv), keys_priv); - auto parse_pub = Parse(MaybeUseHInsteadOfApostrophy(pub), keys_pub); + if (replace_apostrophe_with_h_in_prv) { + parse_priv = Parse(UseHInsteadOfApostrophe(prv), keys_priv, error); + } else { + parse_priv = Parse(prv, keys_priv, error); + } + if (replace_apostrophe_with_h_in_pub) { + parse_pub = Parse(UseHInsteadOfApostrophe(pub), keys_pub, error); + } else { + parse_pub = Parse(pub, keys_pub, error); + } + BOOST_CHECK(parse_priv); BOOST_CHECK(parse_pub); @@ -164,6 +181,32 @@ void Check(const std::string& prv, const std::string& pub, int flags, const std: BOOST_CHECK_MESSAGE(left_paths.empty(), "Not all expected key paths found: " + prv); } +void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY) +{ + bool found_apostrophes_in_prv = false; + bool found_apostrophes_in_pub = false; + + // Do not replace apostrophes with 'h' in prv and pub + DoCheck(prv, pub, flags, scripts, paths); + + // Replace apostrophes with 'h' in prv but not in pub, if apostrophes are found in prv + if (prv.find('\'') != std::string::npos) { + found_apostrophes_in_prv = true; + DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */false); + } + + // Replace apostrophes with 'h' in pub but not in prv, if apostrophes are found in pub + if (pub.find('\'') != std::string::npos) { + found_apostrophes_in_pub = true; + DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */false, /*replace_apostrophe_with_h_in_pub = */true); + } + + // Replace apostrophes with 'h' both in prv and in pub, if apostrophes are found in both + if (found_apostrophes_in_prv && found_apostrophes_in_pub) { + DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */true); + } +} + } BOOST_FIXTURE_TEST_SUITE(descriptor_tests, BasicTestingSetup) @@ -176,14 +219,17 @@ BOOST_AUTO_TEST_CASE(descriptor_test) Check("pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}, {{1,0x80000002UL,3,0x80000004UL}}); Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}); Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}); + CheckUnparsable("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY2))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5))", "Pubkey '03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5' is invalid"); // Invalid pubkey + CheckUnparsable("pkh(deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh(deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "Key origin start '[ character expected but not found, got 'd' instead"); // Missing start bracket in key origin + CheckUnparsable("pkh([deadbeef]/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef]/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "Multiple ']' characters found for a single pubkey"); // Multiple end brackets in key origin // Basic single-key uncompressed Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}); Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}}); Check("pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}); - CheckUnparsable("wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)"); // No uncompressed keys in witness - CheckUnparsable("wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))"); // No uncompressed keys in witness - CheckUnparsable("sh(wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "sh(wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))"); // No uncompressed keys in witness + CheckUnparsable("wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Uncompressed keys are not allowed"); // No uncompressed keys in witness + CheckUnparsable("wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "Uncompressed keys are not allowed"); // No uncompressed keys in witness + CheckUnparsable("sh(wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "sh(wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "Uncompressed keys are not allowed"); // No uncompressed keys in witness // Some unconventional single-key constructions Check("sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}}); @@ -200,38 +246,50 @@ BOOST_AUTO_TEST_CASE(descriptor_test) Check("wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}, {{0x8000000DUL, 1, 2, 0}, {0x8000000DUL, 1, 2, 1}, {0x8000000DUL, 1, 2, 2}}); Check("sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}, {{10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}}); Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, {{0}, {1}}); - CheckUnparsable("combo([012345678]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([012345678]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)"); // Too long key fingerprint - CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)"); // BIP 32 path element overflow + CheckUnparsable("combo([012345678]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([012345678]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long key fingerprint + CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)", "Key path value 2147483648 is out of range"); // BIP 32 path element overflow + CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/1aa)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1aa)", "Key path value '1aa' is not a valid uint32"); // Path is not valid uint // Multisig constructions Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}); Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, {{0x8000006FUL,222},{0}}); Check("wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}, {{0xFFFFFFFFUL,0}, {1,2,0}, {1,2,1}, {1,2,2}, {10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}}); Check("sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}}); - CheckUnparsable("sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9))","sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232))"); // P2SH does not fit 16 compressed pubkeys in a redeemscript - CheckUnparsable("wsh(multi(2,[aaaaaaaa][aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // Double key origin descriptor - CheckUnparsable("wsh(multi(2,[aaaagaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaagaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // Non hex fingerprint - CheckUnparsable("wsh(multi(2,[aaaaaaaa],xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa],xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // No public key with origin - CheckUnparsable("wsh(multi(2,[aaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // Too short fingerprint - CheckUnparsable("wsh(multi(2,[aaaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // Too long fingerprint + CheckUnparsable("sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9))","sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232))", "P2SH script is too large, 547 bytes is larger than 520 bytes"); // P2SH does not fit 16 compressed pubkeys in a redeemscript + CheckUnparsable("wsh(multi(2,[aaaaaaaa][aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multiple ']' characters found for a single pubkey"); // Double key origin descriptor + CheckUnparsable("wsh(multi(2,[aaaagaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaagaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Fingerprint 'aaagaaaa' is not hex"); // Non hex fingerprint + CheckUnparsable("wsh(multi(2,[aaaaaaaa],xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa],xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "No key provided"); // No public key with origin + CheckUnparsable("wsh(multi(2,[aaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Fingerprint is not 4 bytes (7 characters instead of 8 characters)"); // Too short fingerprint + CheckUnparsable("wsh(multi(2,[aaaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long fingerprint + CheckUnparsable("multi(a,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(a,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Multi threshold 'a' is not valid"); // Invalid threshold + CheckUnparsable("multi(0,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Multisig threshold cannot be 0, must be at least 1"); // Threshold of 0 + CheckUnparsable("multi(3,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(3,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Multisig threshold cannot be larger than the number of keys; threshold is 3 but only 2 keys specified"); // Threshold larger than number of keys + CheckUnparsable("multi(3,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f)", "multi(3,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8)", "Cannot have 4 pubkeys in bare multisig; only at most 3 pubkeys"); // Threshold larger than number of keys + CheckUnparsable("sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))","sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "Cannot have 17 keys in multisig; must have between 1 and 16 keys, inclusive"); // Cannot have more than 16 keys in a multisig // Check for invalid nesting of structures - CheckUnparsable("sh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)"); // P2SH needs a script, not a key - CheckUnparsable("sh(combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))"); // Old must be top level - CheckUnparsable("wsh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wsh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)"); // P2WSH needs a script, not a key - CheckUnparsable("wsh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))"); // Cannot embed witness inside witness - CheckUnparsable("wsh(sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "wsh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))"); // Cannot embed P2SH inside P2WSH - CheckUnparsable("sh(sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))"); // Cannot embed P2SH inside P2SH - CheckUnparsable("wsh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "wsh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))"); // Cannot embed P2WSH inside P2WSH + CheckUnparsable("sh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "A function is needed within P2SH"); // P2SH needs a script, not a key + CheckUnparsable("sh(combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "Cannot have combo in non-top level"); // Old must be top level + CheckUnparsable("wsh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wsh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "A function is needed within P2WSH"); // P2WSH needs a script, not a key + CheckUnparsable("wsh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "Cannot have wpkh within wsh"); // Cannot embed witness inside witness + CheckUnparsable("wsh(sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "wsh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "Cannot have sh in non-top level"); // Cannot embed P2SH inside P2WSH + CheckUnparsable("sh(sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "Cannot have sh in non-top level"); // Cannot embed P2SH inside P2SH + CheckUnparsable("wsh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "wsh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "Cannot have wsh within wsh"); // Cannot embed P2WSH inside P2WSH // Checksums Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, {{0x8000006FUL,222},{0}}); Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, {{0x8000006FUL,222},{0}}); - CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#"); // Empty checksum - CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfyq", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5tq"); // Too long checksum - CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxf", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5"); // Too short checksum - CheckUnparsable("sh(multi(3,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(3,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t"); // Error in payload - CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggssrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjq09x4t"); // Error in checksum + CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#", "Expected 8 character checksum, not 0 characters"); // Empty checksum + CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfyq", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5tq", "Expected 8 character checksum, not 9 characters"); // Too long checksum + CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxf", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5", "Expected 8 character checksum, not 7 characters"); // Too short checksum + CheckUnparsable("sh(multi(3,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(3,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", "Provided checksum 'tjg09x5t' does not match computed checksum 'd4x0uxyv'"); // Error in payload + CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggssrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjq09x4t", "Provided checksum 'tjq09x4t' does not match computed checksum 'tjg09x5t'"); // Error in checksum + CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))##ggssrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))##tjq09x4t", "Multiple '#' symbols"); // Error in checksum + + // Addr and raw tests + CheckUnparsable("", "addr(asdf)", "Address is not valid"); // Invalid address + CheckUnparsable("", "raw(asdf)", "Raw script is not hex"); // Invalid script + CheckUnparsable("", "raw(Ü)#00000000", "Invalid characters in payload"); // Invalid chars } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/flatfile_tests.cpp b/src/test/flatfile_tests.cpp index ef3946a115..740d805cce 100644 --- a/src/test/flatfile_tests.cpp +++ b/src/test/flatfile_tests.cpp @@ -2,8 +2,11 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <clientversion.h> #include <flatfile.h> +#include <streams.h> #include <test/setup_common.h> +#include <util/system.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/fs_tests.cpp b/src/test/fs_tests.cpp index 10d3bbde55..6d5a6641f0 100644 --- a/src/test/fs_tests.cpp +++ b/src/test/fs_tests.cpp @@ -4,6 +4,7 @@ // #include <fs.h> #include <test/setup_common.h> +#include <util/system.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp index 97d7633715..9364ac4a32 100644 --- a/src/test/fuzz/deserialize.cpp +++ b/src/test/fuzz/deserialize.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 <addrdb.h> #include <addrman.h> #include <blockencodings.h> #include <chain.h> @@ -11,8 +12,6 @@ #include <net.h> #include <primitives/block.h> #include <protocol.h> -#include <pubkey.h> -#include <script/script.h> #include <streams.h> #include <undo.h> #include <version.h> @@ -20,8 +19,6 @@ #include <stdint.h> #include <unistd.h> -#include <algorithm> -#include <memory> #include <vector> #include <test/fuzz/fuzz.h> diff --git a/src/test/fuzz/fuzz.h b/src/test/fuzz/fuzz.h index 8b03a7e46e..4e009d9b54 100644 --- a/src/test/fuzz/fuzz.h +++ b/src/test/fuzz/fuzz.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_TEST_FUZZ_FUZZ_H #define BITCOIN_TEST_FUZZ_FUZZ_H -#include <functional> #include <stdint.h> #include <vector> diff --git a/src/test/fuzz/script_flags.cpp b/src/test/fuzz/script_flags.cpp index 2c0bfa360c..9b90d66755 100644 --- a/src/test/fuzz/script_flags.cpp +++ b/src/test/fuzz/script_flags.cpp @@ -3,7 +3,6 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <script/interpreter.h> -#include <script/script.h> #include <streams.h> #include <version.h> diff --git a/src/test/getarg_tests.cpp b/src/test/getarg_tests.cpp index 8a42344642..77304fe918 100644 --- a/src/test/getarg_tests.cpp +++ b/src/test/getarg_tests.cpp @@ -7,6 +7,7 @@ #include <test/setup_common.h> #include <string> +#include <utility> #include <vector> #include <boost/algorithm/string.hpp> @@ -32,17 +33,18 @@ static void ResetArgs(const std::string& strArg) BOOST_CHECK(gArgs.ParseParameters(vecChar.size(), vecChar.data(), error)); } -static void SetupArgs(const std::vector<std::string>& args) +static void SetupArgs(const std::vector<std::pair<std::string, unsigned int>>& args) { gArgs.ClearArgs(); - for (const std::string& arg : args) { - gArgs.AddArg(arg, "", false, OptionsCategory::OPTIONS); + for (const auto& arg : args) { + gArgs.AddArg(arg.first, "", arg.second, OptionsCategory::OPTIONS); } } BOOST_AUTO_TEST_CASE(boolarg) { - SetupArgs({"-foo"}); + const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_BOOL); + SetupArgs({foo}); ResetArgs("-foo"); BOOST_CHECK(gArgs.GetBoolArg("-foo", false)); BOOST_CHECK(gArgs.GetBoolArg("-foo", true)); @@ -95,7 +97,9 @@ BOOST_AUTO_TEST_CASE(boolarg) BOOST_AUTO_TEST_CASE(stringarg) { - SetupArgs({"-foo", "-bar"}); + const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_STRING); + const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_STRING); + SetupArgs({foo, bar}); ResetArgs(""); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", ""), ""); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", "eleven"), "eleven"); @@ -120,7 +124,9 @@ BOOST_AUTO_TEST_CASE(stringarg) BOOST_AUTO_TEST_CASE(intarg) { - SetupArgs({"-foo", "-bar"}); + const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_INT); + const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_INT); + SetupArgs({foo, bar}); ResetArgs(""); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 11), 11); BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 0), 0); @@ -140,7 +146,9 @@ BOOST_AUTO_TEST_CASE(intarg) BOOST_AUTO_TEST_CASE(doubledash) { - SetupArgs({"-foo", "-bar"}); + const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_ANY); + const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_ANY); + SetupArgs({foo, bar}); ResetArgs("--foo"); BOOST_CHECK_EQUAL(gArgs.GetBoolArg("-foo", false), true); @@ -151,7 +159,9 @@ BOOST_AUTO_TEST_CASE(doubledash) BOOST_AUTO_TEST_CASE(boolargno) { - SetupArgs({"-foo", "-bar"}); + const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_BOOL); + const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_BOOL); + SetupArgs({foo, bar}); ResetArgs("-nofoo"); BOOST_CHECK(!gArgs.GetBoolArg("-foo", true)); BOOST_CHECK(!gArgs.GetBoolArg("-foo", false)); diff --git a/src/test/hash_tests.cpp b/src/test/hash_tests.cpp index 325b7002f2..d91fcb0034 100644 --- a/src/test/hash_tests.cpp +++ b/src/test/hash_tests.cpp @@ -2,13 +2,12 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <clientversion.h> #include <crypto/siphash.h> #include <hash.h> #include <util/strencodings.h> #include <test/setup_common.h> -#include <vector> - #include <boost/test/unit_test.hpp> BOOST_FIXTURE_TEST_SUITE(hash_tests, BasicTestingSetup) diff --git a/src/test/key_properties.cpp b/src/test/key_properties.cpp index 8b508ed7f7..abcfc4547b 100644 --- a/src/test/key_properties.cpp +++ b/src/test/key_properties.cpp @@ -3,13 +3,9 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <key.h> -#include <base58.h> -#include <script/script.h> #include <uint256.h> #include <util/system.h> -#include <util/strencodings.h> #include <test/setup_common.h> -#include <string> #include <vector> #include <boost/test/unit_test.hpp> diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index 1b95105eab..3e99dcaa40 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -5,7 +5,6 @@ #include <key.h> #include <key_io.h> -#include <script/script.h> #include <uint256.h> #include <util/system.h> #include <util/strencodings.h> diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index 0f74b379c0..fe5d31b7d3 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -5,15 +5,17 @@ #include <policy/policy.h> #include <txmempool.h> #include <util/system.h> +#include <util/time.h> #include <test/setup_common.h> #include <boost/test/unit_test.hpp> -#include <list> #include <vector> BOOST_FIXTURE_TEST_SUITE(mempool_tests, TestingSetup) +static constexpr auto REMOVAL_REASON_DUMMY = MemPoolRemovalReason::REPLACED; + BOOST_AUTO_TEST_CASE(MempoolRemoveTest) { // Test CTxMemPool::remove functionality @@ -59,13 +61,13 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest) // Nothing in pool, remove should do nothing: unsigned int poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txParent)); + testPool.removeRecursive(CTransaction(txParent), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize); // Just the parent: testPool.addUnchecked(entry.FromTx(txParent)); poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txParent)); + testPool.removeRecursive(CTransaction(txParent), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 1); // Parent, children, grandchildren: @@ -77,18 +79,18 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest) } // Remove Child[0], GrandChild[0] should be removed: poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txChild[0])); + testPool.removeRecursive(CTransaction(txChild[0]), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 2); // ... make sure grandchild and child are gone: poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txGrandChild[0])); + testPool.removeRecursive(CTransaction(txGrandChild[0]), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize); poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txChild[0])); + testPool.removeRecursive(CTransaction(txChild[0]), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize); // Remove parent, all children/grandchildren should go: poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txParent)); + testPool.removeRecursive(CTransaction(txParent), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 5); BOOST_CHECK_EQUAL(testPool.size(), 0U); @@ -101,7 +103,7 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest) // Now remove the parent, as might happen if a block-re-org occurs but the parent cannot be // put into the mempool (maybe because it is non-standard): poolSize = testPool.size(); - testPool.removeRecursive(CTransaction(txParent)); + testPool.removeRecursive(CTransaction(txParent), REMOVAL_REASON_DUMMY); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 6); BOOST_CHECK_EQUAL(testPool.size(), 0U); } @@ -283,11 +285,11 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) BOOST_CHECK_EQUAL(pool.size(), 10U); // Now try removing tx10 and verify the sort order returns to normal - pool.removeRecursive(pool.mapTx.find(tx10.GetHash())->GetTx()); + pool.removeRecursive(pool.mapTx.find(tx10.GetHash())->GetTx(), REMOVAL_REASON_DUMMY); CheckSort<descendant_score>(pool, snapshotOrder); - pool.removeRecursive(pool.mapTx.find(tx9.GetHash())->GetTx()); - pool.removeRecursive(pool.mapTx.find(tx8.GetHash())->GetTx()); + pool.removeRecursive(pool.mapTx.find(tx9.GetHash())->GetTx(), REMOVAL_REASON_DUMMY); + pool.removeRecursive(pool.mapTx.find(tx8.GetHash())->GetTx(), REMOVAL_REASON_DUMMY); } BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 9a182d7bd3..c9661b730d 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -9,12 +9,12 @@ #include <consensus/tx_verify.h> #include <miner.h> #include <policy/policy.h> -#include <pubkey.h> #include <script/standard.h> #include <txmempool.h> #include <uint256.h> #include <util/strencodings.h> #include <util/system.h> +#include <util/time.h> #include <validation.h> #include <test/setup_common.h> @@ -158,7 +158,7 @@ static void TestPackageSelection(const CChainParams& chainparams, const CScript& // Test that packages above the min relay fee do get included, even if one // of the transactions is below the min relay fee // Remove the low fee transaction and replace with a higher fee transaction - mempool.removeRecursive(CTransaction(tx)); + mempool.removeRecursive(CTransaction(tx), MemPoolRemovalReason::REPLACED); tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee hashLowFeeTx = tx.GetHash(); mempool.addUnchecked(entry.Fee(feeToUse+2).FromTx(tx)); @@ -372,7 +372,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) CBlockIndex* prev = ::ChainActive().Tip(); CBlockIndex* next = new CBlockIndex(); next->phashBlock = new uint256(InsecureRand256()); - pcoinsTip->SetBestBlock(next->GetBlockHash()); + ::ChainstateActive().CoinsTip().SetBestBlock(next->GetBlockHash()); next->pprev = prev; next->nHeight = prev->nHeight + 1; next->BuildSkip(); @@ -384,7 +384,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) CBlockIndex* prev = ::ChainActive().Tip(); CBlockIndex* next = new CBlockIndex(); next->phashBlock = new uint256(InsecureRand256()); - pcoinsTip->SetBestBlock(next->GetBlockHash()); + ::ChainstateActive().CoinsTip().SetBestBlock(next->GetBlockHash()); next->pprev = prev; next->nHeight = prev->nHeight + 1; next->BuildSkip(); @@ -414,7 +414,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) while (::ChainActive().Tip()->nHeight > nHeight) { CBlockIndex* del = ::ChainActive().Tip(); ::ChainActive().SetTip(del->pprev); - pcoinsTip->SetBestBlock(del->pprev->GetBlockHash()); + ::ChainstateActive().CoinsTip().SetBestBlock(del->pprev->GetBlockHash()); delete del->phashBlock; delete del; } diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 10a732d64d..7c60abb93f 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -3,12 +3,13 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <key.h> -#include <keystore.h> #include <policy/policy.h> #include <script/script.h> #include <script/script_error.h> #include <script/interpreter.h> #include <script/sign.h> +#include <script/signingprovider.h> +#include <tinyformat.h> #include <uint256.h> #include <test/setup_common.h> @@ -173,7 +174,7 @@ BOOST_AUTO_TEST_CASE(multisig_IsStandard) BOOST_AUTO_TEST_CASE(multisig_Sign) { // Test SignSignature() (and therefore the version of Solver() that signs transactions) - CBasicKeyStore keystore; + FillableSigningProvider keystore; CKey key[4]; for (int i = 0; i < 4; i++) { diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index d23a4b8fcc..fed65afdbf 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -1,16 +1,19 @@ // Copyright (c) 2012-2019 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 <addrdb.h> #include <addrman.h> +#include <clientversion.h> #include <test/setup_common.h> #include <string> #include <boost/test/unit_test.hpp> -#include <hash.h> #include <serialize.h> #include <streams.h> #include <net.h> #include <netbase.h> #include <chainparams.h> +#include <util/memory.h> #include <util/system.h> #include <memory> diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index 86c0cecbf1..a3d0831624 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <netbase.h> +#include <net_permissions.h> #include <test/setup_common.h> #include <util/strencodings.h> @@ -321,4 +322,82 @@ BOOST_AUTO_TEST_CASE(netbase_parsenetwork) BOOST_CHECK_EQUAL(ParseNetwork(""), NET_UNROUTABLE); } +BOOST_AUTO_TEST_CASE(netpermissions_test) +{ + std::string error; + NetWhitebindPermissions whitebindPermissions; + NetWhitelistPermissions whitelistPermissions; + + // Detect invalid white bind + BOOST_CHECK(!NetWhitebindPermissions::TryParse("", whitebindPermissions, error)); + BOOST_CHECK(error.find("Cannot resolve -whitebind address") != std::string::npos); + BOOST_CHECK(!NetWhitebindPermissions::TryParse("127.0.0.1", whitebindPermissions, error)); + BOOST_CHECK(error.find("Need to specify a port with -whitebind") != std::string::npos); + BOOST_CHECK(!NetWhitebindPermissions::TryParse("", whitebindPermissions, error)); + + // If no permission flags, assume backward compatibility + BOOST_CHECK(NetWhitebindPermissions::TryParse("1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK(error.empty()); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_ISIMPLICIT); + BOOST_CHECK(NetPermissions::HasFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT)); + NetPermissions::ClearFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT); + BOOST_CHECK(!NetPermissions::HasFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_NONE); + NetPermissions::AddFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT); + BOOST_CHECK(NetPermissions::HasFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT)); + + // Can set one permission + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER); + BOOST_CHECK(NetWhitebindPermissions::TryParse("@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_NONE); + + // Happy path, can parse flags + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,forcerelay@1.2.3.4:32", whitebindPermissions, error)); + // forcerelay should also activate the relay permission + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER | PF_FORCERELAY | PF_RELAY); + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,relay,noban@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER | PF_RELAY | PF_NOBAN); + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,forcerelay,noban@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK(NetWhitebindPermissions::TryParse("all@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_ALL); + + // Allow dups + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,relay,noban,noban@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER | PF_RELAY | PF_NOBAN); + + // Allow empty + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,relay,,noban@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER | PF_RELAY | PF_NOBAN); + BOOST_CHECK(NetWhitebindPermissions::TryParse(",@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_NONE); + BOOST_CHECK(NetWhitebindPermissions::TryParse(",,@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_NONE); + + // Detect invalid flag + BOOST_CHECK(!NetWhitebindPermissions::TryParse("bloom,forcerelay,oopsie@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK(error.find("Invalid P2P permission") != std::string::npos); + + // Check whitelist error + BOOST_CHECK(!NetWhitelistPermissions::TryParse("bloom,forcerelay,noban@1.2.3.4:32", whitelistPermissions, error)); + BOOST_CHECK(error.find("Invalid netmask specified in -whitelist") != std::string::npos); + + // Happy path for whitelist parsing + BOOST_CHECK(NetWhitelistPermissions::TryParse("noban@1.2.3.4", whitelistPermissions, error)); + BOOST_CHECK_EQUAL(whitelistPermissions.m_flags, PF_NOBAN); + BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay@1.2.3.4/32", whitelistPermissions, error)); + BOOST_CHECK_EQUAL(whitelistPermissions.m_flags, PF_BLOOMFILTER | PF_FORCERELAY | PF_NOBAN | PF_RELAY); + BOOST_CHECK(error.empty()); + BOOST_CHECK_EQUAL(whitelistPermissions.m_subnet.ToString(), "1.2.3.4/32"); + BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay,mempool@1.2.3.4/32", whitelistPermissions, error)); + + const auto strings = NetPermissions::ToStrings(PF_ALL); + BOOST_CHECK_EQUAL(strings.size(), 5); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "bloomfilter") != strings.end()); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "forcerelay") != strings.end()); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "relay") != strings.end()); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "noban") != strings.end()); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "mempool") != strings.end()); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/policyestimator_tests.cpp b/src/test/policyestimator_tests.cpp index 149094fc00..016a4f471b 100644 --- a/src/test/policyestimator_tests.cpp +++ b/src/test/policyestimator_tests.cpp @@ -7,6 +7,7 @@ #include <txmempool.h> #include <uint256.h> #include <util/system.h> +#include <util/time.h> #include <test/setup_common.h> diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index 653433bfce..1123d4202c 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -5,7 +5,6 @@ #include <chain.h> #include <chainparams.h> #include <pow.h> -#include <random.h> #include <util/system.h> #include <test/setup_common.h> diff --git a/src/test/raii_event_tests.cpp b/src/test/raii_event_tests.cpp index 2b01acf7fa..41ca8029e5 100644 --- a/src/test/raii_event_tests.cpp +++ b/src/test/raii_event_tests.cpp @@ -14,8 +14,6 @@ #include <test/setup_common.h> -#include <vector> - #include <boost/test/unit_test.hpp> static std::map<void*, short> tags; diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 63bfe1d346..5ae0812243 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -9,8 +9,8 @@ #include <core_io.h> #include <init.h> #include <interfaces/chain.h> - #include <test/setup_common.h> +#include <util/time.h> #include <boost/algorithm/string.hpp> #include <boost/test/unit_test.hpp> diff --git a/src/test/script_p2sh_tests.cpp b/src/test/script_p2sh_tests.cpp index 735b67c06e..f451d80984 100644 --- a/src/test/script_p2sh_tests.cpp +++ b/src/test/script_p2sh_tests.cpp @@ -4,13 +4,13 @@ #include <consensus/tx_verify.h> #include <key.h> -#include <keystore.h> #include <validation.h> #include <policy/policy.h> #include <script/script.h> #include <script/script_error.h> #include <policy/settings.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <test/setup_common.h> #include <vector> @@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(sign) // scriptPubKey: HASH160 <hash> EQUAL // Test SignSignature() (and therefore the version of Solver() that signs transactions) - CBasicKeyStore keystore; + FillableSigningProvider keystore; CKey key[4]; for (int i = 0; i < 4; i++) { @@ -151,7 +151,7 @@ BOOST_AUTO_TEST_CASE(set) { LOCK(cs_main); // Test the CScript::Set* methods - CBasicKeyStore keystore; + FillableSigningProvider keystore; CKey key[4]; std::vector<CPubKey> keys; for (int i = 0; i < 4; i++) @@ -265,7 +265,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) LOCK(cs_main); CCoinsView coinsDummy; CCoinsViewCache coins(&coinsDummy); - CBasicKeyStore keystore; + FillableSigningProvider keystore; CKey key[6]; std::vector<CPubKey> keys; for (int i = 0; i < 6; i++) diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp index 195283f89f..412a57dd9d 100644 --- a/src/test/script_standard_tests.cpp +++ b/src/test/script_standard_tests.cpp @@ -3,9 +3,8 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <key.h> -#include <keystore.h> #include <script/script.h> -#include <script/script_error.h> +#include <script/signingprovider.h> #include <script/standard.h> #include <test/setup_common.h> diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 4798909e2f..84a70fe78b 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -6,20 +6,20 @@ #include <core_io.h> #include <key.h> -#include <keystore.h> #include <script/script.h> #include <script/script_error.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <util/system.h> #include <util/strencodings.h> #include <test/setup_common.h> #include <rpc/util.h> +#include <streams.h> #if defined(HAVE_CONSENSUS_LIB) #include <script/bitcoinconsensus.h> #endif -#include <fstream> #include <stdint.h> #include <string> #include <vector> @@ -1199,7 +1199,7 @@ SignatureData CombineSignatures(const CTxOut& txout, const CMutableTransaction& BOOST_AUTO_TEST_CASE(script_combineSigs) { // Test the ProduceSignature's ability to combine signatures function - CBasicKeyStore keystore; + FillableSigningProvider keystore; std::vector<CKey> keys; std::vector<CPubKey> pubkeys; for (int i = 0; i < 3; i++) diff --git a/src/test/scriptnum10.h b/src/test/scriptnum10.h index e763b64275..2c89a18331 100644 --- a/src/test/scriptnum10.h +++ b/src/test/scriptnum10.h @@ -6,7 +6,6 @@ #ifndef BITCOIN_TEST_SCRIPTNUM10_H #define BITCOIN_TEST_SCRIPTNUM10_H -#include <algorithm> #include <limits> #include <stdexcept> #include <stdint.h> diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp index 2fab309aa4..b90be15fba 100644 --- a/src/test/serialize_tests.cpp +++ b/src/test/serialize_tests.cpp @@ -6,6 +6,7 @@ #include <streams.h> #include <hash.h> #include <test/setup_common.h> +#include <util/strencodings.h> #include <stdint.h> @@ -257,6 +258,14 @@ static bool isCanonicalException(const std::ios_base::failure& ex) return strcmp(expectedException.what(), ex.what()) == 0; } +BOOST_AUTO_TEST_CASE(vector_bool) +{ + std::vector<uint8_t> vec1{1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1}; + std::vector<bool> vec2{1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1}; + + BOOST_CHECK(vec1 == std::vector<uint8_t>(vec2.begin(), vec2.end())); + BOOST_CHECK(SerializeHash(vec1) == SerializeHash(vec2)); +} BOOST_AUTO_TEST_CASE(noncanonical) { diff --git a/src/test/setup_common.cpp b/src/test/setup_common.cpp index aaf69b9575..bbdf1ef830 100644 --- a/src/test/setup_common.cpp +++ b/src/test/setup_common.cpp @@ -12,15 +12,23 @@ #include <crypto/sha256.h> #include <init.h> #include <miner.h> -#include <net_processing.h> +#include <net.h> #include <noui.h> #include <pow.h> #include <rpc/register.h> #include <rpc/server.h> #include <script/sigcache.h> #include <streams.h> +#include <txdb.h> +#include <util/memory.h> +#include <util/strencodings.h> +#include <util/time.h> +#include <util/translation.h> #include <util/validation.h> #include <validation.h> +#include <validationinterface.h> + +#include <functional> const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; @@ -68,7 +76,6 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha const CChainParams& chainparams = Params(); // Ideally we'd move all the RPC tests to the functional testing framework // instead of unit tests, but for now we need these here. - RegisterAllCoreRPCCommands(tableRPC); // We have to run a scheduler thread to prevent ActivateBestChain @@ -78,8 +85,12 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha mempool.setSanityCheck(1.0); pblocktree.reset(new CBlockTreeDB(1 << 20, true)); - pcoinsdbview.reset(new CCoinsViewDB(1 << 23, true)); - pcoinsTip.reset(new CCoinsViewCache(pcoinsdbview.get())); + g_chainstate = MakeUnique<CChainState>(); + ::ChainstateActive().InitCoinsDB( + /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); + assert(!::ChainstateActive().CanFlushToDisk()); + ::ChainstateActive().InitCoinsCache(); + assert(::ChainstateActive().CanFlushToDisk()); if (!LoadGenesisBlock(chainparams)) { throw std::runtime_error("LoadGenesisBlock failed."); } @@ -106,8 +117,7 @@ TestingSetup::~TestingSetup() g_connman.reset(); g_banman.reset(); UnloadBlockIndex(); - pcoinsTip.reset(); - pcoinsdbview.reset(); + g_chainstate.reset(); pblocktree.reset(); } @@ -115,7 +125,7 @@ TestChain100Setup::TestChain100Setup() : TestingSetup(CBaseChainParams::REGTEST) { // CreateAndProcessBlock() does not support building SegWit blocks, so don't activate in these tests. // TODO: fix the code to support SegWit blocks. - gArgs.ForceSetArg("-vbparams", strprintf("segwit:0:%d", (int64_t)Consensus::BIP9Deployment::NO_TIMEOUT)); + gArgs.ForceSetArg("-segwitheight", "432"); SelectParams(CBaseChainParams::REGTEST); // Generate a 100-block chain: diff --git a/src/test/setup_common.h b/src/test/setup_common.h index b1bb5e6b25..6c9494898c 100644 --- a/src/test/setup_common.h +++ b/src/test/setup_common.h @@ -11,10 +11,8 @@ #include <pubkey.h> #include <random.h> #include <scheduler.h> -#include <txdb.h> #include <txmempool.h> -#include <memory> #include <type_traits> #include <boost/thread.hpp> diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index 5c12ec13d2..a32f2cda92 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -2,8 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <consensus/consensus.h> #include <consensus/tx_verify.h> -#include <consensus/validation.h> #include <pubkey.h> #include <key.h> #include <script/script.h> diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index 4e37199c63..b812cef801 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -3,7 +3,6 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <streams.h> -#include <support/allocators/zeroafterfree.h> #include <test/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/timedata_tests.cpp b/src/test/timedata_tests.cpp index b4c0e6a0f4..7b00222ab7 100644 --- a/src/test/timedata_tests.cpp +++ b/src/test/timedata_tests.cpp @@ -2,8 +2,14 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // -#include <timedata.h> + +#include <netaddress.h> +#include <noui.h> #include <test/setup_common.h> +#include <timedata.h> +#include <warnings.h> + +#include <string> #include <boost/test/unit_test.hpp> @@ -34,4 +40,61 @@ BOOST_AUTO_TEST_CASE(util_MedianFilter) BOOST_CHECK_EQUAL(filter.median(), 7); } +static void MultiAddTimeData(int n, int64_t offset) +{ + static int cnt = 0; + for (int i = 0; i < n; ++i) { + CNetAddr addr; + addr.SetInternal(std::to_string(++cnt)); + AddTimeData(addr, offset); + } +} + + +BOOST_AUTO_TEST_CASE(addtimedata) +{ + BOOST_CHECK_EQUAL(GetTimeOffset(), 0); + + //Part 1: Add large offsets to test a warning message that our clock may be wrong. + MultiAddTimeData(3, DEFAULT_MAX_TIME_ADJUSTMENT + 1); + // Filter size is 1 + 3 = 4: It is always initialized with a single element (offset 0) + + noui_suppress(); + MultiAddTimeData(1, DEFAULT_MAX_TIME_ADJUSTMENT + 1); //filter size 5 + noui_reconnect(); + + BOOST_CHECK(GetWarnings("gui").find("clock is wrong") != std::string::npos); + + // nTimeOffset is not changed if the median of offsets exceeds DEFAULT_MAX_TIME_ADJUSTMENT + BOOST_CHECK_EQUAL(GetTimeOffset(), 0); + + // Part 2: Test positive and negative medians by adding more offsets + MultiAddTimeData(4, 100); // filter size 9 + BOOST_CHECK_EQUAL(GetTimeOffset(), 100); + MultiAddTimeData(10, -100); //filter size 19 + BOOST_CHECK_EQUAL(GetTimeOffset(), -100); + + // Part 3: Test behaviour when filter has reached maximum number of offsets + const int MAX_SAMPLES = 200; + int nfill = (MAX_SAMPLES - 3 - 19) / 2; //89 + MultiAddTimeData(nfill, 100); + MultiAddTimeData(nfill, -100); //filter size MAX_SAMPLES - 3 + BOOST_CHECK_EQUAL(GetTimeOffset(), -100); + + MultiAddTimeData(2, 100); + //filter size MAX_SAMPLES -1, median is the initial 0 offset + //since we added same number of positive/negative offsets + + BOOST_CHECK_EQUAL(GetTimeOffset(), 0); + + // After the number of offsets has reached MAX_SAMPLES -1 (=199), nTimeOffset will never change + // because it is only updated when the number of elements in the filter becomes odd. It was decided + // not to fix this because it prevents possible attacks. See the comment in AddTimeData() or issue #4521 + // for a more detailed explanation. + MultiAddTimeData(2, 100); // filter median is 100 now, but nTimeOffset will not change + BOOST_CHECK_EQUAL(GetTimeOffset(), 0); + + // We want this test to end with nTimeOffset==0, otherwise subsequent tests of the suite will fail. +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index f5ff18c055..34192c6b6a 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -12,14 +12,15 @@ #include <consensus/validation.h> #include <core_io.h> #include <key.h> -#include <keystore.h> #include <validation.h> #include <policy/policy.h> #include <policy/settings.h> #include <script/script.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <script/script_error.h> #include <script/standard.h> +#include <streams.h> #include <util/strencodings.h> #include <map> @@ -288,7 +289,7 @@ BOOST_AUTO_TEST_CASE(basic_transaction_tests) // paid to a TX_PUBKEYHASH. // static std::vector<CMutableTransaction> -SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) +SetupDummyInputs(FillableSigningProvider& keystoreRet, CCoinsViewCache& coinsRet) { std::vector<CMutableTransaction> dummyTransactions; dummyTransactions.resize(2); @@ -321,7 +322,7 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) BOOST_AUTO_TEST_CASE(test_Get) { - CBasicKeyStore keystore; + FillableSigningProvider keystore; CCoinsView coinsDummy; CCoinsViewCache coins(&coinsDummy); std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins); @@ -345,7 +346,7 @@ BOOST_AUTO_TEST_CASE(test_Get) BOOST_CHECK_EQUAL(coins.GetValueIn(CTransaction(t1)), (50+21+22)*CENT); } -static void CreateCreditAndSpend(const CKeyStore& keystore, const CScript& outscript, CTransactionRef& output, CMutableTransaction& input, bool success = true) +static void CreateCreditAndSpend(const FillableSigningProvider& keystore, const CScript& outscript, CTransactionRef& output, CMutableTransaction& input, bool success = true) { CMutableTransaction outputm; outputm.nVersion = 1; @@ -422,7 +423,7 @@ BOOST_AUTO_TEST_CASE(test_big_witness_transaction) CKey key; key.MakeNewKey(true); // Need to use compressed keys in segwit or the signing will fail - CBasicKeyStore keystore; + FillableSigningProvider keystore; BOOST_CHECK(keystore.AddKeyPubKey(key, key.GetPubKey())); CKeyID hash = key.GetPubKey().GetID(); CScript scriptPubKey = CScript() << OP_0 << std::vector<unsigned char>(hash.begin(), hash.end()); @@ -506,7 +507,7 @@ SignatureData CombineSignatures(const CMutableTransaction& input1, const CMutabl BOOST_AUTO_TEST_CASE(test_witness) { - CBasicKeyStore keystore, keystore2; + FillableSigningProvider keystore, keystore2; CKey key1, key2, key3, key1L, key2L; CPubKey pubkey1, pubkey2, pubkey3, pubkey1L, pubkey2L; key1.MakeNewKey(true); @@ -681,7 +682,7 @@ BOOST_AUTO_TEST_CASE(test_witness) BOOST_AUTO_TEST_CASE(test_IsStandard) { LOCK(cs_main); - CBasicKeyStore keystore; + FillableSigningProvider keystore; CCoinsView coinsDummy; CCoinsViewCache coins(&coinsDummy); std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins); diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp index 26ae7be202..2356e0ccdc 100644 --- a/src/test/txvalidation_tests.cpp +++ b/src/test/txvalidation_tests.cpp @@ -3,8 +3,6 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <validation.h> -#include <txmempool.h> -#include <amount.h> #include <consensus/validation.h> #include <primitives/transaction.h> #include <script/script.h> diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index 352ce0295b..193858cca9 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -5,19 +5,15 @@ #include <consensus/validation.h> #include <key.h> #include <validation.h> -#include <miner.h> -#include <pubkey.h> #include <txmempool.h> -#include <random.h> #include <script/standard.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <test/setup_common.h> -#include <util/time.h> -#include <keystore.h> #include <boost/test/unit_test.hpp> -bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks); +bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks); BOOST_AUTO_TEST_SUITE(tx_validationcache_tests) @@ -101,7 +97,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) BOOST_CHECK_EQUAL(mempool.size(), 0U); } -// Run CheckInputs (using pcoinsTip) on the given transaction, for all script +// Run CheckInputs (using CoinsTip()) on the given transaction, for all script // flags. Test that CheckInputs passes for all flags that don't overlap with // the failing_flags argument, but otherwise fails. // CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY (and future NOP codes that may @@ -129,7 +125,7 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail // WITNESS requires P2SH test_flags |= SCRIPT_VERIFY_P2SH; } - bool ret = CheckInputs(tx, state, pcoinsTip.get(), true, test_flags, true, add_to_cache, txdata, nullptr); + bool ret = CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, nullptr); // CheckInputs should succeed iff test_flags doesn't intersect with // failing_flags bool expected_return_value = !(test_flags & failing_flags); @@ -139,13 +135,13 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail if (ret && add_to_cache) { // Check that we get a cache hit if the tx was valid std::vector<CScriptCheck> scriptchecks; - BOOST_CHECK(CheckInputs(tx, state, pcoinsTip.get(), true, test_flags, true, add_to_cache, txdata, &scriptchecks)); + BOOST_CHECK(CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, &scriptchecks)); BOOST_CHECK(scriptchecks.empty()); } else { // Check that we get script executions to check, if the transaction // was invalid, or we didn't add to cache. std::vector<CScriptCheck> scriptchecks; - BOOST_CHECK(CheckInputs(tx, state, pcoinsTip.get(), true, test_flags, true, add_to_cache, txdata, &scriptchecks)); + BOOST_CHECK(CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, &scriptchecks)); BOOST_CHECK_EQUAL(scriptchecks.size(), tx.vin.size()); } } @@ -165,7 +161,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) CScript p2pkh_scriptPubKey = GetScriptForDestination(PKHash(coinbaseKey.GetPubKey())); CScript p2wpkh_scriptPubKey = GetScriptForWitness(p2pkh_scriptPubKey); - CBasicKeyStore keystore; + FillableSigningProvider keystore; BOOST_CHECK(keystore.AddKey(coinbaseKey)); BOOST_CHECK(keystore.AddCScript(p2pk_scriptPubKey)); @@ -208,13 +204,13 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) CValidationState state; PrecomputedTransactionData ptd_spend_tx(spend_tx); - BOOST_CHECK(!CheckInputs(CTransaction(spend_tx), state, pcoinsTip.get(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr)); + BOOST_CHECK(!CheckInputs(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr)); // If we call again asking for scriptchecks (as happens in // ConnectBlock), we should add a script check object for this -- we're // not caching invalidity (if that changes, delete this test case). std::vector<CScriptCheck> scriptchecks; - BOOST_CHECK(CheckInputs(CTransaction(spend_tx), state, pcoinsTip.get(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, &scriptchecks)); + BOOST_CHECK(CheckInputs(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, &scriptchecks)); BOOST_CHECK_EQUAL(scriptchecks.size(), 1U); // Test that CheckInputs returns true iff DERSIG-enforcing flags are @@ -231,7 +227,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) block = CreateAndProcessBlock({spend_tx}, p2pk_scriptPubKey); LOCK(cs_main); BOOST_CHECK(::ChainActive().Tip()->GetBlockHash() == block.GetHash()); - BOOST_CHECK(pcoinsTip->GetBestBlock() == block.GetHash()); + BOOST_CHECK(::ChainstateActive().CoinsTip().GetBestBlock() == block.GetHash()); // Test P2SH: construct a transaction that is valid without P2SH, and // then test validity with P2SH. @@ -276,7 +272,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100; CValidationState state; PrecomputedTransactionData txdata(invalid_with_cltv_tx); - BOOST_CHECK(CheckInputs(CTransaction(invalid_with_cltv_tx), state, pcoinsTip.get(), true, SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr)); + BOOST_CHECK(CheckInputs(CTransaction(invalid_with_cltv_tx), state, ::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr)); } // TEST CHECKSEQUENCEVERIFY @@ -304,7 +300,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100; CValidationState state; PrecomputedTransactionData txdata(invalid_with_csv_tx); - BOOST_CHECK(CheckInputs(CTransaction(invalid_with_csv_tx), state, pcoinsTip.get(), true, SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr)); + BOOST_CHECK(CheckInputs(CTransaction(invalid_with_csv_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr)); } // TODO: add tests for remaining script flags @@ -366,12 +362,12 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) CValidationState state; PrecomputedTransactionData txdata(tx); // This transaction is now invalid under segwit, because of the second input. - BOOST_CHECK(!CheckInputs(CTransaction(tx), state, pcoinsTip.get(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr)); + BOOST_CHECK(!CheckInputs(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr)); std::vector<CScriptCheck> scriptchecks; // Make sure this transaction was not cached (ie because the first // input was valid) - BOOST_CHECK(CheckInputs(CTransaction(tx), state, pcoinsTip.get(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, &scriptchecks)); + BOOST_CHECK(CheckInputs(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, &scriptchecks)); // Should get 2 script checks back -- caching is on a whole-transaction basis. BOOST_CHECK_EQUAL(scriptchecks.size(), 2U); } diff --git a/src/test/uint256_tests.cpp b/src/test/uint256_tests.cpp index c1749fb856..33a118c2bb 100644 --- a/src/test/uint256_tests.cpp +++ b/src/test/uint256_tests.cpp @@ -1,19 +1,17 @@ // Copyright (c) 2011-2019 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 <arith_uint256.h> +#include <streams.h> #include <uint256.h> #include <version.h> #include <test/setup_common.h> #include <boost/test/unit_test.hpp> -#include <stdint.h> #include <sstream> #include <iomanip> -#include <limits> -#include <cmath> #include <string> -#include <stdio.h> BOOST_FIXTURE_TEST_SUITE(uint256_tests, BasicTestingSetup) diff --git a/src/test/util.cpp b/src/test/util.cpp index bc09d00b7a..b7bb6deeaa 100644 --- a/src/test/util.cpp +++ b/src/test/util.cpp @@ -17,22 +17,15 @@ #include <wallet/wallet.h> #endif -#include <boost/thread.hpp> - const std::string ADDRESS_BCRT1_UNSPENDABLE = "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj"; #ifdef ENABLE_WALLET std::string getnewaddress(CWallet& w) { constexpr auto output_type = OutputType::BECH32; - - CPubKey new_key; - if (!w.GetKeyFromPool(new_key)) assert(false); - - w.LearnRelatedScripts(new_key, output_type); - const auto dest = GetDestinationForKey(new_key, output_type); - - w.SetAddressBook(dest, /* label */ "", "receive"); + CTxDestination dest; + std::string error; + if (!w.GetNewDestination(output_type, "", dest, error)) assert(false); return EncodeDestination(dest); } diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index d1fcb345b4..65cb956fbe 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -5,14 +5,17 @@ #include <util/system.h> #include <clientversion.h> -#include <primitives/transaction.h> #include <sync.h> +#include <test/setup_common.h> #include <test/util.h> -#include <util/strencodings.h> #include <util/moneystr.h> -#include <test/setup_common.h> +#include <util/strencodings.h> +#include <util/string.h> +#include <util/time.h> #include <stdint.h> +#include <thread> +#include <utility> #include <vector> #ifndef WIN32 #include <signal.h> @@ -121,6 +124,19 @@ BOOST_AUTO_TEST_CASE(util_HexStr) ); } +BOOST_AUTO_TEST_CASE(util_Join) +{ + // Normal version + BOOST_CHECK_EQUAL(Join({}, ", "), ""); + BOOST_CHECK_EQUAL(Join({"foo"}, ", "), "foo"); + BOOST_CHECK_EQUAL(Join({"foo", "bar"}, ", "), "foo, bar"); + + // Version with unary operator + const auto op_upper = [](const std::string& s) { return ToUpper(s); }; + BOOST_CHECK_EQUAL(Join<std::string>({}, ", ", op_upper), ""); + BOOST_CHECK_EQUAL(Join<std::string>({"foo"}, ", ", op_upper), "FOO"); + BOOST_CHECK_EQUAL(Join<std::string>({"foo", "bar"}, ", ", op_upper), "FOO, BAR"); +} BOOST_AUTO_TEST_CASE(util_FormatISO8601DateTime) { @@ -153,10 +169,10 @@ struct TestArgsManager : public ArgsManager LOCK(cs_args); m_network_only_args.insert(arg); } - void SetupArgs(int argv, const char* args[]) + void SetupArgs(const std::vector<std::pair<std::string, unsigned int>>& args) { - for (int i = 0; i < argv; ++i) { - AddArg(args[i], "", false, OptionsCategory::OPTIONS); + for (const auto& arg : args) { + AddArg(arg.first, "", arg.second, OptionsCategory::OPTIONS); } } using ArgsManager::ReadConfigStream; @@ -167,11 +183,15 @@ struct TestArgsManager : public ArgsManager BOOST_AUTO_TEST_CASE(util_ParseParameters) { TestArgsManager testArgs; - const char* avail_args[] = {"-a", "-b", "-ccc", "-d"}; + const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY); + const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY); + const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_ANY); + const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY); + const char *argv_test[] = {"-ignored", "-a", "-b", "-ccc=argument", "-ccc=multiple", "f", "-d=e"}; std::string error; - testArgs.SetupArgs(4, avail_args); + testArgs.SetupArgs({a, b, ccc, d}); BOOST_CHECK(testArgs.ParseParameters(0, (char**)argv_test, error)); BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty()); @@ -199,11 +219,17 @@ BOOST_AUTO_TEST_CASE(util_ParseParameters) BOOST_AUTO_TEST_CASE(util_GetBoolArg) { TestArgsManager testArgs; - const char* avail_args[] = {"-a", "-b", "-c", "-d", "-e", "-f"}; + const auto a = std::make_pair("-a", ArgsManager::ALLOW_BOOL); + const auto b = std::make_pair("-b", ArgsManager::ALLOW_BOOL); + const auto c = std::make_pair("-c", ArgsManager::ALLOW_BOOL); + const auto d = std::make_pair("-d", ArgsManager::ALLOW_BOOL); + const auto e = std::make_pair("-e", ArgsManager::ALLOW_BOOL); + const auto f = std::make_pair("-f", ArgsManager::ALLOW_BOOL); + const char *argv_test[] = { "ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"}; std::string error; - testArgs.SetupArgs(6, avail_args); + testArgs.SetupArgs({a, b, c, d, e, f}); BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error)); // Each letter should be set. @@ -236,9 +262,10 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases) TestArgsManager testArgs; // Params test - const char* avail_args[] = {"-foo", "-bar"}; + const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_BOOL); + const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_BOOL); const char *argv_test[] = {"ignored", "-nofoo", "-foo", "-nobar=0"}; - testArgs.SetupArgs(2, avail_args); + testArgs.SetupArgs({foo, bar}); std::string error; BOOST_CHECK(testArgs.ParseParameters(4, (char**)argv_test, error)); @@ -307,8 +334,17 @@ BOOST_AUTO_TEST_CASE(util_ReadConfigStream) "iii=2\n"; TestArgsManager test_args; - const char* avail_args[] = {"-a", "-b", "-ccc", "-d", "-e", "-fff", "-ggg", "-h", "-i", "-iii"}; - test_args.SetupArgs(10, avail_args); + const auto a = std::make_pair("-a", ArgsManager::ALLOW_BOOL); + const auto b = std::make_pair("-b", ArgsManager::ALLOW_BOOL); + const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_STRING); + const auto d = std::make_pair("-d", ArgsManager::ALLOW_STRING); + const auto e = std::make_pair("-e", ArgsManager::ALLOW_ANY); + const auto fff = std::make_pair("-fff", ArgsManager::ALLOW_BOOL); + const auto ggg = std::make_pair("-ggg", ArgsManager::ALLOW_BOOL); + const auto h = std::make_pair("-h", ArgsManager::ALLOW_BOOL); + const auto i = std::make_pair("-i", ArgsManager::ALLOW_BOOL); + const auto iii = std::make_pair("-iii", ArgsManager::ALLOW_INT); + test_args.SetupArgs({a, b, ccc, d, e, fff, ggg, h, i, iii}); test_args.ReadConfigString(str_config); // expectation: a, b, ccc, d, fff, ggg, h, i end up in map @@ -506,8 +542,9 @@ BOOST_AUTO_TEST_CASE(util_GetArg) BOOST_AUTO_TEST_CASE(util_GetChainName) { TestArgsManager test_args; - const char* avail_args[] = {"-testnet", "-regtest"}; - test_args.SetupArgs(2, avail_args); + const auto testnet = std::make_pair("-testnet", ArgsManager::ALLOW_BOOL); + const auto regtest = std::make_pair("-regtest", ArgsManager::ALLOW_BOOL); + test_args.SetupArgs({testnet, regtest}); const char* argv_testnet[] = {"cmd", "-testnet"}; const char* argv_regtest[] = {"cmd", "-regtest"}; @@ -681,7 +718,7 @@ BOOST_FIXTURE_TEST_CASE(util_ArgsMerge, ArgsMergeTestingSetup) const std::string& name = net_specific ? "wallet" : "server"; const std::string key = "-" + name; - parser.AddArg(key, name, false, OptionsCategory::OPTIONS); + parser.AddArg(key, name, ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); if (net_specific) parser.SetNetworkOnlyArg(key); auto args = GetValues(arg_actions, section, name, "a"); @@ -808,8 +845,8 @@ BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup) ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions) { TestArgsManager parser; LOCK(parser.cs_args); - parser.AddArg("-regtest", "regtest", false, OptionsCategory::OPTIONS); - parser.AddArg("-testnet", "testnet", false, OptionsCategory::OPTIONS); + parser.AddArg("-regtest", "regtest", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + parser.AddArg("-testnet", "testnet", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); auto arg = [](Action action) { return action == ENABLE_TEST ? "-testnet=1" : action == DISABLE_TEST ? "-testnet=0" : @@ -1509,17 +1546,9 @@ BOOST_AUTO_TEST_CASE(test_ToLower) BOOST_CHECK_EQUAL(ToLower(0), 0); BOOST_CHECK_EQUAL(ToLower('\xff'), '\xff'); - std::string testVector; - Downcase(testVector); - BOOST_CHECK_EQUAL(testVector, ""); - - testVector = "#HODL"; - Downcase(testVector); - BOOST_CHECK_EQUAL(testVector, "#hodl"); - - testVector = "\x00\xfe\xff"; - Downcase(testVector); - BOOST_CHECK_EQUAL(testVector, "\x00\xfe\xff"); + BOOST_CHECK_EQUAL(ToLower(""), ""); + BOOST_CHECK_EQUAL(ToLower("#HODL"), "#hodl"); + BOOST_CHECK_EQUAL(ToLower("\x00\xfe\xff"), "\x00\xfe\xff"); } BOOST_AUTO_TEST_CASE(test_ToUpper) @@ -1530,6 +1559,10 @@ BOOST_AUTO_TEST_CASE(test_ToUpper) BOOST_CHECK_EQUAL(ToUpper('{'), '{'); BOOST_CHECK_EQUAL(ToUpper(0), 0); BOOST_CHECK_EQUAL(ToUpper('\xff'), '\xff'); + + BOOST_CHECK_EQUAL(ToUpper(""), ""); + BOOST_CHECK_EQUAL(ToUpper("#hodl"), "#HODL"); + BOOST_CHECK_EQUAL(ToUpper("\x00\xfe\xff"), "\x00\xfe\xff"); } BOOST_AUTO_TEST_CASE(test_Capitalize) diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 5dee034b20..b3368d44b6 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -10,14 +10,20 @@ #include <miner.h> #include <pow.h> #include <random.h> +#include <script/standard.h> #include <test/setup_common.h> +#include <util/time.h> #include <validation.h> #include <validationinterface.h> +#include <thread> + struct RegtestingSetup : public TestingSetup { RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {} }; +static const std::vector<unsigned char> V_OP_TRUE{OP_TRUE}; + BOOST_FIXTURE_TEST_SUITE(validation_block_tests, RegtestingSetup) struct TestSubscriber : public CValidationInterface { @@ -59,8 +65,21 @@ std::shared_ptr<CBlock> Block(const uint256& prev_hash) pblock->hashPrevBlock = prev_hash; pblock->nTime = ++time; + pubKey.clear(); + { + WitnessV0ScriptHash witness_program; + CSHA256().Write(&V_OP_TRUE[0], V_OP_TRUE.size()).Finalize(witness_program.begin()); + pubKey << OP_0 << ToByteVector(witness_program); + } + + // Make the coinbase transaction with two outputs: + // One zero-value one that has a unique pubkey to make sure that blocks at the same height can have a different hash + // Another one that has the coinbase reward in a P2WSH with OP_TRUE as witness program to make it easy to spend CMutableTransaction txCoinbase(*pblock->vtx[0]); - txCoinbase.vout.resize(1); + txCoinbase.vout.resize(2); + txCoinbase.vout[1].scriptPubKey = pubKey; + txCoinbase.vout[1].nValue = txCoinbase.vout[0].nValue; + txCoinbase.vout[0].nValue = 0; txCoinbase.vin[0].scriptWitness.SetNull(); pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); @@ -69,6 +88,9 @@ std::shared_ptr<CBlock> Block(const uint256& prev_hash) std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock) { + LOCK(cs_main); // For LookupBlockIndex + GenerateCoinbaseCommitment(*pblock, LookupBlockIndex(pblock->hashPrevBlock), Params().GetConsensus()); + pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) { @@ -79,13 +101,13 @@ std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock) } // construct a valid block -const std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash) +std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash) { return FinalizeBlock(Block(prev_hash)); } // construct an invalid block (but with a valid header) -const std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash) +std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash) { auto pblock = Block(prev_hash); @@ -185,4 +207,131 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) BOOST_CHECK_EQUAL(sub.m_expected_tip, ::ChainActive().Tip()->GetBlockHash()); } +/** + * Test that mempool updates happen atomically with reorgs. + * + * This prevents RPC clients, among others, from retrieving immediately-out-of-date mempool data + * during large reorgs. + * + * The test verifies this by creating a chain of `num_txs` blocks, matures their coinbases, and then + * submits txns spending from their coinbase to the mempool. A fork chain is then processed, + * invalidating the txns and evicting them from the mempool. + * + * We verify that the mempool updates atomically by polling it continuously + * from another thread during the reorg and checking that its size only changes + * once. The size changing exactly once indicates that the polling thread's + * view of the mempool is either consistent with the chain state before reorg, + * or consistent with the chain state after the reorg, and not just consistent + * with some intermediate state during the reorg. + */ +BOOST_AUTO_TEST_CASE(mempool_locks_reorg) +{ + bool ignored; + auto ProcessBlock = [&ignored](std::shared_ptr<const CBlock> block) -> bool { + return ProcessNewBlock(Params(), block, /* fForceProcessing */ true, /* fNewBlock */ &ignored); + }; + + // Process all mined blocks + BOOST_REQUIRE(ProcessBlock(std::make_shared<CBlock>(Params().GenesisBlock()))); + auto last_mined = GoodBlock(Params().GenesisBlock().GetHash()); + BOOST_REQUIRE(ProcessBlock(last_mined)); + + // Run the test multiple times + for (int test_runs = 3; test_runs > 0; --test_runs) { + BOOST_CHECK_EQUAL(last_mined->GetHash(), ::ChainActive().Tip()->GetBlockHash()); + + // Later on split from here + const uint256 split_hash{last_mined->hashPrevBlock}; + + // Create a bunch of transactions to spend the miner rewards of the + // most recent blocks + std::vector<CTransactionRef> txs; + for (int num_txs = 22; num_txs > 0; --num_txs) { + CMutableTransaction mtx; + mtx.vin.push_back(CTxIn{COutPoint{last_mined->vtx[0]->GetHash(), 1}, CScript{}}); + mtx.vin[0].scriptWitness.stack.push_back(V_OP_TRUE); + mtx.vout.push_back(last_mined->vtx[0]->vout[1]); + mtx.vout[0].nValue -= 1000; + txs.push_back(MakeTransactionRef(mtx)); + + last_mined = GoodBlock(last_mined->GetHash()); + BOOST_REQUIRE(ProcessBlock(last_mined)); + } + + // Mature the inputs of the txs + for (int j = COINBASE_MATURITY; j > 0; --j) { + last_mined = GoodBlock(last_mined->GetHash()); + BOOST_REQUIRE(ProcessBlock(last_mined)); + } + + // Mine a reorg (and hold it back) before adding the txs to the mempool + const uint256 tip_init{last_mined->GetHash()}; + + std::vector<std::shared_ptr<const CBlock>> reorg; + last_mined = GoodBlock(split_hash); + reorg.push_back(last_mined); + for (size_t j = COINBASE_MATURITY + txs.size() + 1; j > 0; --j) { + last_mined = GoodBlock(last_mined->GetHash()); + reorg.push_back(last_mined); + } + + // Add the txs to the tx pool + { + LOCK(cs_main); + CValidationState state; + std::list<CTransactionRef> plTxnReplaced; + for (const auto& tx : txs) { + BOOST_REQUIRE(AcceptToMemoryPool( + ::mempool, + state, + tx, + /* pfMissingInputs */ &ignored, + &plTxnReplaced, + /* bypass_limits */ false, + /* nAbsurdFee */ 0)); + } + } + + // Check that all txs are in the pool + { + LOCK(::mempool.cs); + BOOST_CHECK_EQUAL(::mempool.mapTx.size(), txs.size()); + } + + // Run a thread that simulates an RPC caller that is polling while + // validation is doing a reorg + std::thread rpc_thread{[&]() { + // This thread is checking that the mempool either contains all of + // the transactions invalidated by the reorg, or none of them, and + // not some intermediate amount. + while (true) { + LOCK(::mempool.cs); + if (::mempool.mapTx.size() == 0) { + // We are done with the reorg + break; + } + // Internally, we might be in the middle of the reorg, but + // externally the reorg to the most-proof-of-work chain should + // be atomic. So the caller assumes that the returned mempool + // is consistent. That is, it has all txs that were there + // before the reorg. + assert(::mempool.mapTx.size() == txs.size()); + continue; + } + LOCK(cs_main); + // We are done with the reorg, so the tip must have changed + assert(tip_init != ::ChainActive().Tip()->GetBlockHash()); + }}; + + // Submit the reorg in this thread to invalidate and remove the txs from the tx pool + for (const auto& b : reorg) { + ProcessBlock(b); + } + // Check that the reorg was eventually successful + BOOST_CHECK_EQUAL(last_mined->GetHash(), ::ChainActive().Tip()->GetBlockHash()); + + // We can join the other thread, which returns when the reorg was successful + rpc_thread.join(); + } +} BOOST_AUTO_TEST_SUITE_END() diff --git a/src/timedata.cpp b/src/timedata.cpp index b43639d729..9458b9ae0c 100644 --- a/src/timedata.cpp +++ b/src/timedata.cpp @@ -12,6 +12,7 @@ #include <sync.h> #include <ui_interface.h> #include <util/system.h> +#include <util/translation.h> #include <warnings.h> @@ -100,7 +101,7 @@ void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample) if (!fMatch) { fDone = true; - std::string strMessage = strprintf(_("Please check that your computer's date and time are correct! If your clock is wrong, %s will not work properly."), _(PACKAGE_NAME)); + std::string strMessage = strprintf(_("Please check that your computer's date and time are correct! If your clock is wrong, %s will not work properly.").translated, PACKAGE_NAME); SetMiscWarning(strMessage); uiInterface.ThreadSafeMessageBox(strMessage, "", CClientUIInterface::MSG_WARNING); } diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index a1c730ba08..3f40785c21 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -759,7 +759,9 @@ void InterruptTorControl() { if (gBase) { LogPrintf("tor: Thread interrupt\n"); - event_base_loopbreak(gBase); + event_base_once(gBase, -1, EV_TIMEOUT, [](evutil_socket_t, short, void*) { + event_base_loopbreak(gBase); + }, nullptr, nullptr); } } diff --git a/src/txdb.cpp b/src/txdb.cpp index 73fe2a8ee4..18be07e6db 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -5,12 +5,13 @@ #include <txdb.h> -#include <random.h> #include <pow.h> +#include <random.h> #include <shutdown.h> +#include <ui_interface.h> #include <uint256.h> #include <util/system.h> -#include <ui_interface.h> +#include <util/translation.h> #include <stdint.h> @@ -51,7 +52,7 @@ struct CoinEntry { } -CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / "chainstate", nCacheSize, fMemory, fWipe, true) +CCoinsViewDB::CCoinsViewDB(fs::path ldb_path, size_t nCacheSize, bool fMemory, bool fWipe) : db(ldb_path, nCacheSize, fMemory, fWipe, true) { } @@ -250,7 +251,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams, pcursor->Seek(std::make_pair(DB_BLOCK_INDEX, uint256())); - // Load mapBlockIndex + // Load m_block_index while (pcursor->Valid()) { boost::this_thread::interruption_point(); if (ShutdownRequested()) return false; @@ -357,7 +358,7 @@ bool CCoinsViewDB::Upgrade() { int64_t count = 0; LogPrintf("Upgrading utxo-set database...\n"); LogPrintf("[0%%]..."); /* Continued */ - uiInterface.ShowProgress(_("Upgrading UTXO database"), 0, true); + uiInterface.ShowProgress(_("Upgrading UTXO database").translated, 0, true); size_t batch_size = 1 << 24; CDBBatch batch(db); int reportDone = 0; @@ -372,7 +373,7 @@ bool CCoinsViewDB::Upgrade() { if (count++ % 256 == 0) { uint32_t high = 0x100 * *key.second.begin() + *(key.second.begin() + 1); int percentageDone = (int)(high * 100.0 / 65536.0 + 0.5); - uiInterface.ShowProgress(_("Upgrading UTXO database"), percentageDone, true); + uiInterface.ShowProgress(_("Upgrading UTXO database").translated, percentageDone, true); if (reportDone < percentageDone/10) { // report max. every 10% step LogPrintf("[%d%%]...", percentageDone); /* Continued */ diff --git a/src/txdb.h b/src/txdb.h index c4ece11503..140ce2c7ff 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -48,7 +48,10 @@ class CCoinsViewDB final : public CCoinsView protected: CDBWrapper db; public: - explicit CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); + /** + * @param[in] ldb_path Location in the filesystem where leveldb data will be stored. + */ + explicit CCoinsViewDB(fs::path ldb_path, size_t nCacheSize, bool fMemory, bool fWipe); bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; bool HaveCoin(const COutPoint &outpoint) const override; diff --git a/src/txmempool.cpp b/src/txmempool.cpp index cac7beb6a1..9257cff718 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -104,7 +104,7 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap &cachedDescendan // for each such descendant, also update the ancestor state to include the parent. void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate) { - LOCK(cs); + AssertLockHeld(cs); // For each entry in vHashesToUpdate, store the set of in-mempool, but not // in-vHashesToUpdate transactions, so that we don't have to recalculate // descendants when we come across a previously seen entry. @@ -322,8 +322,8 @@ void CTxMemPoolEntry::UpdateAncestorState(int64_t modifySize, CAmount modifyFee, assert(int(nSigOpCostWithAncestors) >= 0); } -CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator) : - nTransactionsUpdated(0), minerPolicyEstimator(estimator) +CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator) + : nTransactionsUpdated(0), minerPolicyEstimator(estimator) { _clear(); //lock free clear @@ -341,13 +341,11 @@ bool CTxMemPool::isSpent(const COutPoint& outpoint) const unsigned int CTxMemPool::GetTransactionsUpdated() const { - LOCK(cs); return nTransactionsUpdated; } void CTxMemPool::AddTransactionsUpdated(unsigned int n) { - LOCK(cs); nTransactionsUpdated += n; } @@ -459,8 +457,7 @@ void CTxMemPool::CalculateDescendants(txiter entryit, setEntries& setDescendants void CTxMemPool::removeRecursive(const CTransaction &origTx, MemPoolRemovalReason reason) { // Remove transaction from memory pool - { - LOCK(cs); + AssertLockHeld(cs); setEntries txToRemove; txiter origit = mapTx.find(origTx.GetHash()); if (origit != mapTx.end()) { @@ -485,13 +482,12 @@ void CTxMemPool::removeRecursive(const CTransaction &origTx, MemPoolRemovalReaso } RemoveStaged(setAllRemoves, false, reason); - } } void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags) { // Remove transactions spending a coinbase which are now immature and no-longer-final transactions - LOCK(cs); + AssertLockHeld(cs); setEntries txToRemove; for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { const CTransaction& tx = it->GetTx(); @@ -547,7 +543,7 @@ void CTxMemPool::removeConflicts(const CTransaction &tx) */ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight) { - LOCK(cs); + AssertLockHeld(cs); std::vector<const CTxMemPoolEntry*> entries; for (const auto& tx : vtx) { @@ -922,7 +918,7 @@ void CTxMemPool::RemoveStaged(setEntries &stage, bool updateDescendants, MemPool } int CTxMemPool::Expire(int64_t time) { - LOCK(cs); + AssertLockHeld(cs); indexed_transaction_set::index<entry_time>::type::iterator it = mapTx.get<entry_time>().begin(); setEntries toremove; while (it != mapTx.get<entry_time>().end() && it->GetTime() < time) { @@ -1015,7 +1011,7 @@ void CTxMemPool::trackPackageRemoved(const CFeeRate& rate) { } void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining) { - LOCK(cs); + AssertLockHeld(cs); unsigned nTxnRemoved = 0; CFeeRate maxFeeRateRemoved(0); diff --git a/src/txmempool.h b/src/txmempool.h index ce0b762336..6e5ba445d3 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -6,12 +6,13 @@ #ifndef BITCOIN_TXMEMPOOL_H #define BITCOIN_TXMEMPOOL_H +#include <atomic> +#include <map> #include <memory> #include <set> -#include <map> -#include <vector> -#include <utility> #include <string> +#include <utility> +#include <vector> #include <amount.h> #include <coins.h> @@ -344,7 +345,6 @@ struct TxMempoolInfo * this is passed to the notification signal. */ enum class MemPoolRemovalReason { - UNKNOWN = 0, //!< Manually removed or unknown reason EXPIRY, //!< Expired from mempool SIZELIMIT, //!< Removed in size limiting REORG, //!< Removed for reorganization @@ -443,7 +443,7 @@ class CTxMemPool { private: uint32_t nCheckFrequency GUARDED_BY(cs); //!< Value n means that n times in 2^32 we check. - unsigned int nTransactionsUpdated; //!< Used by getblocktemplate to trigger CreateNewBlock() invocation + std::atomic<unsigned int> nTransactionsUpdated; //!< Used by getblocktemplate to trigger CreateNewBlock() invocation CBlockPolicyEstimator* minerPolicyEstimator; uint64_t totalTxSize; //!< sum of all mempool tx's virtual sizes. Differs from serialized tx size since witness data is discounted. Defined in BIP 141. @@ -497,7 +497,7 @@ public: * * 1. Locking both `cs_main` and `mempool.cs` will give a view of mempool * that is consistent with current chain tip (`::ChainActive()` and - * `pcoinsTip`) and is fully populated. Fully populated means that if the + * `CoinsTip()`) and is fully populated. Fully populated means that if the * current active chain is missing transactions that were present in a * previously active chain, all the missing transactions will have been * re-added to the mempool and should be present if they meet size and @@ -513,21 +513,12 @@ public: * `mempool.cs` whenever adding transactions to the mempool and whenever * changing the chain tip. It's necessary to keep both mutexes locked until * the mempool is consistent with the new chain tip and fully populated. - * - * @par Consistency bug - * - * The second guarantee above is not currently enforced, but - * https://github.com/bitcoin/bitcoin/pull/14193 will fix it. No known code - * in bitcoin currently depends on second guarantee, but it is important to - * fix for third party code that needs be able to frequently poll the - * mempool without locking `cs_main` and without encountering missing - * transactions during reorgs. */ mutable RecursiveMutex cs; indexed_transaction_set mapTx GUARDED_BY(cs); using txiter = indexed_transaction_set::nth_index<0>::type::const_iterator; - std::vector<std::pair<uint256, txiter> > vTxHashes; //!< All tx witness hashes/entries in mapTx, in random order + std::vector<std::pair<uint256, txiter>> vTxHashes GUARDED_BY(cs); //!< All tx witness hashes/entries in mapTx, in random order struct CompareIteratorByHash { bool operator()(const txiter &a, const txiter &b) const { @@ -582,10 +573,10 @@ public: void addUnchecked(const CTxMemPoolEntry& entry, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); - void removeRecursive(const CTransaction &tx, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN); - void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - void removeConflicts(const CTransaction &tx) EXCLUSIVE_LOCKS_REQUIRED(cs); - void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight); + void removeRecursive(const CTransaction& tx, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs); + void removeForReorg(const CCoinsViewCache* pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); + void removeConflicts(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(cs); + void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight) EXCLUSIVE_LOCKS_REQUIRED(cs); void clear(); void _clear() EXCLUSIVE_LOCKS_REQUIRED(cs); //lock free @@ -598,7 +589,7 @@ public: * Check that none of this transactions inputs are in the mempool, and thus * the tx is not dependent on other mempool transactions to be included in a block. */ - bool HasNoInputsOf(const CTransaction& tx) const; + bool HasNoInputsOf(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs); /** Affect CreateNewBlock prioritisation of transactions */ void PrioritiseTransaction(const uint256& hash, const CAmount& nFeeDelta); @@ -621,7 +612,7 @@ public: * Set updateDescendants to true when removing a tx that was in a block, so * that any in-mempool descendants have their ancestor state updated. */ - void RemoveStaged(setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN) EXCLUSIVE_LOCKS_REQUIRED(cs); + void RemoveStaged(setEntries& stage, bool updateDescendants, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs); /** When adding transactions from a disconnected block back to the mempool, * new mempool entries may have children in the mempool (which is generally @@ -632,7 +623,7 @@ public: * for). Note: vHashesToUpdate should be the set of transactions from the * disconnected block that have been accepted back into the mempool. */ - void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); /** Try to calculate all in-mempool ancestors of entry. * (these are all calculated including the tx itself) @@ -663,10 +654,10 @@ public: * pvNoSpendsRemaining, if set, will be populated with the list of outpoints * which are not in mempool which no longer have any spends in this mempool. */ - void TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining=nullptr); + void TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs); /** Expire all transaction (and their dependencies) in the mempool older than time. Return the number of removed transactions. */ - int Expire(int64_t time); + int Expire(int64_t time) EXCLUSIVE_LOCKS_REQUIRED(cs); /** * Calculate the ancestor and descendant count for the given transaction. @@ -743,7 +734,7 @@ private: * transactions in a chain before we've updated all the state for the * removal. */ - void removeUnchecked(txiter entry, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN) EXCLUSIVE_LOCKS_REQUIRED(cs); + void removeUnchecked(txiter entry, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs); }; /** diff --git a/src/uint256.cpp b/src/uint256.cpp index e3bc9712e8..ea7164c1f0 100644 --- a/src/uint256.cpp +++ b/src/uint256.cpp @@ -37,16 +37,15 @@ void base_blob<BITS>::SetHex(const char* psz) psz += 2; // hex string to uint - const char* pbegin = psz; - while (::HexDigit(*psz) != -1) - psz++; - psz--; + size_t digits = 0; + while (::HexDigit(psz[digits]) != -1) + digits++; unsigned char* p1 = (unsigned char*)data; unsigned char* pend = p1 + WIDTH; - while (psz >= pbegin && p1 < pend) { - *p1 = ::HexDigit(*psz--); - if (psz >= pbegin) { - *p1 |= ((unsigned char)::HexDigit(*psz--) << 4); + while (digits > 0 && p1 < pend) { + *p1 = ::HexDigit(psz[--digits]); + if (digits > 0) { + *p1 |= ((unsigned char)::HexDigit(psz[--digits]) << 4); p1++; } } diff --git a/src/util/error.cpp b/src/util/error.cpp index 68ffd8b046..aa44ed3e3a 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -4,7 +4,9 @@ #include <util/error.h> +#include <tinyformat.h> #include <util/system.h> +#include <util/translation.h> std::string TransactionErrorString(const TransactionError err) { @@ -27,17 +29,24 @@ std::string TransactionErrorString(const TransactionError err) return "PSBTs not compatible (different transactions)"; case TransactionError::SIGHASH_MISMATCH: return "Specified sighash value does not match existing value"; + case TransactionError::MAX_FEE_EXCEEDED: + return "Fee exceeds maximum configured by -maxtxfee"; // no default case, so the compiler can warn about missing cases } assert(false); } -std::string AmountHighWarn(const std::string& optname) +std::string ResolveErrMsg(const std::string& optname, const std::string& strBind) +{ + return strprintf(_("Cannot resolve -%s address: '%s'").translated, optname, strBind); +} + +bilingual_str AmountHighWarn(const std::string& optname) { return strprintf(_("%s is set very high!"), optname); } -std::string AmountErrMsg(const char* const optname, const std::string& strValue) +bilingual_str AmountErrMsg(const std::string& optname, const std::string& strValue) { return strprintf(_("Invalid amount for -%s=<amount>: '%s'"), optname, strValue); } diff --git a/src/util/error.h b/src/util/error.h index d93309551b..f540b0020d 100644 --- a/src/util/error.h +++ b/src/util/error.h @@ -10,13 +10,15 @@ * string functions. Types and functions defined here should not require any * outside dependencies. * - * Error types defined here can be used in different parts of the bitcoin + * Error types defined here can be used in different parts of the * codebase, to avoid the need to write boilerplate code catching and * translating errors passed across wallet/node/rpc/gui code boundaries. */ #include <string> +struct bilingual_str; + enum class TransactionError { OK, //!< No error MISSING_INPUTS, @@ -27,12 +29,15 @@ enum class TransactionError { INVALID_PSBT, PSBT_MISMATCH, SIGHASH_MISMATCH, + MAX_FEE_EXCEEDED, }; std::string TransactionErrorString(const TransactionError error); -std::string AmountHighWarn(const std::string& optname); +std::string ResolveErrMsg(const std::string& optname, const std::string& strBind); + +bilingual_str AmountHighWarn(const std::string& optname); -std::string AmountErrMsg(const char* const optname, const std::string& strValue); +bilingual_str AmountErrMsg(const std::string& optname, const std::string& strValue); #endif // BITCOIN_UTIL_ERROR_H diff --git a/src/util/fees.cpp b/src/util/fees.cpp index 5fdaa1284c..cf16d5e44f 100644 --- a/src/util/fees.cpp +++ b/src/util/fees.cpp @@ -18,7 +18,6 @@ std::string StringForFeeReason(FeeReason reason) { {FeeReason::PAYTXFEE, "PayTxFee set"}, {FeeReason::FALLBACK, "Fallback fee"}, {FeeReason::REQUIRED, "Minimum Required Fee"}, - {FeeReason::MAXTXFEE, "MaxTxFee limit"} }; auto reason_string = fee_reason_strings.find(reason); diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 0acbb4f117..1e7d24c71c 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -546,9 +546,18 @@ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) return true; } -void Downcase(std::string& str) +std::string ToLower(const std::string& str) { - std::transform(str.begin(), str.end(), str.begin(), [](char c){return ToLower(c);}); + std::string r; + for (auto ch : str) r += ToLower((unsigned char)ch); + return r; +} + +std::string ToUpper(const std::string& str) +{ + std::string r; + for (auto ch : str) r += ToUpper((unsigned char)ch); + return r; } std::string Capitalize(std::string str) diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 7c4364a082..e35b2ab857 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -199,6 +199,8 @@ bool ConvertBits(const O& outfn, I it, I end) { * Converts the given character to its lowercase equivalent. * This function is locale independent. It only converts uppercase * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * * @param[in] c the character to convert to lowercase. * @return the lowercase equivalent of c; or the argument * if no conversion is possible. @@ -209,17 +211,22 @@ constexpr char ToLower(char c) } /** - * Converts the given string to its lowercase equivalent. + * Returns the lowercase equivalent of the given string. * This function is locale independent. It only converts uppercase * characters in the standard 7-bit ASCII range. - * @param[in,out] str the string to convert to lowercase. + * This is a feature, not a limitation. + * + * @param[in] str the string to convert to lowercase. + * @returns lowercased equivalent of str */ -void Downcase(std::string& str); +std::string ToLower(const std::string& str); /** * Converts the given character to its uppercase equivalent. * This function is locale independent. It only converts lowercase * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * * @param[in] c the character to convert to uppercase. * @return the uppercase equivalent of c; or the argument * if no conversion is possible. @@ -230,12 +237,24 @@ constexpr char ToUpper(char c) } /** + * Returns the uppercase equivalent of the given string. + * This function is locale independent. It only converts lowercase + * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * + * @param[in] str the string to convert to uppercase. + * @returns UPPERCASED EQUIVALENT OF str + */ +std::string ToUpper(const std::string& str); + +/** * Capitalizes the first character of the given string. - * This function is locale independent. It only capitalizes the - * first character of the argument if it has an uppercase equivalent - * in the standard 7-bit ASCII range. + * This function is locale independent. It only converts lowercase + * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * * @param[in] str the string to capitalize. - * @return string with the first letter capitalized. + * @returns string with the first letter capitalized. */ std::string Capitalize(std::string str); diff --git a/src/util/string.cpp b/src/util/string.cpp new file mode 100644 index 0000000000..8ea3a1afc6 --- /dev/null +++ b/src/util/string.cpp @@ -0,0 +1,5 @@ +// Copyright (c) 2019 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 <util/string.h> diff --git a/src/util/string.h b/src/util/string.h new file mode 100644 index 0000000000..dec0c19b08 --- /dev/null +++ b/src/util/string.h @@ -0,0 +1,35 @@ +// Copyright (c) 2019 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_STRING_H +#define BITCOIN_UTIL_STRING_H + +#include <functional> +#include <string> +#include <vector> + +/** + * Join a list of items + * + * @param list The list to join + * @param separator The separator + * @param unary_op Apply this operator to each item in the list + */ +template <typename T, typename UnaryOp> +std::string Join(const std::vector<T>& list, const std::string& separator, UnaryOp unary_op) +{ + std::string ret; + for (size_t i = 0; i < list.size(); ++i) { + if (i > 0) ret += separator; + ret += unary_op(list.at(i)); + } + return ret; +} + +inline std::string Join(const std::vector<std::string>& list, const std::string& separator) +{ + return Join(list, separator, [](const std::string& i) { return i; }); +} + +#endif // BITCOIN_UTIL_STRENCODINGS_H diff --git a/src/util/system.cpp b/src/util/system.cpp index fca29a9f31..c925dec253 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -7,6 +7,7 @@ #include <chainparamsbase.h> #include <util/strencodings.h> +#include <util/translation.h> #include <stdarg.h> @@ -267,22 +268,24 @@ public: * This method also tracks when the -no form was supplied, and if so, * checks whether there was a double-negative (-nofoo=0 -> -foo=1). * - * If there was not a double negative, it removes the "no" from the key, - * and returns true, indicating the caller should clear the args vector - * to indicate a negated option. + * If there was not a double negative, it removes the "no" from the key + * and clears the args vector to indicate a negated option. * * If there was a double negative, it removes "no" from the key, sets the - * value to "1" and returns false. + * value to "1" and pushes the key and the updated value to the args vector. * - * If there was no "no", it leaves key and value untouched and returns - * false. + * If there was no "no", it leaves key and value untouched and pushes them + * to the args vector. * * Where an option was negated can be later checked using the * IsArgNegated() method. One use case for this is to have a way to disable * options that are not normally boolean (e.g. using -nodebuglogfile to request * that debug log output is not sent to any file at all). */ -static bool InterpretNegatedOption(std::string& key, std::string& val) + +NODISCARD static bool InterpretOption(std::string key, std::string val, unsigned int flags, + std::map<std::string, std::vector<std::string>>& args, + std::string& error) { assert(key[0] == '-'); @@ -293,31 +296,25 @@ static bool InterpretNegatedOption(std::string& key, std::string& val) ++option_index; } if (key.substr(option_index, 2) == "no") { - bool bool_val = InterpretBool(val); key.erase(option_index, 2); - if (!bool_val ) { + if (flags & ArgsManager::ALLOW_BOOL) { + if (InterpretBool(val)) { + args[key].clear(); + return true; + } // Double negatives like -nofoo=0 are supported (but discouraged) LogPrintf("Warning: parsed potentially confusing double-negative %s=%s\n", key, val); val = "1"; } else { - return true; + error = strprintf("Negating of %s is meaningless and therefore forbidden", key.c_str()); + return false; } } - return false; + args[key].push_back(val); + return true; } -ArgsManager::ArgsManager() : - /* These options would cause cross-contamination if values for - * mainnet were used while running on regtest/testnet (or vice-versa). - * Setting them as section_only_args ensures that sharing a config file - * between mainnet and regtest/testnet won't cause problems due to these - * parameters by accident. */ - m_network_only_args{ - "-addnode", "-connect", - "-port", "-bind", - "-rpcport", "-rpcbind", - "-wallet", - } +ArgsManager::ArgsManager() { // nothing to do } @@ -383,6 +380,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin for (int i = 1; i < argc; i++) { std::string key(argv[i]); + if (key == "-") break; //bitcoin-tx using stdin std::string val; size_t is_index = key.find('='); if (is_index != std::string::npos) { @@ -390,7 +388,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin key.erase(is_index); } #ifdef WIN32 - std::transform(key.begin(), key.end(), key.begin(), ToLower); + key = ToLower(key); if (key[0] == '/') key[0] = '-'; #endif @@ -402,19 +400,14 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin if (key.length() > 1 && key[1] == '-') key.erase(0, 1); - // Check for -nofoo - if (InterpretNegatedOption(key, val)) { - m_override_args[key].clear(); - } else { - m_override_args[key].push_back(val); - } - - // Check that the arg is known - if (!(IsSwitchChar(key[0]) && key.size() == 1)) { - if (!IsArgKnown(key)) { - error = strprintf("Invalid parameter %s", key.c_str()); + const unsigned int flags = FlagsOfKnownArg(key); + if (flags) { + if (!InterpretOption(key, val, flags, m_override_args, error)) { return false; } + } else { + error = strprintf("Invalid parameter %s", key.c_str()); + return false; } } @@ -431,21 +424,30 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin return true; } -bool ArgsManager::IsArgKnown(const std::string& key) const +unsigned int ArgsManager::FlagsOfKnownArg(const std::string& key) const { + assert(key[0] == '-'); + size_t option_index = key.find('.'); - std::string arg_no_net; if (option_index == std::string::npos) { - arg_no_net = key; + option_index = 1; } else { - arg_no_net = std::string("-") + key.substr(option_index + 1, std::string::npos); + ++option_index; + } + if (key.substr(option_index, 2) == "no") { + option_index += 2; } + const std::string base_arg_name = '-' + key.substr(option_index); + LOCK(cs_args); for (const auto& arg_map : m_available_args) { - if (arg_map.second.count(arg_no_net)) return true; + const auto search = arg_map.second.find(base_arg_name); + if (search != arg_map.second.end()) { + return search->second.m_flags; + } } - return false; + return ArgsManager::NONE; } std::vector<std::string> ArgsManager::GetArgs(const std::string& strArg) const @@ -537,24 +539,29 @@ void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strV m_override_args[strArg] = {strValue}; } -void ArgsManager::AddArg(const std::string& name, const std::string& help, const bool debug_only, const OptionsCategory& cat) +void ArgsManager::AddArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat) { // Split arg name from its help param size_t eq_index = name.find('='); if (eq_index == std::string::npos) { eq_index = name.size(); } + std::string arg_name = name.substr(0, eq_index); LOCK(cs_args); std::map<std::string, Arg>& arg_map = m_available_args[cat]; - auto ret = arg_map.emplace(name.substr(0, eq_index), Arg(name.substr(eq_index, name.size() - eq_index), help, debug_only)); + auto ret = arg_map.emplace(arg_name, Arg{name.substr(eq_index, name.size() - eq_index), help, flags}); assert(ret.second); // Make sure an insertion actually happened + + if (flags & ArgsManager::NETWORK_ONLY) { + m_network_only_args.emplace(arg_name); + } } void ArgsManager::AddHiddenArgs(const std::vector<std::string>& names) { for (const std::string& name : names) { - AddArg(name, "", false, OptionsCategory::HIDDEN); + AddArg(name, "", ArgsManager::ALLOW_ANY, OptionsCategory::HIDDEN); } } @@ -613,7 +620,7 @@ std::string ArgsManager::GetHelpMessage() const if (arg_map.first == OptionsCategory::HIDDEN) break; for (const auto& arg : arg_map.second) { - if (show_debug || !arg.second.m_debug_only) { + if (show_debug || !(arg.second.m_flags & ArgsManager::DEBUG_ONLY)) { std::string name; if (arg.second.m_help_param.empty()) { name = arg.first; @@ -634,7 +641,7 @@ bool HelpRequested(const ArgsManager& args) void SetupHelpOptions(ArgsManager& args) { - args.AddArg("-?", "Print this help message and exit", false, OptionsCategory::OPTIONS); + args.AddArg("-?", "Print this help message and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); args.AddHiddenArgs({"-h", "-help"}); } @@ -705,19 +712,16 @@ fs::path GetDefaultDataDir() static fs::path g_blocks_path_cache_net_specific; static fs::path pathCached; static fs::path pathCachedNetSpecific; -static CCriticalSection csPathCached; +static RecursiveMutex csPathCached; const fs::path &GetBlocksDir() { - LOCK(csPathCached); - fs::path &path = g_blocks_path_cache_net_specific; - // This can be called during exceptions by LogPrintf(), so we cache the - // value so we don't have to do memory allocations after that. - if (!path.empty()) - return path; + // Cache the path to avoid calling fs::create_directories on every call of + // this function + if (!path.empty()) return path; if (gArgs.IsArgSet("-blocksdir")) { path = fs::system_complete(gArgs.GetArg("-blocksdir", "")); @@ -737,18 +741,16 @@ const fs::path &GetBlocksDir() const fs::path &GetDataDir(bool fNetSpecific) { - LOCK(csPathCached); - fs::path &path = fNetSpecific ? pathCachedNetSpecific : pathCached; - // This can be called during exceptions by LogPrintf(), so we cache the - // value so we don't have to do memory allocations after that. - if (!path.empty()) - return path; + // Cache the path to avoid calling fs::create_directories on every call of + // this function + if (!path.empty()) return path; - if (gArgs.IsArgSet("-datadir")) { - path = fs::system_complete(gArgs.GetArg("-datadir", "")); + std::string datadir = gArgs.GetArg("-datadir", ""); + if (!datadir.empty()) { + path = fs::system_complete(datadir); if (!fs::is_directory(path)) { path = ""; return path; @@ -767,6 +769,12 @@ const fs::path &GetDataDir(bool fNetSpecific) return path; } +bool CheckDataDirOption() +{ + std::string datadir = gArgs.GetArg("-datadir", ""); + return datadir.empty() || fs::is_directory(fs::system_complete(datadir)); +} + void ClearDatadirCache() { LOCK(csPathCached); @@ -844,22 +852,18 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file return false; } for (const std::pair<std::string, std::string>& option : options) { - std::string strKey = std::string("-") + option.first; - std::string strValue = option.second; - - if (InterpretNegatedOption(strKey, strValue)) { - m_config_args[strKey].clear(); + const std::string strKey = std::string("-") + option.first; + const unsigned int flags = FlagsOfKnownArg(strKey); + if (flags) { + if (!InterpretOption(strKey, option.second, flags, m_config_args, error)) { + return false; + } } else { - m_config_args[strKey].push_back(strValue); - } - - // Check that the arg is known - if (!IsArgKnown(strKey)) { - if (!ignore_invalid_keys) { + if (ignore_invalid_keys) { + LogPrintf("Ignoring unknown configuration value %s\n", option.first); + } else { error = strprintf("Invalid configuration value %s", option.first.c_str()); return false; - } else { - LogPrintf("Ignoring unknown configuration value %s\n", option.first); } } } @@ -940,7 +944,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) // If datadir is changed in .conf file: ClearDatadirCache(); - if (!fs::is_directory(GetDataDir(false))) { + if (!CheckDataDirOption()) { error = strprintf("specified data directory \"%s\" does not exist.", gArgs.GetArg("-datadir", "").c_str()); return false; } @@ -1120,6 +1124,7 @@ fs::path GetSpecialFolderPath(int nFolder, bool fCreate) } #endif +#if HAVE_SYSTEM void runCommand(const std::string& strCommand) { if (strCommand.empty()) return; @@ -1131,6 +1136,7 @@ void runCommand(const std::string& strCommand) if (nErr) LogPrintf("runCommand error: system(%s) returned %d\n", strCommand, nErr); } +#endif void SetupEnvironment() { @@ -1188,10 +1194,11 @@ int GetNumCores() std::string CopyrightHolders(const std::string& strPrefix) { - std::string strCopyrightHolders = strPrefix + strprintf(_(COPYRIGHT_HOLDERS), _(COPYRIGHT_HOLDERS_SUBSTITUTION)); + const auto copyright_devs = strprintf(_(COPYRIGHT_HOLDERS).translated, COPYRIGHT_HOLDERS_SUBSTITUTION); + std::string strCopyrightHolders = strPrefix + copyright_devs; - // Check for untranslated substitution to make sure Bitcoin Core copyright is not removed by accident - if (strprintf(COPYRIGHT_HOLDERS, COPYRIGHT_HOLDERS_SUBSTITUTION).find("Bitcoin Core") == std::string::npos) { + // Make sure Bitcoin Core copyright is not removed by accident + if (copyright_devs.find("Bitcoin Core") == std::string::npos) { strCopyrightHolders += "\n" + strPrefix + "The Bitcoin Core developers"; } return strCopyrightHolders; @@ -1205,6 +1212,9 @@ int64_t GetStartupTime() fs::path AbsPathForConfigVal(const fs::path& path, bool net_specific) { + if (path.is_absolute()) { + return path; + } return fs::absolute(path, GetDataDir(net_specific)); } diff --git a/src/util/system.h b/src/util/system.h index 1a83cb67b1..908a3c407d 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -20,18 +20,16 @@ #include <fs.h> #include <logging.h> #include <sync.h> -#include <util/threadnames.h> #include <tinyformat.h> #include <util/memory.h> +#include <util/threadnames.h> #include <util/time.h> -#include <atomic> #include <exception> #include <map> #include <set> #include <stdint.h> #include <string> -#include <unordered_set> #include <utility> #include <vector> @@ -42,18 +40,6 @@ int64_t GetStartupTime(); extern const char * const BITCOIN_CONF_FILENAME; -/** Translate a message to the native language of the user. */ -const extern std::function<std::string(const char*)> G_TRANSLATION_FUN; - -/** - * Translation function. - * If no translation function is set, simply return the input. - */ -inline std::string _(const char* psz) -{ - return G_TRANSLATION_FUN ? (G_TRANSLATION_FUN)(psz) : psz; -} - void SetupEnvironment(); bool SetupNetworking(); @@ -85,12 +71,17 @@ fs::path GetDefaultDataDir(); // The blocks directory is always net specific. const fs::path &GetBlocksDir(); const fs::path &GetDataDir(bool fNetSpecific = true); +// Return true if -datadir option points to a valid directory or is not specified. +bool CheckDataDirOption(); +/** Tests only */ void ClearDatadirCache(); fs::path GetConfigFile(const std::string& confPath); #ifdef WIN32 fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true); #endif +#if HAVE_SYSTEM void runCommand(const std::string& strCommand); +#endif /** * Most paths passed as configuration arguments are treated as relative to @@ -138,6 +129,23 @@ struct SectionInfo class ArgsManager { +public: + enum Flags { + NONE = 0x00, + // Boolean options can accept negation syntax -noOPTION or -noOPTION=1 + ALLOW_BOOL = 0x01, + ALLOW_INT = 0x02, + ALLOW_STRING = 0x04, + ALLOW_ANY = ALLOW_BOOL | ALLOW_INT | ALLOW_STRING, + DEBUG_ONLY = 0x100, + /* Some options would cause cross-contamination if values for + * mainnet were used while running on regtest/testnet (or vice-versa). + * Setting them as NETWORK_ONLY ensures that sharing a config file + * between mainnet and regtest/testnet won't cause problems due to these + * parameters by accident. */ + NETWORK_ONLY = 0x200, + }; + protected: friend class ArgsManagerHelper; @@ -145,9 +153,7 @@ protected: { std::string m_help_param; std::string m_help_text; - bool m_debug_only; - - Arg(const std::string& help_param, const std::string& help_text, bool debug_only) : m_help_param(help_param), m_help_text(help_text), m_debug_only(debug_only) {}; + unsigned int m_flags; }; mutable CCriticalSection cs_args; @@ -267,7 +273,7 @@ public: /** * Add argument */ - void AddArg(const std::string& name, const std::string& help, const bool debug_only, const OptionsCategory& cat); + void AddArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat); /** * Add many hidden arguments @@ -280,6 +286,7 @@ public: void ClearArgs() { LOCK(cs_args); m_available_args.clear(); + m_network_only_args.clear(); } /** @@ -288,9 +295,10 @@ public: std::string GetHelpMessage() const; /** - * Check whether we know of this arg + * Return Flags for known arg. + * Return ArgsManager::NONE for unknown arg. */ - bool IsArgKnown(const std::string& key) const; + unsigned int FlagsOfKnownArg(const std::string& key) const; }; extern ArgsManager gArgs; diff --git a/src/util/translation.h b/src/util/translation.h new file mode 100644 index 0000000000..f100dab20d --- /dev/null +++ b/src/util/translation.h @@ -0,0 +1,42 @@ +// Copyright (c) 2019 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_TRANSLATION_H +#define BITCOIN_UTIL_TRANSLATION_H + +#include <tinyformat.h> + +#include <utility> + +/** + * Bilingual messages: + * - in GUI: user's native language + untranslated (i.e. English) + * - in log and stderr: untranslated only + */ +struct bilingual_str { + std::string original; + std::string translated; +}; + +namespace tinyformat { +template <typename... Args> +bilingual_str format(const bilingual_str& fmt, const Args&... args) +{ + return bilingual_str{format(fmt.original, args...), format(fmt.translated, args...)}; +} +} // namespace tinyformat + +/** Translate a message to the native language of the user. */ +const extern std::function<std::string(const char*)> G_TRANSLATION_FUN; + +/** + * Translation function. + * If no translation function is set, simply return the input. + */ +inline bilingual_str _(const char* psz) +{ + return bilingual_str{psz, G_TRANSLATION_FUN ? (G_TRANSLATION_FUN)(psz) : psz}; +} + +#endif // BITCOIN_UTIL_TRANSLATION_H diff --git a/src/validation.cpp b/src/validation.cpp index d39b78614c..d470fd5b6e 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -41,6 +41,7 @@ #include <util/rbf.h> #include <util/strencodings.h> #include <util/system.h> +#include <util/translation.h> #include <util/validation.h> #include <validationinterface.h> #include <warnings.h> @@ -77,11 +78,21 @@ bool CBlockIndexWorkComparator::operator()(const CBlockIndex *pa, const CBlockIn return false; } -static CChainState g_chainstate; +namespace { +BlockManager g_blockman; +} // anon namespace -CChainState& ChainstateActive() { return g_chainstate; } +std::unique_ptr<CChainState> g_chainstate; -CChain& ChainActive() { return g_chainstate.m_chain; } +CChainState& ChainstateActive() { + assert(g_chainstate); + return *g_chainstate; +} + +CChain& ChainActive() { + assert(g_chainstate); + return g_chainstate->m_chain; +} /** * Mutex to guard access to validation specific variables, such as reading @@ -95,7 +106,6 @@ CChain& ChainActive() { return g_chainstate.m_chain; } */ RecursiveMutex cs_main; -BlockMap& mapBlockIndex = ::ChainstateActive().mapBlockIndex; CBlockIndex *pindexBestHeader = nullptr; Mutex g_best_block_mutex; std::condition_variable g_best_block_cv; @@ -125,12 +135,7 @@ CScript COINBASE_FLAGS; // Internal stuff namespace { - CBlockIndex *&pindexBestInvalid = ::ChainstateActive().pindexBestInvalid; - - /** All pairs A->B, where A (or one of its ancestors) misses transactions, but B has transactions. - * Pruned nodes may have entries where B is missing data. - */ - std::multimap<CBlockIndex*, CBlockIndex*>& mapBlocksUnlinked = ::ChainstateActive().mapBlocksUnlinked; + CBlockIndex* pindexBestInvalid = nullptr; CCriticalSection cs_LastBlockFile; std::vector<CBlockFileInfo> vinfoBlockFile; @@ -148,6 +153,13 @@ namespace { std::set<int> setDirtyFileInfo; } // anon namespace +CBlockIndex* LookupBlockIndex(const uint256& hash) +{ + AssertLockHeld(cs_main); + BlockMap::const_iterator it = g_blockman.m_block_index.find(hash); + return it == g_blockman.m_block_index.end() ? nullptr : it->second; +} + CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator) { AssertLockHeld(cs_main); @@ -167,14 +179,12 @@ CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& loc return chain.Genesis(); } -std::unique_ptr<CCoinsViewDB> pcoinsdbview; -std::unique_ptr<CCoinsViewCache> pcoinsTip; std::unique_ptr<CBlockTreeDB> pblocktree; // See definition for documentation static void FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPruneHeight); static void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight); -bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks = nullptr); +bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks = nullptr); static FILE* OpenUndoFile(const FlatFilePos &pos, bool fReadOnly = false); static FlatFileSeq BlockFileSeq(); static FlatFileSeq UndoFileSeq(); @@ -254,8 +264,8 @@ bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flag lockPair.second = lp->time; } else { - // pcoinsTip contains the UTXO set for ::ChainActive().Tip() - CCoinsViewMemPool viewMemPool(pcoinsTip.get(), pool); + // CoinsTip() contains the UTXO set for ::ChainActive().Tip() + CCoinsViewMemPool viewMemPool(&::ChainstateActive().CoinsTip(), pool); std::vector<int> prevheights; prevheights.resize(tx.vin.size()); for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) { @@ -304,7 +314,9 @@ bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flag // Returns the script flags which should be checked for a given block static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consensus::Params& chainparams); -static void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age) { +static void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age) + EXCLUSIVE_LOCKS_REQUIRED(pool.cs, ::cs_main) +{ int expired = pool.Expire(GetTime() - age); if (expired != 0) { LogPrint(BCLog::MEMPOOL, "Expired %i transactions from the memory pool\n", expired); @@ -313,7 +325,7 @@ static void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age) std::vector<COutPoint> vNoSpendsRemaining; pool.TrimToSize(limit, &vNoSpendsRemaining); for (const COutPoint& removed : vNoSpendsRemaining) - pcoinsTip->Uncache(removed); + ::ChainstateActive().CoinsTip().Uncache(removed); } static bool IsCurrentForFeeEstimation() EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -341,7 +353,7 @@ static bool IsCurrentForFeeEstimation() EXCLUSIVE_LOCKS_REQUIRED(cs_main) * and instead just erase from the mempool as needed. */ -static void UpdateMempoolForReorg(DisconnectedBlockTransactions &disconnectpool, bool fAddToMempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void UpdateMempoolForReorg(DisconnectedBlockTransactions& disconnectpool, bool fAddToMempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs) { AssertLockHeld(cs_main); std::vector<uint256> vHashUpdate; @@ -375,7 +387,7 @@ static void UpdateMempoolForReorg(DisconnectedBlockTransactions &disconnectpool, mempool.UpdateTransactionsFromBlock(vHashUpdate); // We also need to remove any now-immature transactions - mempool.removeForReorg(pcoinsTip.get(), ::ChainActive().Tip()->nHeight + 1, STANDARD_LOCKTIME_VERIFY_FLAGS); + mempool.removeForReorg(&::ChainstateActive().CoinsTip(), ::ChainActive().Tip()->nHeight + 1, STANDARD_LOCKTIME_VERIFY_FLAGS); // Re-limit mempool size, in case we added any transactions LimitMempoolSize(mempool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60); } @@ -407,13 +419,13 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, CValidationSt assert(txFrom->vout.size() > txin.prevout.n); assert(txFrom->vout[txin.prevout.n] == coin.out); } else { - const Coin& coinFromDisk = pcoinsTip->AccessCoin(txin.prevout); + const Coin& coinFromDisk = ::ChainstateActive().CoinsTip().AccessCoin(txin.prevout); assert(!coinFromDisk.IsSpent()); assert(coinFromDisk.out == coin.out); } } - return CheckInputs(tx, state, view, true, flags, cacheSigStore, true, txdata); + return CheckInputs(tx, state, view, flags, cacheSigStore, true, txdata); } /** @@ -507,23 +519,24 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool CCoinsViewCache view(&dummy); LockPoints lp; - CCoinsViewMemPool viewMemPool(pcoinsTip.get(), pool); + CCoinsViewCache& coins_cache = ::ChainstateActive().CoinsTip(); + CCoinsViewMemPool viewMemPool(&coins_cache, pool); view.SetBackend(viewMemPool); // do all inputs exist? for (const CTxIn& txin : tx.vin) { - if (!pcoinsTip->HaveCoinInCache(txin.prevout)) { + if (!coins_cache.HaveCoinInCache(txin.prevout)) { coins_to_uncache.push_back(txin.prevout); } // Note: this call may add txin.prevout to the coins cache - // (pcoinsTip.cacheCoins) by way of FetchCoin(). It should be removed + // (CoinsTip().cacheCoins) by way of FetchCoin(). It should be removed // later (via coins_to_uncache) if this tx turns out to be invalid. if (!view.HaveCoin(txin.prevout)) { // Are inputs missing because we already have the tx? for (size_t out = 0; out < tx.vout.size(); out++) { // Optimistically just do efficient check of cache for outputs - if (pcoinsTip->HaveCoinInCache(COutPoint(hash, out))) { + if (coins_cache.HaveCoinInCache(COutPoint(hash, out))) { return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "txn-already-known"); } } @@ -602,15 +615,69 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool REJECT_HIGHFEE, "absurdly-high-fee", strprintf("%d > %d", nFees, nAbsurdFee)); + const CTxMemPool::setEntries setIterConflicting = pool.GetIterSet(setConflicts); // Calculate in-mempool ancestors, up to a limit. CTxMemPool::setEntries setAncestors; size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000; size_t nLimitDescendants = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000; + + if (setConflicts.size() == 1) { + // In general, when we receive an RBF transaction with mempool conflicts, we want to know whether we + // would meet the chain limits after the conflicts have been removed. However, there isn't a practical + // way to do this short of calculating the ancestor and descendant sets with an overlay cache of + // changed mempool entries. Due to both implementation and runtime complexity concerns, this isn't + // very realistic, thus we only ensure a limited set of transactions are RBF'able despite mempool + // conflicts here. Importantly, we need to ensure that some transactions which were accepted using + // the below carve-out are able to be RBF'ed, without impacting the security the carve-out provides + // for off-chain contract systems (see link in the comment below). + // + // Specifically, the subset of RBF transactions which we allow despite chain limits are those which + // conflict directly with exactly one other transaction (but may evict children of said transaction), + // and which are not adding any new mempool dependencies. Note that the "no new mempool dependencies" + // check is accomplished later, so we don't bother doing anything about it here, but if BIP 125 is + // amended, we may need to move that check to here instead of removing it wholesale. + // + // Such transactions are clearly not merging any existing packages, so we are only concerned with + // ensuring that (a) no package is growing past the package size (not count) limits and (b) we are + // not allowing something to effectively use the (below) carve-out spot when it shouldn't be allowed + // to. + // + // To check these we first check if we meet the RBF criteria, above, and increment the descendant + // limits by the direct conflict and its descendants (as these are recalculated in + // CalculateMempoolAncestors by assuming the new transaction being added is a new descendant, with no + // removals, of each parent's existing dependant set). The ancestor count limits are unmodified (as + // the ancestor limits should be the same for both our new transaction and any conflicts). + // We don't bother incrementing nLimitDescendants by the full removal count as that limit never comes + // into force here (as we're only adding a single transaction). + assert(setIterConflicting.size() == 1); + CTxMemPool::txiter conflict = *setIterConflicting.begin(); + + nLimitDescendants += 1; + nLimitDescendantSize += conflict->GetSizeWithDescendants(); + } + std::string errString; if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { - return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too-long-mempool-chain", errString); + setAncestors.clear(); + // If CalculateMemPoolAncestors fails second time, we want the original error string. + std::string dummy_err_string; + // Contracting/payment channels CPFP carve-out: + // If the new transaction is relatively small (up to 40k weight) + // and has at most one ancestor (ie ancestor limit of 2, including + // the new transaction), allow it if its parent has exactly the + // descendant limit descendants. + // + // This allows protocols which rely on distrusting counterparties + // being able to broadcast descendants of an unconfirmed transaction + // to be secure by simply only having two immediately-spendable + // outputs - one for each counterparty. For more info on the uses for + // this, see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html + if (nSize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || + !pool.CalculateMemPoolAncestors(entry, setAncestors, 2, nLimitAncestorSize, nLimitDescendants + 1, nLimitDescendantSize + EXTRA_DESCENDANT_TX_SIZE_LIMIT, dummy_err_string)) { + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too-long-mempool-chain", errString); + } } // A transaction that spends outputs that would be replaced by it is invalid. Now @@ -645,7 +712,6 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool CFeeRate newFeeRate(nModifiedFees, nSize); std::set<uint256> setConflictsParents; const int maxDescendantsToVisit = 100; - const CTxMemPool::setEntries setIterConflicting = pool.GetIterSet(setConflicts); for (const auto& mi : setIterConflicting) { // Don't allow the replacement to reduce the feerate of the // mempool. @@ -705,6 +771,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // feerate junk to be mined first. Ideally we'd keep track of // the ancestor feerates and make the decision based on that, // but for now requiring all new inputs to be confirmed works. + // + // Note that if you relax this to make RBF a little more useful, + // this may break the CalculateMempoolAncestors RBF relaxation, + // above. See the comment above the first CalculateMempoolAncestors + // call for more info. if (!setConflictsParents.count(tx.vin[j].prevout.hash)) { // Rather than check the UTXO set - potentially expensive - @@ -744,15 +815,17 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS; // Check against previous transactions - // This is done last to help prevent CPU exhaustion denial-of-service attacks. + // The first loop above does all the inexpensive checks. + // Only if ALL inputs pass do we perform expensive ECDSA signature checks. + // Helps prevent CPU exhaustion denial-of-service attacks. PrecomputedTransactionData txdata(tx); - if (!CheckInputs(tx, state, view, true, scriptVerifyFlags, true, false, txdata)) { + if (!CheckInputs(tx, state, view, scriptVerifyFlags, true, false, txdata)) { // SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_WITNESS, so we // need to turn both off, and compare against just turning off CLEANSTACK // to see if the failure is specifically due to witness validation. CValidationState stateDummy; // Want reported failures to be from first CheckInputs - if (!tx.HasWitness() && CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) && - !CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) { + if (!tx.HasWitness() && CheckInputs(tx, stateDummy, view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) && + !CheckInputs(tx, stateDummy, view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) { // Only the witness is missing, so the transaction itself may be fine. state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false, state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage()); @@ -837,7 +910,7 @@ static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPo // (`CCoinsViewCache::cacheCoins`). for (const COutPoint& hashTx : coins_to_uncache) - pcoinsTip->Uncache(hashTx); + ::ChainstateActive().CoinsTip().Uncache(hashTx); } // After we've (potentially) uncached entries, ensure our coins cache is still within its size limits CValidationState stateDummy; @@ -1017,6 +1090,40 @@ CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams) return nSubsidy; } +CoinsViews::CoinsViews( + std::string ldb_name, + size_t cache_size_bytes, + bool in_memory, + bool should_wipe) : m_dbview( + GetDataDir() / ldb_name, cache_size_bytes, in_memory, should_wipe), + m_catcherview(&m_dbview) {} + +void CoinsViews::InitCache() +{ + m_cacheview = MakeUnique<CCoinsViewCache>(&m_catcherview); +} + +// NOTE: for now m_blockman is set to a global, but this will be changed +// in a future commit. +CChainState::CChainState() : m_blockman(g_blockman) {} + + +void CChainState::InitCoinsDB( + size_t cache_size_bytes, + bool in_memory, + bool should_wipe, + std::string leveldb_name) +{ + m_coins_views = MakeUnique<CoinsViews>( + leveldb_name, cache_size_bytes, in_memory, should_wipe); +} + +void CChainState::InitCoinsCache() +{ + assert(m_coins_views != nullptr); + m_coins_views->InitCache(); +} + // Note that though this is marked const, we may end up modifying `m_cached_finished_ibd`, which // is a performance-related implementation detail. This function must be marked // `const` so that `CValidationInterface` clients (which are given a `const CChainState*`) @@ -1046,9 +1153,15 @@ bool CChainState::IsInitialBlockDownload() const static CBlockIndex *pindexBestForkTip = nullptr, *pindexBestForkBase = nullptr; +BlockMap& BlockIndex() +{ + return g_blockman.m_block_index; +} + static void AlertNotify(const std::string& strMessage) { uiInterface.NotifyAlertChanged(); +#if HAVE_SYSTEM std::string strCmd = gArgs.GetArg("-alertnotify", ""); if (strCmd.empty()) return; @@ -1062,6 +1175,7 @@ static void AlertNotify(const std::string& strMessage) std::thread t(runCommand, strCmd); t.detach(); // thread runs free +#endif } static void CheckForkWarningConditions() EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -1157,7 +1271,7 @@ void static InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(c void CChainState::InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) { if (state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; - m_failed_blocks.insert(pindex); + m_blockman.m_failed_blocks.insert(pindex); setDirtyBlockIndex.insert(pindex); setBlockIndexCandidates.erase(pindex); InvalidChainFound(pindex); @@ -1228,90 +1342,79 @@ void InitScriptExecutionCache() { * * Non-static (and re-declared) in src/test/txvalidationcache_tests.cpp */ -bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - if (!tx.IsCoinBase()) - { - if (pvChecks) - pvChecks->reserve(tx.vin.size()); - - // The first loop above does all the inexpensive checks. - // Only if ALL inputs pass do we perform expensive ECDSA signature checks. - // Helps prevent CPU exhaustion attacks. - - // Skip script verification when connecting blocks under the - // assumevalid block. Assuming the assumevalid block is valid this - // is safe because block merkle hashes are still computed and checked, - // Of course, if an assumed valid block is invalid due to false scriptSigs - // this optimization would allow an invalid chain to be accepted. - if (fScriptChecks) { - // First check if script executions have been cached with the same - // flags. Note that this assumes that the inputs provided are - // correct (ie that the transaction hash which is in tx's prevouts - // properly commits to the scriptPubKey in the inputs view of that - // transaction). - uint256 hashCacheEntry; - // We only use the first 19 bytes of nonce to avoid a second SHA - // round - giving us 19 + 32 + 4 = 55 bytes (+ 8 + 1 = 64) - static_assert(55 - sizeof(flags) - 32 >= 128/8, "Want at least 128 bits of nonce for script execution cache"); - CSHA256().Write(scriptExecutionCacheNonce.begin(), 55 - sizeof(flags) - 32).Write(tx.GetWitnessHash().begin(), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin()); - AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks - if (scriptExecutionCache.contains(hashCacheEntry, !cacheFullScriptStore)) { - return true; - } - - for (unsigned int i = 0; i < tx.vin.size(); i++) { - const COutPoint &prevout = tx.vin[i].prevout; - const Coin& coin = inputs.AccessCoin(prevout); - assert(!coin.IsSpent()); - - // We very carefully only pass in things to CScriptCheck which - // are clearly committed to by tx' witness hash. This provides - // a sanity check that our caching is not introducing consensus - // failures through additional data in, eg, the coins being - // spent being checked as a part of CScriptCheck. - - // Verify signature - CScriptCheck check(coin.out, tx, i, flags, cacheSigStore, &txdata); - if (pvChecks) { - pvChecks->push_back(CScriptCheck()); - check.swap(pvChecks->back()); - } else if (!check()) { - if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) { - // Check whether the failure was caused by a - // non-mandatory script verification check, such as - // non-standard DER encodings or non-null dummy - // arguments; if so, ensure we return NOT_STANDARD - // instead of CONSENSUS to avoid downstream users - // splitting the network between upgraded and - // non-upgraded nodes by banning CONSENSUS-failing - // data providers. - CScriptCheck check2(coin.out, tx, i, - flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata); - if (check2()) - return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); - } - // MANDATORY flag failures correspond to - // ValidationInvalidReason::CONSENSUS. Because CONSENSUS - // failures are the most serious case of validation - // failures, we may need to consider using - // RECENT_CONSENSUS_CHANGE for any script failure that - // could be due to non-upgraded nodes which we may want to - // support, to avoid splitting the network (but this - // depends on the details of how net_processing handles - // such errors). - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); - } - } + if (tx.IsCoinBase()) return true; + + if (pvChecks) { + pvChecks->reserve(tx.vin.size()); + } + + // First check if script executions have been cached with the same + // flags. Note that this assumes that the inputs provided are + // correct (ie that the transaction hash which is in tx's prevouts + // properly commits to the scriptPubKey in the inputs view of that + // transaction). + uint256 hashCacheEntry; + // We only use the first 19 bytes of nonce to avoid a second SHA + // round - giving us 19 + 32 + 4 = 55 bytes (+ 8 + 1 = 64) + static_assert(55 - sizeof(flags) - 32 >= 128/8, "Want at least 128 bits of nonce for script execution cache"); + CSHA256().Write(scriptExecutionCacheNonce.begin(), 55 - sizeof(flags) - 32).Write(tx.GetWitnessHash().begin(), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin()); + AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks + if (scriptExecutionCache.contains(hashCacheEntry, !cacheFullScriptStore)) { + return true; + } - if (cacheFullScriptStore && !pvChecks) { - // We executed all of the provided scripts, and were told to - // cache the result. Do so now. - scriptExecutionCache.insert(hashCacheEntry); + for (unsigned int i = 0; i < tx.vin.size(); i++) { + const COutPoint &prevout = tx.vin[i].prevout; + const Coin& coin = inputs.AccessCoin(prevout); + assert(!coin.IsSpent()); + + // We very carefully only pass in things to CScriptCheck which + // are clearly committed to by tx' witness hash. This provides + // a sanity check that our caching is not introducing consensus + // failures through additional data in, eg, the coins being + // spent being checked as a part of CScriptCheck. + + // Verify signature + CScriptCheck check(coin.out, tx, i, flags, cacheSigStore, &txdata); + if (pvChecks) { + pvChecks->push_back(CScriptCheck()); + check.swap(pvChecks->back()); + } else if (!check()) { + if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) { + // Check whether the failure was caused by a + // non-mandatory script verification check, such as + // non-standard DER encodings or non-null dummy + // arguments; if so, ensure we return NOT_STANDARD + // instead of CONSENSUS to avoid downstream users + // splitting the network between upgraded and + // non-upgraded nodes by banning CONSENSUS-failing + // data providers. + CScriptCheck check2(coin.out, tx, i, + flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata); + if (check2()) + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); } + // MANDATORY flag failures correspond to + // ValidationInvalidReason::CONSENSUS. Because CONSENSUS + // failures are the most serious case of validation + // failures, we may need to consider using + // RECENT_CONSENSUS_CHANGE for any script failure that + // could be due to non-upgraded nodes which we may want to + // support, to avoid splitting the network (but this + // depends on the details of how net_processing handles + // such errors). + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); } } + if (cacheFullScriptStore && !pvChecks) { + // We executed all of the provided scripts, and were told to + // cache the result. Do so now. + scriptExecutionCache.insert(hashCacheEntry); + } + return true; } @@ -1381,7 +1484,7 @@ static bool AbortNode(const std::string& strMessage, const std::string& userMess if (!userMessage.empty()) { uiInterface.ThreadSafeMessageBox(userMessage, "", CClientUIInterface::MSG_ERROR | prefix); } else { - uiInterface.ThreadSafeMessageBox(_("Error: A fatal internal error occurred, see debug.log for details"), "", CClientUIInterface::MSG_ERROR | CClientUIInterface::MSG_NOPREFIX); + uiInterface.ThreadSafeMessageBox(_("Error: A fatal internal error occurred, see debug.log for details").translated, "", CClientUIInterface::MSG_ERROR | CClientUIInterface::MSG_NOPREFIX); } StartShutdown(); return false; @@ -1578,7 +1681,7 @@ static ThresholdConditionCache warningcache[VERSIONBITS_NUM_BITS] GUARDED_BY(cs_ // environment. See test/functional/p2p-segwit.py. static bool IsScriptWitnessEnabled(const Consensus::Params& params) { - return params.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nTimeout != 0; + return params.SegwitHeight != std::numeric_limits<int>::max(); } static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consensus::Params& consensusparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { @@ -1614,12 +1717,13 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consens flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY; } - // Start enforcing BIP68 (sequence locks) and BIP112 (CHECKSEQUENCEVERIFY) using versionbits logic. - if (VersionBitsState(pindex->pprev, consensusparams, Consensus::DEPLOYMENT_CSV, versionbitscache) == ThresholdState::ACTIVE) { + // Start enforcing BIP112 (CHECKSEQUENCEVERIFY) + if (pindex->nHeight >= consensusparams.CSVHeight) { flags |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY; } - if (IsNullDummyEnabled(pindex->pprev, consensusparams)) { + // Start enforcing BIP147 NULLDUMMY (activated simultaneously with segwit) + if (IsWitnessEnabled(pindex->pprev, consensusparams)) { flags |= SCRIPT_VERIFY_NULLDUMMY; } @@ -1692,12 +1796,17 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // relative to a piece of software is an objective fact these defaults can be easily reviewed. // This setting doesn't force the selection of any particular chain but makes validating some faster by // effectively caching the result of part of the verification. - BlockMap::const_iterator it = mapBlockIndex.find(hashAssumeValid); - if (it != mapBlockIndex.end()) { + BlockMap::const_iterator it = m_blockman.m_block_index.find(hashAssumeValid); + if (it != m_blockman.m_block_index.end()) { if (it->second->GetAncestor(pindex->nHeight) == pindex && pindexBestHeader->GetAncestor(pindex->nHeight) == pindex && pindexBestHeader->nChainWork >= nMinimumChainWork) { // This block is a member of the assumed verified chain and an ancestor of the best header. + // Script verification is skipped when connecting blocks under the + // assumevalid block. Assuming the assumevalid block is valid this + // is safe because block merkle hashes are still computed and checked, + // Of course, if an assumed valid block is invalid due to false scriptSigs + // this optimization would allow an invalid chain to be accepted. // The equivalent time check discourages hash power from extorting the network via DOS attack // into accepting an invalid block through telling users they must manually set assumevalid. // Requiring a software change or burying the invalid block, regardless of the setting, makes @@ -1804,9 +1913,9 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl } } - // Start enforcing BIP68 (sequence locks) and BIP112 (CHECKSEQUENCEVERIFY) using versionbits logic. + // Start enforcing BIP68 (sequence locks) int nLockTimeFlags = 0; - if (VersionBitsState(pindex->pprev, chainparams.GetConsensus(), Consensus::DEPLOYMENT_CSV, versionbitscache) == ThresholdState::ACTIVE) { + if (pindex->nHeight >= chainparams.GetConsensus().CSVHeight) { nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE; } @@ -1881,7 +1990,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl { std::vector<CScriptCheck> vChecks; bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */ - if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) { + if (fScriptChecks && !CheckInputs(tx, state, view, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) { if (state.GetReason() == ValidationInvalidReason::TX_NOT_STANDARD) { // CheckInputs may return NOT_STANDARD for extra flags we passed, // but we can't return that, as it's not defined for a block, so @@ -1951,6 +2060,7 @@ bool CChainState::FlushStateToDisk( { int64_t nMempoolUsage = mempool.DynamicMemoryUsage(); LOCK(cs_main); + assert(this->CanFlushToDisk()); static int64_t nLastWrite = 0; static int64_t nLastFlush = 0; std::set<int> setFilesToPrune; @@ -1984,7 +2094,7 @@ bool CChainState::FlushStateToDisk( nLastFlush = nNow; } int64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; - int64_t cacheSize = pcoinsTip->DynamicMemoryUsage(); + int64_t cacheSize = CoinsTip().DynamicMemoryUsage(); int64_t nTotalSpace = nCoinCacheUsage + std::max<int64_t>(nMempoolSizeMax - nMempoolUsage, 0); // The cache is large and we're within 10% and 10 MiB of the limit, but we have time now (not in the middle of a block processing). bool fCacheLarge = mode == FlushStateMode::PERIODIC && cacheSize > std::max((9 * nTotalSpace) / 10, nTotalSpace - MAX_BLOCK_COINSDB_USAGE * 1024 * 1024); @@ -2000,7 +2110,7 @@ bool CChainState::FlushStateToDisk( if (fDoFullFlush || fPeriodicWrite) { // Depend on nMinDiskSpace to ensure we can write block index if (!CheckDiskSpace(GetBlocksDir())) { - return AbortNode(state, "Disk space is too low!", _("Error: Disk space is too low!"), CClientUIInterface::MSG_NOPREFIX); + return AbortNode(state, "Disk space is too low!", _("Error: Disk space is too low!").translated, CClientUIInterface::MSG_NOPREFIX); } // First make sure all block and undo data is flushed to disk. FlushBlockFile(); @@ -2028,17 +2138,17 @@ bool CChainState::FlushStateToDisk( nLastWrite = nNow; } // Flush best chain related state. This can only be done if the blocks / block index write was also done. - if (fDoFullFlush && !pcoinsTip->GetBestBlock().IsNull()) { + if (fDoFullFlush && !CoinsTip().GetBestBlock().IsNull()) { // Typical Coin structures on disk are around 48 bytes in size. // Pushing a new one to the database can cause it to be written // twice (once in the log, and once in the tables). This is already // an overestimation, as most will delete an existing entry or // overwrite one. Still, use a conservative safety factor of 2. - if (!CheckDiskSpace(GetDataDir(), 48 * 2 * 2 * pcoinsTip->GetCacheSize())) { - return AbortNode(state, "Disk space is too low!", _("Error: Disk space is too low!"), CClientUIInterface::MSG_NOPREFIX); + if (!CheckDiskSpace(GetDataDir(), 48 * 2 * 2 * CoinsTip().GetCacheSize())) { + return AbortNode(state, "Disk space is too low!", _("Error: Disk space is too low!").translated, CClientUIInterface::MSG_NOPREFIX); } // Flush the chainstate (which may refer to block index entries). - if (!pcoinsTip->Flush()) + if (!CoinsTip().Flush()) return AbortNode(state, "Failed to write to coin database"); nLastFlush = nNow; full_flush_completed = true; @@ -2090,7 +2200,9 @@ static void AppendWarning(std::string& res, const std::string& warn) } /** Check warning conditions and do some notifications on new chain tip set. */ -void static UpdateTip(const CBlockIndex *pindexNew, const CChainParams& chainParams) { +void static UpdateTip(const CBlockIndex* pindexNew, const CChainParams& chainParams) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main) +{ // New best block mempool.AddTransactionsUpdated(1); @@ -2109,7 +2221,7 @@ void static UpdateTip(const CBlockIndex *pindexNew, const CChainParams& chainPar WarningBitsConditionChecker checker(bit); ThresholdState state = checker.GetStateFor(pindex, chainParams.GetConsensus(), warningcache[bit]); if (state == ThresholdState::ACTIVE || state == ThresholdState::LOCKED_IN) { - const std::string strWarning = strprintf(_("Warning: unknown new rules activated (versionbit %i)"), bit); + const std::string strWarning = strprintf(_("Warning: unknown new rules activated (versionbit %i)").translated, bit); if (state == ThresholdState::ACTIVE) { DoWarning(strWarning); } else { @@ -2126,13 +2238,13 @@ void static UpdateTip(const CBlockIndex *pindexNew, const CChainParams& chainPar pindex = pindex->pprev; } if (nUpgraded > 0) - AppendWarning(warningMessages, strprintf(_("%d of last 100 blocks have unexpected version"), nUpgraded)); + AppendWarning(warningMessages, strprintf(_("%d of last 100 blocks have unexpected version").translated, nUpgraded)); } LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)", __func__, /* Continued */ pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, pindexNew->nVersion, log(pindexNew->nChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx, FormatISO8601DateTime(pindexNew->GetBlockTime()), - GuessVerificationProgress(chainParams.TxData(), pindexNew), pcoinsTip->DynamicMemoryUsage() * (1.0 / (1<<20)), pcoinsTip->GetCacheSize()); + GuessVerificationProgress(chainParams.TxData(), pindexNew), ::ChainstateActive().CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), ::ChainstateActive().CoinsTip().GetCacheSize()); if (!warningMessages.empty()) LogPrintf(" warning='%s'", warningMessages); /* Continued */ LogPrintf("\n"); @@ -2157,11 +2269,11 @@ bool CChainState::DisconnectTip(CValidationState& state, const CChainParams& cha std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>(); CBlock& block = *pblock; if (!ReadBlockFromDisk(block, pindexDelete, chainparams.GetConsensus())) - return AbortNode(state, "Failed to read block"); + return error("DisconnectTip(): Failed to read block"); // Apply the block atomically to the chain state. int64_t nStart = GetTimeMicros(); { - CCoinsViewCache view(pcoinsTip.get()); + CCoinsViewCache view(&CoinsTip()); assert(view.GetBestBlock() == pindexDelete->GetBlockHash()); if (DisconnectBlock(block, pindexDelete, view) != DISCONNECT_OK) return error("DisconnectTip(): DisconnectBlock %s failed", pindexDelete->GetBlockHash().ToString()); @@ -2289,7 +2401,7 @@ bool CChainState::ConnectTip(CValidationState& state, const CChainParams& chainp int64_t nTime3; LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs]\n", (nTime2 - nTime1) * MILLI, nTimeReadFromDisk * MICRO); { - CCoinsViewCache view(pcoinsTip.get()); + CCoinsViewCache view(&CoinsTip()); bool rv = ConnectBlock(blockConnecting, state, pindexNew, view, chainparams); GetMainSignals().BlockChecked(blockConnecting, state); if (!rv) { @@ -2363,10 +2475,11 @@ CBlockIndex* CChainState::FindMostWorkChain() { if (fFailedChain) { pindexFailed->nStatus |= BLOCK_FAILED_CHILD; } else if (fMissingData) { - // If we're missing data, then add back to mapBlocksUnlinked, + // If we're missing data, then add back to m_blocks_unlinked, // so that if the block arrives in the future we can try adding // to setBlockIndexCandidates again. - mapBlocksUnlinked.insert(std::make_pair(pindexFailed->pprev, pindexFailed)); + m_blockman.m_blocks_unlinked.insert( + std::make_pair(pindexFailed->pprev, pindexFailed)); } setBlockIndexCandidates.erase(pindexFailed); pindexFailed = pindexFailed->pprev; @@ -2413,6 +2526,11 @@ bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainPar // This is likely a fatal error, but keep the mempool consistent, // just in case. Only remove from the mempool in this case. UpdateMempoolForReorg(disconnectpool, false); + + // If we're unable to disconnect a block during normal operation, + // then that is a failure of our local system -- we should abort + // rather than stay on a less work chain. + AbortNode(state, "Failed to disconnect block; see debug.log for details"); return false; } fBlocksDisconnected = true; @@ -2470,7 +2588,7 @@ bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainPar // any disconnected transactions back to the mempool. UpdateMempoolForReorg(disconnectpool, true); } - mempool.check(pcoinsTip.get()); + mempool.check(&CoinsTip()); // Callbacks/notifications for a new best chain. if (fInvalidFound) @@ -2481,7 +2599,7 @@ bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainPar return true; } -static void NotifyHeaderTip() LOCKS_EXCLUDED(cs_main) { +static bool NotifyHeaderTip() LOCKS_EXCLUDED(cs_main) { bool fNotify = false; bool fInitialBlockDownload = false; static CBlockIndex* pindexHeaderOld = nullptr; @@ -2500,6 +2618,7 @@ static void NotifyHeaderTip() LOCKS_EXCLUDED(cs_main) { if (fNotify) { uiInterface.NotifyHeaderTip(fInitialBlockDownload, pindexHeader); } + return fNotify; } static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) { @@ -2547,7 +2666,7 @@ bool CChainState::ActivateBestChain(CValidationState &state, const CChainParams& LimitValidationInterfaceQueue(); { - LOCK(cs_main); + LOCK2(cs_main, ::mempool.cs); // Lock transaction pool for at least as long as it takes for connectTrace to be consumed CBlockIndex* starting_tip = m_chain.Tip(); bool blocks_connected = false; do { @@ -2667,6 +2786,7 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c LimitValidationInterfaceQueue(); LOCK(cs_main); + LOCK(::mempool.cs); // Lock for as long as disconnectpool is in scope to make sure UpdateMempoolForReorg is called after DisconnectTip without unlocking in between if (!m_chain.Contains(pindex)) break; pindex_was_in_chain = true; CBlockIndex *invalid_walk_tip = m_chain.Tip(); @@ -2716,12 +2836,12 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c to_mark_failed->nStatus |= BLOCK_FAILED_VALID; setDirtyBlockIndex.insert(to_mark_failed); setBlockIndexCandidates.erase(to_mark_failed); - m_failed_blocks.insert(to_mark_failed); + m_blockman.m_failed_blocks.insert(to_mark_failed); // The resulting new best tip may not be in setBlockIndexCandidates anymore, so // add it again. - BlockMap::iterator it = mapBlockIndex.begin(); - while (it != mapBlockIndex.end()) { + BlockMap::iterator it = m_blockman.m_block_index.begin(); + while (it != m_blockman.m_block_index.end()) { if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->HaveTxsDownloaded() && !setBlockIndexCandidates.value_comp()(it->second, m_chain.Tip())) { setBlockIndexCandidates.insert(it->second); } @@ -2748,8 +2868,8 @@ void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { int nHeight = pindex->nHeight; // Remove the invalidity flag from this block and all its descendants. - BlockMap::iterator it = mapBlockIndex.begin(); - while (it != mapBlockIndex.end()) { + BlockMap::iterator it = m_blockman.m_block_index.begin(); + while (it != m_blockman.m_block_index.end()) { if (!it->second->IsValid() && it->second->GetAncestor(nHeight) == pindex) { it->second->nStatus &= ~BLOCK_FAILED_MASK; setDirtyBlockIndex.insert(it->second); @@ -2760,7 +2880,7 @@ void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { // Reset invalid block marker if it was pointing to one of those. pindexBestInvalid = nullptr; } - m_failed_blocks.erase(it->second); + m_blockman.m_failed_blocks.erase(it->second); } it++; } @@ -2770,7 +2890,7 @@ void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { if (pindex->nStatus & BLOCK_FAILED_MASK) { pindex->nStatus &= ~BLOCK_FAILED_MASK; setDirtyBlockIndex.insert(pindex); - m_failed_blocks.erase(pindex); + m_blockman.m_failed_blocks.erase(pindex); } pindex = pindex->pprev; } @@ -2780,14 +2900,14 @@ void ResetBlockFailureFlags(CBlockIndex *pindex) { return ::ChainstateActive().ResetBlockFailureFlags(pindex); } -CBlockIndex* CChainState::AddToBlockIndex(const CBlockHeader& block) +CBlockIndex* BlockManager::AddToBlockIndex(const CBlockHeader& block) { AssertLockHeld(cs_main); // Check for duplicate uint256 hash = block.GetHash(); - BlockMap::iterator it = mapBlockIndex.find(hash); - if (it != mapBlockIndex.end()) + BlockMap::iterator it = m_block_index.find(hash); + if (it != m_block_index.end()) return it->second; // Construct new block index object @@ -2796,10 +2916,10 @@ CBlockIndex* CChainState::AddToBlockIndex(const CBlockHeader& block) // to avoid miners withholding blocks but broadcasting headers, to get a // competitive advantage. pindexNew->nSequenceId = 0; - BlockMap::iterator mi = mapBlockIndex.insert(std::make_pair(hash, pindexNew)).first; + BlockMap::iterator mi = m_block_index.insert(std::make_pair(hash, pindexNew)).first; pindexNew->phashBlock = &((*mi).first); - BlockMap::iterator miPrev = mapBlockIndex.find(block.hashPrevBlock); - if (miPrev != mapBlockIndex.end()) + BlockMap::iterator miPrev = m_block_index.find(block.hashPrevBlock); + if (miPrev != m_block_index.end()) { pindexNew->pprev = (*miPrev).second; pindexNew->nHeight = pindexNew->pprev->nHeight + 1; @@ -2848,17 +2968,17 @@ void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pi if (m_chain.Tip() == nullptr || !setBlockIndexCandidates.value_comp()(pindex, m_chain.Tip())) { setBlockIndexCandidates.insert(pindex); } - std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> range = mapBlocksUnlinked.equal_range(pindex); + std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> range = m_blockman.m_blocks_unlinked.equal_range(pindex); while (range.first != range.second) { std::multimap<CBlockIndex*, CBlockIndex*>::iterator it = range.first; queue.push_back(it->second); range.first++; - mapBlocksUnlinked.erase(it); + m_blockman.m_blocks_unlinked.erase(it); } } } else { if (pindexNew->pprev && pindexNew->pprev->IsValid(BLOCK_VALID_TREE)) { - mapBlocksUnlinked.insert(std::make_pair(pindexNew->pprev, pindexNew)); + m_blockman.m_blocks_unlinked.insert(std::make_pair(pindexNew->pprev, pindexNew)); } } } @@ -2901,7 +3021,7 @@ static bool FindBlockPos(FlatFilePos &pos, unsigned int nAddSize, unsigned int n bool out_of_space; size_t bytes_allocated = BlockFileSeq().Allocate(pos, nAddSize, out_of_space); if (out_of_space) { - return AbortNode("Disk space is too low!", _("Error: Disk space is too low!"), CClientUIInterface::MSG_NOPREFIX); + return AbortNode("Disk space is too low!", _("Error: Disk space is too low!").translated, CClientUIInterface::MSG_NOPREFIX); } if (bytes_allocated != 0 && fPruneMode) { fCheckForPruning = true; @@ -2925,7 +3045,7 @@ static bool FindUndoPos(CValidationState &state, int nFile, FlatFilePos &pos, un bool out_of_space; size_t bytes_allocated = UndoFileSeq().Allocate(pos, nAddSize, out_of_space); if (out_of_space) { - return AbortNode(state, "Disk space is too low!", _("Error: Disk space is too low!"), CClientUIInterface::MSG_NOPREFIX); + return AbortNode(state, "Disk space is too low!", _("Error: Disk space is too low!").translated, CClientUIInterface::MSG_NOPREFIX); } if (bytes_allocated != 0 && fPruneMode) { fCheckForPruning = true; @@ -3008,14 +3128,8 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params) { - LOCK(cs_main); - return (VersionBitsState(pindexPrev, params, Consensus::DEPLOYMENT_SEGWIT, versionbitscache) == ThresholdState::ACTIVE); -} - -bool IsNullDummyEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params) -{ - LOCK(cs_main); - return (VersionBitsState(pindexPrev, params, Consensus::DEPLOYMENT_SEGWIT, versionbitscache) == ThresholdState::ACTIVE); + int height = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; + return (height >= params.SegwitHeight); } // Compute at which vout of the block's coinbase transaction the witness @@ -3050,7 +3164,7 @@ std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBloc std::vector<unsigned char> commitment; int commitpos = GetWitnessCommitmentIndex(block); std::vector<unsigned char> ret(32, 0x00); - if (consensusParams.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nTimeout != 0) { + if (consensusParams.SegwitHeight != std::numeric_limits<int>::max()) { if (commitpos == -1) { uint256 witnessroot = BlockWitnessMerkleRoot(block, nullptr); CHash256().Write(witnessroot.begin(), 32).Write(ret.data(), 32).Finalize(witnessroot.begin()); @@ -3113,7 +3227,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta if (fCheckpointsEnabled) { // Don't accept any forks from the main chain prior to last checkpoint. // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our - // MapBlockIndex. + // g_blockman.m_block_index. CBlockIndex* pcheckpoint = GetLastCheckpoint(params.Checkpoints()); if (pcheckpoint && nHeight < pcheckpoint->nHeight) return state.Invalid(ValidationInvalidReason::BLOCK_CHECKPOINT, error("%s: forked chain older than last checkpoint (height %d)", __func__, nHeight), REJECT_CHECKPOINT, "bad-fork-prior-to-checkpoint"); @@ -3148,9 +3262,9 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c { const int nHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; - // Start enforcing BIP113 (Median Time Past) using versionbits logic. + // Start enforcing BIP113 (Median Time Past). int nLockTimeFlags = 0; - if (VersionBitsState(pindexPrev, consensusParams, Consensus::DEPLOYMENT_CSV, versionbitscache) == ThresholdState::ACTIVE) { + if (nHeight >= consensusParams.CSVHeight) { assert(pindexPrev != nullptr); nLockTimeFlags |= LOCKTIME_MEDIAN_TIME_PAST; } @@ -3185,7 +3299,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c // {0xaa, 0x21, 0xa9, 0xed}, and the following 32 bytes are SHA256^2(witness root, witness reserved value). In case there are // multiple, the last one is used. bool fHaveWitness = false; - if (VersionBitsState(pindexPrev, consensusParams, Consensus::DEPLOYMENT_SEGWIT, versionbitscache) == ThresholdState::ACTIVE) { + if (nHeight >= consensusParams.SegwitHeight) { int commitpos = GetWitnessCommitmentIndex(block); if (commitpos != -1) { bool malleated = false; @@ -3226,15 +3340,15 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c return true; } -bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex) +bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex) { AssertLockHeld(cs_main); // Check for duplicate uint256 hash = block.GetHash(); - BlockMap::iterator miSelf = mapBlockIndex.find(hash); + BlockMap::iterator miSelf = m_block_index.find(hash); CBlockIndex *pindex = nullptr; if (hash != chainparams.GetConsensus().hashGenesisBlock) { - if (miSelf != mapBlockIndex.end()) { + if (miSelf != m_block_index.end()) { // Block header is already known. pindex = miSelf->second; if (ppindex) @@ -3249,8 +3363,8 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& // Get prev block index CBlockIndex* pindexPrev = nullptr; - BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); - if (mi == mapBlockIndex.end()) + BlockMap::iterator mi = m_block_index.find(block.hashPrevBlock); + if (mi == m_block_index.end()) return state.Invalid(ValidationInvalidReason::BLOCK_MISSING_PREV, error("%s: prev block not found", __func__), 0, "prev-blk-not-found"); pindexPrev = (*mi).second; if (pindexPrev->nStatus & BLOCK_FAILED_MASK) @@ -3302,8 +3416,6 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& if (ppindex) *ppindex = pindex; - CheckBlockIndex(chainparams.GetConsensus()); - return true; } @@ -3315,7 +3427,10 @@ bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, CValidatio LOCK(cs_main); for (const CBlockHeader& header : headers) { CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast - if (!::ChainstateActive().AcceptBlockHeader(header, state, chainparams, &pindex)) { + bool accepted = g_blockman.AcceptBlockHeader(header, state, chainparams, &pindex); + ::ChainstateActive().CheckBlockIndex(chainparams.GetConsensus()); + + if (!accepted) { if (first_invalid) *first_invalid = header; return false; } @@ -3324,7 +3439,11 @@ bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, CValidatio } } } - NotifyHeaderTip(); + if (NotifyHeaderTip()) { + if (::ChainstateActive().IsInitialBlockDownload() && ppindex && *ppindex) { + LogPrintf("Synchronizing blockheaders, height: %d (~%.2f%%)\n", (*ppindex)->nHeight, 100.0/((*ppindex)->nHeight+(GetAdjustedTime() - (*ppindex)->GetBlockTime()) / Params().GetConsensus().nPowTargetSpacing) * (*ppindex)->nHeight); + } + } return true; } @@ -3358,7 +3477,10 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CVali CBlockIndex *pindexDummy = nullptr; CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy; - if (!AcceptBlockHeader(block, state, chainparams, &pindex)) + bool accepted_header = m_blockman.AcceptBlockHeader(block, state, chainparams, &pindex); + CheckBlockIndex(chainparams.GetConsensus()); + + if (!accepted_header) return false; // Try to process all requested blocks that we don't have, but only @@ -3467,7 +3589,7 @@ bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams, { AssertLockHeld(cs_main); assert(pindexPrev && pindexPrev == ::ChainActive().Tip()); - CCoinsViewCache viewNew(pcoinsTip.get()); + CCoinsViewCache viewNew(&::ChainstateActive().CoinsTip()); uint256 block_hash(block.GetHash()); CBlockIndex indexDummy(block); indexDummy.pprev = pindexPrev; @@ -3509,7 +3631,7 @@ void PruneOneBlockFile(const int fileNumber) { LOCK(cs_LastBlockFile); - for (const auto& entry : mapBlockIndex) { + for (const auto& entry : g_blockman.m_block_index) { CBlockIndex* pindex = entry.second; if (pindex->nFile == fileNumber) { pindex->nStatus &= ~BLOCK_HAVE_DATA; @@ -3519,16 +3641,16 @@ void PruneOneBlockFile(const int fileNumber) pindex->nUndoPos = 0; setDirtyBlockIndex.insert(pindex); - // Prune from mapBlocksUnlinked -- any block we prune would have + // Prune from m_blocks_unlinked -- any block we prune would have // to be downloaded again in order to consider its chain, at which // point it would be considered as a candidate for - // mapBlocksUnlinked or setBlockIndexCandidates. - std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> range = mapBlocksUnlinked.equal_range(pindex->pprev); + // m_blocks_unlinked or setBlockIndexCandidates. + auto range = g_blockman.m_blocks_unlinked.equal_range(pindex->pprev); while (range.first != range.second) { std::multimap<CBlockIndex *, CBlockIndex *>::iterator _it = range.first; range.first++; if (_it->second == pindex) { - mapBlocksUnlinked.erase(_it); + g_blockman.m_blocks_unlinked.erase(_it); } } } @@ -3677,7 +3799,7 @@ fs::path GetBlockPosFilename(const FlatFilePos &pos) return BlockFileSeq().FileName(pos); } -CBlockIndex * CChainState::InsertBlockIndex(const uint256& hash) +CBlockIndex * BlockManager::InsertBlockIndex(const uint256& hash) { AssertLockHeld(cs_main); @@ -3685,27 +3807,30 @@ CBlockIndex * CChainState::InsertBlockIndex(const uint256& hash) return nullptr; // Return existing - BlockMap::iterator mi = mapBlockIndex.find(hash); - if (mi != mapBlockIndex.end()) + BlockMap::iterator mi = m_block_index.find(hash); + if (mi != m_block_index.end()) return (*mi).second; // Create new CBlockIndex* pindexNew = new CBlockIndex(); - mi = mapBlockIndex.insert(std::make_pair(hash, pindexNew)).first; + mi = m_block_index.insert(std::make_pair(hash, pindexNew)).first; pindexNew->phashBlock = &((*mi).first); return pindexNew; } -bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlockTreeDB& blocktree) +bool BlockManager::LoadBlockIndex( + const Consensus::Params& consensus_params, + CBlockTreeDB& blocktree, + std::set<CBlockIndex*, CBlockIndexWorkComparator>& block_index_candidates) { if (!blocktree.LoadBlockIndexGuts(consensus_params, [this](const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return this->InsertBlockIndex(hash); })) return false; // Calculate nChainWork std::vector<std::pair<int, CBlockIndex*> > vSortedByHeight; - vSortedByHeight.reserve(mapBlockIndex.size()); - for (const std::pair<const uint256, CBlockIndex*>& item : mapBlockIndex) + vSortedByHeight.reserve(m_block_index.size()); + for (const std::pair<const uint256, CBlockIndex*>& item : m_block_index) { CBlockIndex* pindex = item.second; vSortedByHeight.push_back(std::make_pair(pindex->nHeight, pindex)); @@ -3725,7 +3850,7 @@ bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlo pindex->nChainTx = pindex->pprev->nChainTx + pindex->nTx; } else { pindex->nChainTx = 0; - mapBlocksUnlinked.insert(std::make_pair(pindex->pprev, pindex)); + m_blocks_unlinked.insert(std::make_pair(pindex->pprev, pindex)); } } else { pindex->nChainTx = pindex->nTx; @@ -3735,8 +3860,9 @@ bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlo pindex->nStatus |= BLOCK_FAILED_CHILD; setDirtyBlockIndex.insert(pindex); } - if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr)) - setBlockIndexCandidates.insert(pindex); + if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr)) { + block_index_candidates.insert(pindex); + } if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork)) pindexBestInvalid = pindex; if (pindex->pprev) @@ -3748,9 +3874,21 @@ bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlo return true; } +void BlockManager::Unload() { + m_failed_blocks.clear(); + m_blocks_unlinked.clear(); + + for (const BlockMap::value_type& entry : m_block_index) { + delete entry.second; + } + + m_block_index.clear(); +} + bool static LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - if (!::ChainstateActive().LoadBlockIndex(chainparams.GetConsensus(), *pblocktree)) + if (!g_blockman.LoadBlockIndex( + chainparams.GetConsensus(), *pblocktree, ::ChainstateActive().setBlockIndexCandidates)) return false; // Load block file info @@ -3773,7 +3911,7 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_RE // Check presence of blk files LogPrintf("Checking all blk files are present...\n"); std::set<int> setBlkDataFiles; - for (const std::pair<const uint256, CBlockIndex*>& item : mapBlockIndex) + for (const std::pair<const uint256, CBlockIndex*>& item : g_blockman.m_block_index) { CBlockIndex* pindex = item.second; if (pindex->nStatus & BLOCK_HAVE_DATA) { @@ -3804,12 +3942,14 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_RE bool LoadChainTip(const CChainParams& chainparams) { AssertLockHeld(cs_main); - assert(!pcoinsTip->GetBestBlock().IsNull()); // Never called when the coins view is empty + const CCoinsViewCache& coins_cache = ::ChainstateActive().CoinsTip(); + assert(!coins_cache.GetBestBlock().IsNull()); // Never called when the coins view is empty - if (::ChainActive().Tip() && ::ChainActive().Tip()->GetBlockHash() == pcoinsTip->GetBestBlock()) return true; + if (::ChainActive().Tip() && + ::ChainActive().Tip()->GetBlockHash() == coins_cache.GetBestBlock()) return true; // Load pointer to end of best chain - CBlockIndex* pindex = LookupBlockIndex(pcoinsTip->GetBestBlock()); + CBlockIndex* pindex = LookupBlockIndex(coins_cache.GetBestBlock()); if (!pindex) { return false; } @@ -3826,7 +3966,7 @@ bool LoadChainTip(const CChainParams& chainparams) CVerifyDB::CVerifyDB() { - uiInterface.ShowProgress(_("Verifying blocks..."), 0, false); + uiInterface.ShowProgress(_("Verifying blocks...").translated, 0, false); } CVerifyDB::~CVerifyDB() @@ -3860,7 +4000,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, LogPrintf("[%d%%]...", percentageDone); /* Continued */ reportDone = percentageDone/10; } - uiInterface.ShowProgress(_("Verifying blocks..."), percentageDone, false); + uiInterface.ShowProgress(_("Verifying blocks...").translated, percentageDone, false); if (pindex->nHeight <= ::ChainActive().Height()-nCheckDepth) break; if (fPruneMode && !(pindex->nStatus & BLOCK_HAVE_DATA)) { @@ -3886,7 +4026,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, } } // check level 3: check for inconsistencies during memory-only disconnect of tip blocks - if (nCheckLevel >= 3 && (coins.DynamicMemoryUsage() + pcoinsTip->DynamicMemoryUsage()) <= nCoinCacheUsage) { + if (nCheckLevel >= 3 && (coins.DynamicMemoryUsage() + ::ChainstateActive().CoinsTip().DynamicMemoryUsage()) <= nCoinCacheUsage) { assert(coins.GetBestBlock() == pindex->GetBlockHash()); DisconnectResult res = ::ChainstateActive().DisconnectBlock(block, pindex, coins); if (res == DISCONNECT_FAILED) { @@ -3918,7 +4058,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, LogPrintf("[%d%%]...", percentageDone); /* Continued */ reportDone = percentageDone/10; } - uiInterface.ShowProgress(_("Verifying blocks..."), percentageDone, false); + uiInterface.ShowProgress(_("Verifying blocks...").translated, percentageDone, false); pindex = ::ChainActive().Next(pindex); CBlock block; if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus())) @@ -3965,23 +4105,23 @@ bool CChainState::ReplayBlocks(const CChainParams& params, CCoinsView* view) if (hashHeads.empty()) return true; // We're already in a consistent state. if (hashHeads.size() != 2) return error("ReplayBlocks(): unknown inconsistent state"); - uiInterface.ShowProgress(_("Replaying blocks..."), 0, false); + uiInterface.ShowProgress(_("Replaying blocks...").translated, 0, false); LogPrintf("Replaying blocks\n"); const CBlockIndex* pindexOld = nullptr; // Old tip during the interrupted flush. const CBlockIndex* pindexNew; // New tip during the interrupted flush. const CBlockIndex* pindexFork = nullptr; // Latest block common to both the old and the new tip. - if (mapBlockIndex.count(hashHeads[0]) == 0) { + if (m_blockman.m_block_index.count(hashHeads[0]) == 0) { return error("ReplayBlocks(): reorganization to unknown block requested"); } - pindexNew = mapBlockIndex[hashHeads[0]]; + pindexNew = m_blockman.m_block_index[hashHeads[0]]; if (!hashHeads[1].IsNull()) { // The old tip is allowed to be 0, indicating it's the first flush. - if (mapBlockIndex.count(hashHeads[1]) == 0) { + if (m_blockman.m_block_index.count(hashHeads[1]) == 0) { return error("ReplayBlocks(): reorganization from unknown block requested"); } - pindexOld = mapBlockIndex[hashHeads[1]]; + pindexOld = m_blockman.m_block_index[hashHeads[1]]; pindexFork = LastCommonAncestor(pindexOld, pindexNew); assert(pindexFork != nullptr); } @@ -4011,7 +4151,7 @@ bool CChainState::ReplayBlocks(const CChainParams& params, CCoinsView* view) for (int nHeight = nForkHeight + 1; nHeight <= pindexNew->nHeight; ++nHeight) { const CBlockIndex* pindex = pindexNew->GetAncestor(nHeight); LogPrintf("Rolling forward %s (%i)\n", pindex->GetBlockHash().ToString(), nHeight); - uiInterface.ShowProgress(_("Replaying blocks..."), (int) ((nHeight - nForkHeight) * 100.0 / (pindexNew->nHeight - nForkHeight)) , false); + uiInterface.ShowProgress(_("Replaying blocks...").translated, (int) ((nHeight - nForkHeight) * 100.0 / (pindexNew->nHeight - nForkHeight)) , false); if (!RollforwardBlock(pindex, cache, params)) return false; } @@ -4047,10 +4187,10 @@ void CChainState::EraseBlockData(CBlockIndex* index) setDirtyBlockIndex.insert(index); // Update indexes setBlockIndexCandidates.erase(index); - std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> ret = mapBlocksUnlinked.equal_range(index->pprev); + auto ret = m_blockman.m_blocks_unlinked.equal_range(index->pprev); while (ret.first != ret.second) { if (ret.first->second == index) { - mapBlocksUnlinked.erase(ret.first++); + m_blockman.m_blocks_unlinked.erase(ret.first++); } else { ++ret.first; } @@ -4070,7 +4210,7 @@ bool CChainState::RewindBlockIndex(const CChainParams& params) // blocks will be dealt with below (releasing cs_main in between). { LOCK(cs_main); - for (const auto& entry : mapBlockIndex) { + for (const auto& entry : m_blockman.m_block_index) { if (IsWitnessEnabled(entry.second->pprev, params.GetConsensus()) && !(entry.second->nStatus & BLOCK_OPT_WITNESS) && !m_chain.Contains(entry.second)) { EraseBlockData(entry.second); } @@ -4100,7 +4240,7 @@ bool CChainState::RewindBlockIndex(const CChainParams& params) // Loop until the tip is below nHeight, or we reach a pruned block. while (!ShutdownRequested()) { { - LOCK(cs_main); + LOCK2(cs_main, ::mempool.cs); // Make sure nothing changed from under us (this won't happen because RewindBlockIndex runs before importing/network are active) assert(tip == m_chain.Tip()); if (tip == nullptr || tip->nHeight < nHeight) break; @@ -4176,7 +4316,6 @@ bool RewindBlockIndex(const CChainParams& params) { void CChainState::UnloadBlockIndex() { nBlockSequenceId = 1; - m_failed_blocks.clear(); setBlockIndexCandidates.clear(); } @@ -4187,10 +4326,10 @@ void UnloadBlockIndex() { LOCK(cs_main); ::ChainActive().SetTip(nullptr); + g_blockman.Unload(); pindexBestInvalid = nullptr; pindexBestHeader = nullptr; mempool.clear(); - mapBlocksUnlinked.clear(); vinfoBlockFile.clear(); nLastBlockFile = 0; setDirtyBlockIndex.clear(); @@ -4199,11 +4338,6 @@ void UnloadBlockIndex() for (int b = 0; b < VERSIONBITS_NUM_BITS; b++) { warningcache[b].clear(); } - - for (const BlockMap::value_type& entry : mapBlockIndex) { - delete entry.second; - } - mapBlockIndex.clear(); fHavePruned = false; ::ChainstateActive().UnloadBlockIndex(); @@ -4216,7 +4350,7 @@ bool LoadBlockIndex(const CChainParams& chainparams) if (!fReindex) { bool ret = LoadBlockIndexDB(chainparams); if (!ret) return false; - needs_init = mapBlockIndex.empty(); + needs_init = g_blockman.m_block_index.empty(); } if (needs_init) { @@ -4236,10 +4370,10 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams) LOCK(cs_main); // Check whether we're already initialized by checking for genesis in - // mapBlockIndex. Note that we can't use m_chain here, since it is + // m_blockman.m_block_index. Note that we can't use m_chain here, since it is // set based on the coins db, not the block index db, which is the only // thing loaded at this point. - if (mapBlockIndex.count(chainparams.GenesisBlock().GetHash())) + if (m_blockman.m_block_index.count(chainparams.GenesisBlock().GetHash())) return true; try { @@ -4247,7 +4381,7 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams) FlatFilePos blockPos = SaveBlockToDisk(block, 0, chainparams, nullptr); if (blockPos.IsNull()) return error("%s: writing genesis block to disk failed", __func__); - CBlockIndex *pindex = AddToBlockIndex(block); + CBlockIndex *pindex = m_blockman.AddToBlockIndex(block); ReceivedBlockTransactions(block, pindex, blockPos, chainparams.GetConsensus()); } catch (const std::runtime_error& e) { return error("%s: failed to write genesis block: %s", __func__, e.what()); @@ -4392,20 +4526,20 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) LOCK(cs_main); // During a reindex, we read the genesis block and call CheckBlockIndex before ActivateBestChain, - // so we have the genesis block in mapBlockIndex but no active chain. (A few of the tests when - // iterating the block tree require that m_chain has been initialized.) + // so we have the genesis block in m_blockman.m_block_index but no active chain. (A few of the + // tests when iterating the block tree require that m_chain has been initialized.) if (m_chain.Height() < 0) { - assert(mapBlockIndex.size() <= 1); + assert(m_blockman.m_block_index.size() <= 1); return; } // Build forward-pointing map of the entire block tree. std::multimap<CBlockIndex*,CBlockIndex*> forward; - for (const std::pair<const uint256, CBlockIndex*>& entry : mapBlockIndex) { + for (const std::pair<const uint256, CBlockIndex*>& entry : m_blockman.m_block_index) { forward.insert(std::make_pair(entry.second->pprev, entry.second)); } - assert(forward.size() == mapBlockIndex.size()); + assert(forward.size() == m_blockman.m_block_index.size()); std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeGenesis = forward.equal_range(nullptr); CBlockIndex *pindex = rangeGenesis.first->second; @@ -4459,7 +4593,7 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) assert(pindex->nHeight == nHeight); // nHeight must be consistent. assert(pindex->pprev == nullptr || pindex->nChainWork >= pindex->pprev->nChainWork); // For every block except the genesis block, the chainwork must be larger than the parent's. assert(nHeight < 2 || (pindex->pskip && (pindex->pskip->nHeight < nHeight))); // The pskip pointer must point back for all but the first 2 blocks. - assert(pindexFirstNotTreeValid == nullptr); // All mapBlockIndex entries must at least be TREE valid + assert(pindexFirstNotTreeValid == nullptr); // All m_blockman.m_block_index entries must at least be TREE valid if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_TREE) assert(pindexFirstNotTreeValid == nullptr); // TREE valid implies all parents are TREE valid if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_CHAIN) assert(pindexFirstNotChainValid == nullptr); // CHAIN valid implies all parents are CHAIN valid if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_SCRIPTS) assert(pindexFirstNotScriptsValid == nullptr); // SCRIPTS valid implies all parents are SCRIPTS valid @@ -4478,13 +4612,13 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) } // If some parent is missing, then it could be that this block was in // setBlockIndexCandidates but had to be removed because of the missing data. - // In this case it must be in mapBlocksUnlinked -- see test below. + // In this case it must be in m_blocks_unlinked -- see test below. } } else { // If this block sorts worse than the current tip or some ancestor's block has never been seen, it cannot be in setBlockIndexCandidates. assert(setBlockIndexCandidates.count(pindex) == 0); } - // Check whether this block is in mapBlocksUnlinked. - std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeUnlinked = mapBlocksUnlinked.equal_range(pindex->pprev); + // Check whether this block is in m_blocks_unlinked. + std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeUnlinked = m_blockman.m_blocks_unlinked.equal_range(pindex->pprev); bool foundInUnlinked = false; while (rangeUnlinked.first != rangeUnlinked.second) { assert(rangeUnlinked.first->first == pindex->pprev); @@ -4495,22 +4629,22 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) rangeUnlinked.first++; } if (pindex->pprev && (pindex->nStatus & BLOCK_HAVE_DATA) && pindexFirstNeverProcessed != nullptr && pindexFirstInvalid == nullptr) { - // If this block has block data available, some parent was never received, and has no invalid parents, it must be in mapBlocksUnlinked. + // If this block has block data available, some parent was never received, and has no invalid parents, it must be in m_blocks_unlinked. assert(foundInUnlinked); } - if (!(pindex->nStatus & BLOCK_HAVE_DATA)) assert(!foundInUnlinked); // Can't be in mapBlocksUnlinked if we don't HAVE_DATA - if (pindexFirstMissing == nullptr) assert(!foundInUnlinked); // We aren't missing data for any parent -- cannot be in mapBlocksUnlinked. + if (!(pindex->nStatus & BLOCK_HAVE_DATA)) assert(!foundInUnlinked); // Can't be in m_blocks_unlinked if we don't HAVE_DATA + if (pindexFirstMissing == nullptr) assert(!foundInUnlinked); // We aren't missing data for any parent -- cannot be in m_blocks_unlinked. if (pindex->pprev && (pindex->nStatus & BLOCK_HAVE_DATA) && pindexFirstNeverProcessed == nullptr && pindexFirstMissing != nullptr) { // We HAVE_DATA for this block, have received data for all parents at some point, but we're currently missing data for some parent. assert(fHavePruned); // We must have pruned. - // This block may have entered mapBlocksUnlinked if: + // This block may have entered m_blocks_unlinked if: // - it has a descendant that at some point had more work than the // tip, and // - we tried switching to that descendant but were missing // data for some intermediate block between m_chain and the // tip. // So if this block is itself better than m_chain.Tip() and it wasn't in - // setBlockIndexCandidates, then it must be in mapBlocksUnlinked. + // setBlockIndexCandidates, then it must be in m_blocks_unlinked. if (!CBlockIndexWorkComparator()(pindex, m_chain.Tip()) && setBlockIndexCandidates.count(pindex) == 0) { if (pindexFirstInvalid == nullptr) { assert(foundInUnlinked); @@ -4754,10 +4888,10 @@ public: CMainCleanup() {} ~CMainCleanup() { // block headers - BlockMap::iterator it1 = mapBlockIndex.begin(); - for (; it1 != mapBlockIndex.end(); it1++) + BlockMap::iterator it1 = g_blockman.m_block_index.begin(); + for (; it1 != g_blockman.m_block_index.end(); it1++) delete (*it1).second; - mapBlockIndex.clear(); + g_blockman.m_block_index.clear(); } }; static CMainCleanup instance_of_cmaincleanup; diff --git a/src/validation.h b/src/validation.h index 638229952d..99850f71d9 100644 --- a/src/validation.h +++ b/src/validation.h @@ -18,6 +18,8 @@ #include <protocol.h> // For CMessageHeader::MessageStartChars #include <script/script_error.h> #include <sync.h> +#include <txmempool.h> // For CTxMemPool::cs +#include <txdb.h> #include <versionbits.h> #include <algorithm> @@ -36,7 +38,6 @@ class CBlockIndex; class CBlockTreeDB; class CBlockUndo; class CChainParams; -class CCoinsViewDB; class CInv; class CConnman; class CScriptCheck; @@ -49,10 +50,6 @@ struct DisconnectedBlockTransactions; struct PrecomputedTransactionData; struct LockPoints; -/** Default for -whitelistrelay. */ -static const bool DEFAULT_WHITELISTRELAY = true; -/** Default for -whitelistforcerelay. */ -static const bool DEFAULT_WHITELISTFORCERELAY = false; /** Default for -minrelaytxfee, minimum relay fee for transactions */ static const unsigned int DEFAULT_MIN_RELAY_TX_FEE = 1000; /** Default for -limitancestorcount, max number of in-mempool ancestors */ @@ -63,6 +60,12 @@ static const unsigned int DEFAULT_ANCESTOR_SIZE_LIMIT = 101; static const unsigned int DEFAULT_DESCENDANT_LIMIT = 25; /** Default for -limitdescendantsize, maximum kilobytes of in-mempool descendants */ static const unsigned int DEFAULT_DESCENDANT_SIZE_LIMIT = 101; +/** + * An extra transaction can be added to a package, as long as it only has one + * ancestor and is no larger than this. Not really any reason to make this + * configurable as it doesn't materially change DoS parameters. + */ +static const unsigned int EXTRA_DESCENDANT_TX_SIZE_LIMIT = 10000; /** Default for -mempoolexpiry, expiration time for mempool transactions in hours */ static const unsigned int DEFAULT_MEMPOOL_EXPIRY = 336; /** Maximum kilobytes for transactions to store for processing during reorg */ @@ -125,8 +128,6 @@ static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8; /** Maximum number of unconnecting headers announcements before DoS score */ static const int MAX_UNCONNECTING_HEADERS = 10; -static const bool DEFAULT_PEERBLOOMFILTERS = true; - /** Default for -stopatheight */ static const int DEFAULT_STOPATHEIGHT = 0; @@ -143,7 +144,6 @@ extern CCriticalSection cs_main; extern CBlockPolicyEstimator feeEstimator; extern CTxMemPool mempool; typedef std::unordered_map<uint256, CBlockIndex*, BlockHasher> BlockMap; -extern BlockMap& mapBlockIndex GUARDED_BY(cs_main); extern Mutex g_best_block_mutex; extern std::condition_variable g_best_block_cv; extern uint256 g_best_block; @@ -379,12 +379,10 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P /** Check a block is completely valid from start to finish (only works on top of our current best block) */ bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams, const CBlock& block, CBlockIndex* pindexPrev, bool fCheckPOW = true, bool fCheckMerkleRoot = true) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -/** Check whether witness commitments are required for block. */ +/** Check whether witness commitments are required for a block, and whether to enforce NULLDUMMY (BIP 147) rules. + * Note that transaction witness validation rules are always enforced when P2SH is enforced. */ bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params); -/** Check whether NULLDUMMY (BIP 147) has activated. */ -bool IsNullDummyEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params); - /** When there are blocks in the active chain with missing data, rewind the chainstate and remove them from the block index */ bool RewindBlockIndex(const CChainParams& params) LOCKS_EXCLUDED(cs_main); @@ -405,12 +403,7 @@ public: /** Replay blocks that aren't fully applied to the database. */ bool ReplayBlocks(const CChainParams& params, CCoinsView* view); -inline CBlockIndex* LookupBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) -{ - AssertLockHeld(cs_main); - BlockMap::const_iterator it = mapBlockIndex.find(hash); - return it == mapBlockIndex.end() ? nullptr : it->second; -} +CBlockIndex* LookupBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Find the last common block between the parameter chain and a locator. */ CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -438,27 +431,125 @@ struct CBlockIndexWorkComparator }; /** - * CChainState stores and provides an API to update our local knowledge of the - * current best chain and header tree. + * Maintains a tree of blocks (stored in `m_block_index`) which is consulted + * to determine where the most-work tip is. * - * It generally provides access to the current block tree, as well as functions - * to provide new data, which it will appropriately validate and incorporate in - * its state as necessary. + * This data is used mostly in `CChainState` - information about, e.g., + * candidate tips is not maintained here. + */ +class BlockManager { +public: + BlockMap m_block_index GUARDED_BY(cs_main); + + /** In order to efficiently track invalidity of headers, we keep the set of + * blocks which we tried to connect and found to be invalid here (ie which + * were set to BLOCK_FAILED_VALID since the last restart). We can then + * walk this set and check if a new header is a descendant of something in + * this set, preventing us from having to walk m_block_index when we try + * to connect a bad block and fail. + * + * While this is more complicated than marking everything which descends + * from an invalid block as invalid at the time we discover it to be + * invalid, doing so would require walking all of m_block_index to find all + * descendants. Since this case should be very rare, keeping track of all + * BLOCK_FAILED_VALID blocks in a set should be just fine and work just as + * well. + * + * Because we already walk m_block_index in height-order at startup, we go + * ahead and mark descendants of invalid blocks as FAILED_CHILD at that time, + * instead of putting things in this set. + */ + std::set<CBlockIndex*> m_failed_blocks; + + /** + * All pairs A->B, where A (or one of its ancestors) misses transactions, but B has transactions. + * Pruned nodes may have entries where B is missing data. + */ + std::multimap<CBlockIndex*, CBlockIndex*> m_blocks_unlinked; + + /** + * Load the blocktree off disk and into memory. Populate certain metadata + * per index entry (nStatus, nChainWork, nTimeMax, etc.) as well as peripheral + * collections like setDirtyBlockIndex. + * + * @param[out] block_index_candidates Fill this set with any valid blocks for + * which we've downloaded all transactions. + */ + bool LoadBlockIndex( + const Consensus::Params& consensus_params, + CBlockTreeDB& blocktree, + std::set<CBlockIndex*, CBlockIndexWorkComparator>& block_index_candidates) + EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** Clear all data members. */ + void Unload() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + CBlockIndex* AddToBlockIndex(const CBlockHeader& block) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** Create a new block index entry for a given block hash */ + CBlockIndex* InsertBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** + * If a block header hasn't already been seen, call CheckBlockHeader on it, ensure + * that it doesn't descend from an invalid block, and then add it to m_block_index. + */ + bool AcceptBlockHeader( + const CBlockHeader& block, + CValidationState& state, + const CChainParams& chainparams, + CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); +}; + +/** + * A convenience class for constructing the CCoinsView* hierarchy used + * to facilitate access to the UTXO set. + * + * This class consists of an arrangement of layered CCoinsView objects, + * preferring to store and retrieve coins in memory via `m_cacheview` but + * ultimately falling back on cache misses to the canonical store of UTXOs on + * disk, `m_dbview`. + */ +class CoinsViews { + +public: + //! The lowest level of the CoinsViews cache hierarchy sits in a leveldb database on disk. + //! All unspent coins reside in this store. + CCoinsViewDB m_dbview GUARDED_BY(cs_main); + + //! This view wraps access to the leveldb instance and handles read errors gracefully. + CCoinsViewErrorCatcher m_catcherview GUARDED_BY(cs_main); + + //! This is the top layer of the cache hierarchy - it keeps as many coins in memory as + //! can fit per the dbcache setting. + std::unique_ptr<CCoinsViewCache> m_cacheview GUARDED_BY(cs_main); + + //! This constructor initializes CCoinsViewDB and CCoinsViewErrorCatcher instances, but it + //! *does not* create a CCoinsViewCache instance by default. This is done separately because the + //! presence of the cache has implications on whether or not we're allowed to flush the cache's + //! state to disk, which should not be done until the health of the database is verified. + //! + //! All arguments forwarded onto CCoinsViewDB. + CoinsViews(std::string ldb_name, size_t cache_size_bytes, bool in_memory, bool should_wipe); + + //! Initialize the CCoinsViewCache member. + void InitCache() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); +}; + +/** + * CChainState stores and provides an API to update our local knowledge of the + * current best chain. * * Eventually, the API here is targeted at being exposed externally as a * consumable libconsensus library, so any functions added must only call * other class member functions, pure functions in other parts of the consensus * library, callbacks via the validation interface, or read/write-to-disk * functions (eventually this will also be via callbacks). + * + * Anything that is contingent on the current tip of the chain is stored here, + * whereas block information and metadata independent of the current tip is + * kept in `BlockMetadataManager`. */ class CChainState { private: - /** - * The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for itself and all ancestors) and - * as good as our current tip or better. Entries may be failed, though, and pruning nodes may be - * missing the data for the block. - */ - std::set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexCandidates; /** * Every received block is assigned a unique and increasing identifier, so we @@ -472,26 +563,6 @@ private: /** chainwork for the last block that preciousblock has been applied to. */ arith_uint256 nLastPreciousChainwork = 0; - /** In order to efficiently track invalidity of headers, we keep the set of - * blocks which we tried to connect and found to be invalid here (ie which - * were set to BLOCK_FAILED_VALID since the last restart). We can then - * walk this set and check if a new header is a descendant of something in - * this set, preventing us from having to walk mapBlockIndex when we try - * to connect a bad block and fail. - * - * While this is more complicated than marking everything which descends - * from an invalid block as invalid at the time we discover it to be - * invalid, doing so would require walking all of mapBlockIndex to find all - * descendants. Since this case should be very rare, keeping track of all - * BLOCK_FAILED_VALID blocks in a set should be just fine and work just as - * well. - * - * Because we already walk mapBlockIndex in height-order at startup, we go - * ahead and mark descendants of invalid blocks as FAILED_CHILD at that time, - * instead of putting things in this set. - */ - std::set<CBlockIndex*> m_failed_blocks; - /** * the ChainState CriticalSection * A lock that must be held when modifying this ChainState - held in ActivateBestChain() @@ -506,15 +577,73 @@ private: */ mutable std::atomic<bool> m_cached_finished_ibd{false}; + //! Reference to a BlockManager instance which itself is shared across all + //! CChainState instances. Keeping a local reference allows us to test more + //! easily as opposed to referencing a global. + BlockManager& m_blockman; + + //! Manages the UTXO set, which is a reflection of the contents of `m_chain`. + std::unique_ptr<CoinsViews> m_coins_views; + public: + CChainState(BlockManager& blockman) : m_blockman(blockman) {} + CChainState(); + + /** + * Initialize the CoinsViews UTXO set database management data structures. The in-memory + * cache is initialized separately. + * + * All parameters forwarded to CoinsViews. + */ + void InitCoinsDB( + size_t cache_size_bytes, + bool in_memory, + bool should_wipe, + std::string leveldb_name = "chainstate"); + + //! Initialize the in-memory coins cache (to be done after the health of the on-disk database + //! is verified). + void InitCoinsCache() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + + //! @returns whether or not the CoinsViews object has been fully initialized and we can + //! safely flush this object to disk. + bool CanFlushToDisk() EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + return m_coins_views && m_coins_views->m_cacheview; + } + //! The current chain of blockheaders we consult and build on. //! @see CChain, CBlockIndex. CChain m_chain; - BlockMap mapBlockIndex GUARDED_BY(cs_main); - std::multimap<CBlockIndex*, CBlockIndex*> mapBlocksUnlinked; - CBlockIndex *pindexBestInvalid = nullptr; - bool LoadBlockIndex(const Consensus::Params& consensus_params, CBlockTreeDB& blocktree) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** + * The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for itself and all ancestors) and + * as good as our current tip or better. Entries may be failed, though, and pruning nodes may be + * missing the data for the block. + */ + std::set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexCandidates; + + //! @returns A reference to the in-memory cache of the UTXO set. + CCoinsViewCache& CoinsTip() EXCLUSIVE_LOCKS_REQUIRED(cs_main) + { + assert(m_coins_views->m_cacheview); + return *m_coins_views->m_cacheview.get(); + } + + //! @returns A reference to the on-disk UTXO set database. + CCoinsViewDB& CoinsDB() EXCLUSIVE_LOCKS_REQUIRED(cs_main) + { + return m_coins_views->m_dbview; + } + + //! @returns A reference to a wrapped view of the in-memory UTXO set that + //! handles disk read errors gracefully. + CCoinsViewErrorCatcher& CoinsErrorCatcher() EXCLUSIVE_LOCKS_REQUIRED(cs_main) + { + return m_coins_views->m_catcherview; + } + + //! Destructs all objects related to accessing the UTXO set. + void ResetCoinsViews() { m_coins_views.reset(); } /** * Update the on-disk chain state. @@ -540,11 +669,6 @@ public: bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) LOCKS_EXCLUDED(cs_main); - /** - * If a block header hasn't already been seen, call CheckBlockHeader on it, ensure - * that it doesn't descend from an invalid block, and then add it to mapBlockIndex. - */ - bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Block (dis)connection on a given view: @@ -552,8 +676,8 @@ public: bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - // Block disconnection on our pcoinsTip: - bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + // Apply the effects of a block disconnection on the UTXO set. + bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); // Manual block validity manipulation: bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); @@ -571,13 +695,6 @@ public: /** Check whether we are doing an initial block download (synchronizing from disk or network) */ bool IsInitialBlockDownload() const; -private: - bool ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - bool ConnectTip(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions &disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - - CBlockIndex* AddToBlockIndex(const CBlockHeader& block) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - /** Create a new block index entry for a given block hash */ - CBlockIndex* InsertBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** * Make various assertions about the state of the block index. * @@ -585,6 +702,10 @@ private: */ void CheckBlockIndex(const Consensus::Params& consensusParams); +private: + bool ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); + bool ConnectTip(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); + void InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); CBlockIndex* FindMostWorkChain() EXCLUSIVE_LOCKS_REQUIRED(cs_main); void ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos, const Consensus::Params& consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -614,11 +735,13 @@ CChainState& ChainstateActive(); /** @returns the most-work chain. */ CChain& ChainActive(); -/** Global variable that points to the coins database (protected by cs_main) */ -extern std::unique_ptr<CCoinsViewDB> pcoinsdbview; +/** @returns the global block index map. */ +BlockMap& BlockIndex(); -/** Global variable that points to the active CCoinsView (protected by cs_main) */ -extern std::unique_ptr<CCoinsViewCache> pcoinsTip; +// Most often ::ChainstateActive() should be used instead of this, but some code +// may not be able to assume that this has been initialized yet and so must use it +// directly, e.g. init.cpp. +extern std::unique_ptr<CChainState> g_chainstate; /** Global variable that points to the active block tree (protected by cs_main) */ extern std::unique_ptr<CBlockTreeDB> pblocktree; diff --git a/src/validationinterface.h b/src/validationinterface.h index ea1b2e7e76..3ce617b827 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -16,7 +16,6 @@ extern CCriticalSection cs_main; class CBlock; class CBlockIndex; struct CBlockLocator; -class CBlockIndex; class CConnman; class CValidationInterface; class CValidationState; diff --git a/src/versionbits.cpp b/src/versionbits.cpp index 3f297c0ebb..2285579cd9 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -94,7 +94,6 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* return state; } -// return the numerical statistics of blocks signalling the specified BIP9 condition in this current period BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params) const { BIP9Stats stats = {}; diff --git a/src/versionbits.h b/src/versionbits.h index cdc947cd9e..d8dda7d95b 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -17,12 +17,17 @@ static const int32_t VERSIONBITS_TOP_MASK = 0xE0000000UL; /** Total bits available for versionbits */ static const int32_t VERSIONBITS_NUM_BITS = 29; +/** BIP 9 defines a finite-state-machine to deploy a softfork in multiple stages. + * State transitions happen during retarget period if conditions are met + * In case of reorg, transitions can go backward. Without transition, state is + * inherited between periods. All blocks of a period share the same state. + */ enum class ThresholdState { - DEFINED, - STARTED, - LOCKED_IN, - ACTIVE, - FAILED, + DEFINED, // First state that each softfork starts out as. The genesis block is by definition in this state for each deployment. + STARTED, // For blocks past the starttime. + LOCKED_IN, // For one retarget period after the first retarget period with STARTED blocks of which at least threshold have the associated bit set in nVersion. + ACTIVE, // For all blocks after the LOCKED_IN retarget period (final state) + FAILED, // For all blocks once the first retarget period after the timeout time is hit, if LOCKED_IN wasn't already reached (final state) }; // A map that gives the state for blocks whose height is a multiple of Period(). @@ -30,11 +35,17 @@ enum class ThresholdState { // will either be nullptr or a block with (height + 1) % Period() == 0. typedef std::map<const CBlockIndex*, ThresholdState> ThresholdConditionCache; +/** Display status of an in-progress BIP9 softfork */ struct BIP9Stats { + /** Length of blocks of the BIP9 signalling period */ int period; + /** Number of blocks with the version bit set required to activate the softfork */ int threshold; + /** Number of blocks elapsed since the beginning of the current period */ int elapsed; + /** Number of blocks with the version bit set since the beginning of the current period */ int count; + /** False if there are not enough blocks left in this period to pass activation threshold */ bool possible; }; @@ -50,12 +61,17 @@ protected: virtual int Threshold(const Consensus::Params& params) const =0; public: + /** Returns the numerical statistics of an in-progress BIP9 softfork in the current period */ BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params) const; - // Note that the functions below take a pindexPrev as input: they compute information for block B based on its parent. + /** Returns the state for pindex A based on parent pindexPrev B. Applies any state transition if conditions are present. + * Caches state from first block of period. */ ThresholdState GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const; + /** Returns the height since when the ThresholdState has started for pindex A based on parent pindexPrev B, all blocks of a period share the same */ int GetStateSinceHeightFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const; }; +/** BIP 9 allows multiple softforks to be deployed in parallel. We cache per-period state for every one of them + * keyed by the bit position used to signal support. */ struct VersionBitsCache { ThresholdConditionCache caches[Consensus::MAX_VERSION_BITS_DEPLOYMENTS]; diff --git a/src/versionbitsinfo.cpp b/src/versionbitsinfo.cpp index ecf3482927..82df92ac90 100644 --- a/src/versionbitsinfo.cpp +++ b/src/versionbitsinfo.cpp @@ -11,12 +11,4 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B /*.name =*/ "testdummy", /*.gbt_force =*/ true, }, - { - /*.name =*/ "csv", - /*.gbt_force =*/ true, - }, - { - /*.name =*/ "segwit", - /*.gbt_force =*/ true, - } }; diff --git a/src/wallet/coincontrol.cpp b/src/wallet/coincontrol.cpp index 60bce66839..14513bc9e9 100644 --- a/src/wallet/coincontrol.cpp +++ b/src/wallet/coincontrol.cpp @@ -20,5 +20,7 @@ void CCoinControl::SetNull() m_confirm_target.reset(); m_signal_bip125_rbf.reset(); m_fee_mode = FeeEstimateMode::UNSET; + m_min_depth = DEFAULT_MIN_DEPTH; + m_max_depth = DEFAULT_MAX_DEPTH; } diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index 249c402e4d..92a290530c 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -12,6 +12,9 @@ #include <boost/optional.hpp> +const int DEFAULT_MIN_DEPTH = 0; +const int DEFAULT_MAX_DEPTH = 9999999; + /** Coin Control Features. */ class CCoinControl { @@ -39,7 +42,9 @@ public: //! Fee estimation mode to control arguments to estimateSmartFee FeeEstimateMode m_fee_mode; //! Minimum chain depth value for coin availability - int m_min_depth{0}; + int m_min_depth = DEFAULT_MIN_DEPTH; + //! Maximum chain depth value for coin availability + int m_max_depth = DEFAULT_MAX_DEPTH; CCoinControl() { diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index dd56ea10ab..0b76c1a0eb 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -107,8 +107,7 @@ bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingM return true; } - -static bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256& nIV, std::vector<unsigned char> &vchCiphertext) +bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256& nIV, std::vector<unsigned char> &vchCiphertext) { CCrypter cKeyCrypter; std::vector<unsigned char> chIV(WALLET_CRYPTO_IV_SIZE); @@ -118,7 +117,7 @@ static bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMateri return cKeyCrypter.Encrypt(*((const CKeyingMaterial*)&vchPlaintext), vchCiphertext); } -static bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext) +bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext) { CCrypter cKeyCrypter; std::vector<unsigned char> chIV(WALLET_CRYPTO_IV_SIZE); @@ -128,7 +127,7 @@ static bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<u return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext)); } -static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key) +bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key) { CKeyingMaterial vchSecret; if(!DecryptSecret(vMasterKey, vchCryptedSecret, vchPubKey.GetHash(), vchSecret)) @@ -140,188 +139,3 @@ static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsi key.Set(vchSecret.begin(), vchSecret.end(), vchPubKey.IsCompressed()); return key.VerifyPubKey(vchPubKey); } - -bool CCryptoKeyStore::SetCrypted() -{ - LOCK(cs_KeyStore); - if (fUseCrypto) - return true; - if (!mapKeys.empty()) - return false; - fUseCrypto = true; - return true; -} - -bool CCryptoKeyStore::IsLocked() const -{ - if (!IsCrypted()) { - return false; - } - LOCK(cs_KeyStore); - return vMasterKey.empty(); -} - -bool CCryptoKeyStore::Lock() -{ - if (!SetCrypted()) - return false; - - { - LOCK(cs_KeyStore); - vMasterKey.clear(); - } - - NotifyStatusChanged(this); - return true; -} - -bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) -{ - { - LOCK(cs_KeyStore); - if (!SetCrypted()) - return false; - - bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys - bool keyFail = false; - CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); - for (; mi != mapCryptedKeys.end(); ++mi) - { - const CPubKey &vchPubKey = (*mi).second.first; - const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; - CKey key; - if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key)) - { - keyFail = true; - break; - } - keyPass = true; - if (fDecryptionThoroughlyChecked) - break; - } - if (keyPass && keyFail) - { - LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n"); - throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt."); - } - if (keyFail || (!keyPass && !accept_no_keys)) - return false; - vMasterKey = vMasterKeyIn; - fDecryptionThoroughlyChecked = true; - } - NotifyStatusChanged(this); - return true; -} - -bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return CBasicKeyStore::AddKeyPubKey(key, pubkey); - } - - if (IsLocked()) { - return false; - } - - std::vector<unsigned char> vchCryptedSecret; - CKeyingMaterial vchSecret(key.begin(), key.end()); - if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) { - return false; - } - - if (!AddCryptedKey(pubkey, vchCryptedSecret)) { - return false; - } - return true; -} - - -bool CCryptoKeyStore::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) -{ - LOCK(cs_KeyStore); - if (!SetCrypted()) { - return false; - } - - mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); - ImplicitlyLearnRelatedKeyScripts(vchPubKey); - return true; -} - -bool CCryptoKeyStore::HaveKey(const CKeyID &address) const -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return CBasicKeyStore::HaveKey(address); - } - return mapCryptedKeys.count(address) > 0; -} - -bool CCryptoKeyStore::GetKey(const CKeyID &address, CKey& keyOut) const -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return CBasicKeyStore::GetKey(address, keyOut); - } - - CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); - if (mi != mapCryptedKeys.end()) - { - const CPubKey &vchPubKey = (*mi).second.first; - const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; - return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); - } - return false; -} - -bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) - return CBasicKeyStore::GetPubKey(address, vchPubKeyOut); - - CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); - if (mi != mapCryptedKeys.end()) - { - vchPubKeyOut = (*mi).second.first; - return true; - } - // Check for watch-only pubkeys - return CBasicKeyStore::GetPubKey(address, vchPubKeyOut); -} - -std::set<CKeyID> CCryptoKeyStore::GetKeys() const -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return CBasicKeyStore::GetKeys(); - } - std::set<CKeyID> set_address; - for (const auto& mi : mapCryptedKeys) { - set_address.insert(mi.first); - } - return set_address; -} - -bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) -{ - LOCK(cs_KeyStore); - if (!mapCryptedKeys.empty() || IsCrypted()) - return false; - - fUseCrypto = true; - for (const KeyMap::value_type& mKey : mapKeys) - { - const CKey &key = mKey.second; - CPubKey vchPubKey = key.GetPubKey(); - CKeyingMaterial vchSecret(key.begin(), key.end()); - std::vector<unsigned char> vchCryptedSecret; - if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) - return false; - if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) - return false; - } - mapKeys.clear(); - return true; -} diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 8e195ca8fa..17a4e9820c 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -5,9 +5,9 @@ #ifndef BITCOIN_WALLET_CRYPTER_H #define BITCOIN_WALLET_CRYPTER_H -#include <keystore.h> #include <serialize.h> #include <support/allocators/secure.h> +#include <script/signingprovider.h> #include <atomic> @@ -109,54 +109,8 @@ public: } }; -/** Keystore which keeps the private keys encrypted. - * It derives from the basic key store, which is used if no encryption is active. - */ -class CCryptoKeyStore : public CBasicKeyStore -{ -private: - - CKeyingMaterial vMasterKey GUARDED_BY(cs_KeyStore); - - //! if fUseCrypto is true, mapKeys must be empty - //! if fUseCrypto is false, vMasterKey must be empty - std::atomic<bool> fUseCrypto; - - //! keeps track of whether Unlock has run a thorough check before - bool fDecryptionThoroughlyChecked; - -protected: - using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>; - - bool SetCrypted(); - - //! will encrypt previously unencrypted keys - bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); - - bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys = false); - CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); - -public: - CCryptoKeyStore() : fUseCrypto(false), fDecryptionThoroughlyChecked(false) - { - } - - bool IsCrypted() const { return fUseCrypto; } - bool IsLocked() const; - bool Lock(); - - virtual bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); - bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; - bool HaveKey(const CKeyID &address) const override; - bool GetKey(const CKeyID &address, CKey& keyOut) const override; - bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; - std::set<CKeyID> GetKeys() const override; - - /** - * Wallet status (encrypted, locked) changed. - * Note: Called without locks held. - */ - boost::signals2::signal<void (CCryptoKeyStore* wallet)> NotifyStatusChanged; -}; +bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256& nIV, std::vector<unsigned char> &vchCiphertext); +bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext); +bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key); #endif // BITCOIN_WALLET_CRYPTER_H diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index b5f90deabd..26aeb754ad 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -6,6 +6,7 @@ #include <wallet/db.h> #include <util/strencodings.h> +#include <util/translation.h> #include <stdint.h> @@ -404,7 +405,7 @@ bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& er LogPrintf("Using wallet %s\n", file_path.string()); if (!env->Open(true /* retry */)) { - errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir); + errorStr = strprintf(_("Error initializing wallet database environment %s!").translated, walletDir); return false; } @@ -426,12 +427,12 @@ bool BerkeleyBatch::VerifyDatabaseFile(const fs::path& file_path, std::string& w warningStr = strprintf(_("Warning: Wallet file corrupt, data salvaged!" " Original %s saved as %s in %s; if" " your balance or transactions are incorrect you should" - " restore from a backup."), + " restore from a backup.").translated, walletFile, backup_filename, walletDir); } if (r == BerkeleyEnvironment::VerifyResult::RECOVER_FAIL) { - errorStr = strprintf(_("%s corrupt, salvage failed"), walletFile); + errorStr = strprintf(_("%s corrupt, salvage failed").translated, walletFile); return false; } } @@ -584,7 +585,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo if (fCreate && !Exists(std::string("version"))) { bool fTmp = fReadOnly; fReadOnly = false; - WriteVersion(CLIENT_VERSION); + Write(std::string("version"), CLIENT_VERSION); fReadOnly = fTmp; } } diff --git a/src/wallet/db.h b/src/wallet/db.h index b3856fbaf9..94f41eaf16 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -399,17 +399,6 @@ public: return (ret == 0); } - bool ReadVersion(int& nVersion) - { - nVersion = 0; - return Read(std::string("version"), nVersion); - } - - bool WriteVersion(int nVersion) - { - return Write(std::string("version"), nVersion); - } - bool static Rewrite(BerkeleyDatabase& database, const char* pszSkip = nullptr); }; diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 46cf6b7616..619197a57a 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -272,18 +272,14 @@ Result CreateRateBumpTransaction(CWallet* wallet, const uint256& txid, const CCo new_coin_control.m_min_depth = 1; CTransactionRef tx_new = MakeTransactionRef(); - CReserveKey reservekey(wallet); CAmount fee_ret; int change_pos_in_out = -1; // No requested location for change std::string fail_reason; - if (!wallet->CreateTransaction(*locked_chain, recipients, tx_new, reservekey, fee_ret, change_pos_in_out, fail_reason, new_coin_control, false)) { + if (!wallet->CreateTransaction(*locked_chain, recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, false)) { errors.push_back("Unable to create transaction: " + fail_reason); return Result::WALLET_ERROR; } - // If change key hasn't been ReturnKey'ed by this point, we take it out of keypool - reservekey.KeepKey(); - // Write back new fee if successful new_fee = fee_ret; @@ -330,9 +326,8 @@ Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransacti mapValue_t mapValue = oldWtx.mapValue; mapValue["replaces_txid"] = oldWtx.GetHash().ToString(); - CReserveKey reservekey(wallet); CValidationState state; - if (!wallet->CommitTransaction(tx, std::move(mapValue), oldWtx.vOrderForm, reservekey, state)) { + if (!wallet->CommitTransaction(tx, std::move(mapValue), oldWtx.vOrderForm, state)) { // NOTE: CommitTransaction never returns false, so this should never happen. errors.push_back(strprintf("The transaction was rejected: %s", FormatStateMessage(state))); return Result::WALLET_ERROR; diff --git a/src/wallet/fees.cpp b/src/wallet/fees.cpp index ad69e84358..2792058f2a 100644 --- a/src/wallet/fees.cpp +++ b/src/wallet/fees.cpp @@ -18,14 +18,7 @@ CAmount GetRequiredFee(const CWallet& wallet, unsigned int nTxBytes) CAmount GetMinimumFee(const CWallet& wallet, unsigned int nTxBytes, const CCoinControl& coin_control, FeeCalculation* feeCalc) { - CAmount fee_needed = GetMinimumFeeRate(wallet, coin_control, feeCalc).GetFee(nTxBytes); - // Always obey the maximum - const CAmount max_tx_fee = wallet.m_default_max_tx_fee; - if (fee_needed > max_tx_fee) { - fee_needed = max_tx_fee; - if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE; - } - return fee_needed; + return GetMinimumFeeRate(wallet, coin_control, feeCalc).GetFee(nTxBytes); } CFeeRate GetRequiredFeeRate(const CWallet& wallet) diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 0265433863..e766deadb7 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -7,11 +7,12 @@ #include <interfaces/chain.h> #include <net.h> #include <outputtype.h> -#include <util/system.h> #include <util/moneystr.h> -#include <walletinitinterface.h> +#include <util/system.h> +#include <util/translation.h> #include <wallet/wallet.h> #include <wallet/walletutil.h> +#include <walletinitinterface.h> class WalletInit : public WalletInitInterface { public: @@ -33,39 +34,41 @@ 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 (always enabled for wallets with \"avoid_reuse\" enabled))", 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("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")", FormatOutputType(DEFAULT_ADDRESS_TYPE)), ArgsManager::ALLOW_ANY, 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 (always enabled for wallets with \"avoid_reuse\" enabled))", DEFAULT_AVOIDPARTIALSPENDS), ArgsManager::ALLOW_ANY, 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)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", ArgsManager::ALLOW_ANY, 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). " "Note: An output is discarded if it is dust at this rate, but we will always discard up to the dust relay fee and a discard fee above that is limited by the fee estimate for the longest target", - CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_FEE)), false, OptionsCategory::WALLET); + CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); gArgs.AddArg("-fallbackfee=<amt>", strprintf("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)", - CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)), false, OptionsCategory::WALLET); - gArgs.AddArg("-keypool=<n>", strprintf("Set key pool size to <n> (default: %u)", DEFAULT_KEYPOOL_SIZE), false, OptionsCategory::WALLET); + CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-keypool=<n>", strprintf("Set key pool size to <n> (default: %u)", DEFAULT_KEYPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); gArgs.AddArg("-maxtxfee=<amt>", strprintf("Maximum total fees (in %s) to use in a single wallet transaction; setting this too low may abort large transactions (default: %s)", - CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), false, OptionsCategory::DEBUG_TEST); + CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-mintxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)", - CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE)), false, OptionsCategory::WALLET); + CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); gArgs.AddArg("-paytxfee=<amt>", strprintf("Fee (in %s/kB) to add to transactions you send (default: %s)", - CURRENCY_UNIT, FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK())), false, OptionsCategory::WALLET); - gArgs.AddArg("-rescan", "Rescan the block chain for missing wallet transactions on startup", false, OptionsCategory::WALLET); - gArgs.AddArg("-salvagewallet", "Attempt to recover private keys from a corrupt wallet on startup", false, OptionsCategory::WALLET); - gArgs.AddArg("-spendzeroconfchange", strprintf("Spend unconfirmed change when sending transactions (default: %u)", DEFAULT_SPEND_ZEROCONF_CHANGE), false, OptionsCategory::WALLET); - gArgs.AddArg("-txconfirmtarget=<n>", strprintf("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)", DEFAULT_TX_CONFIRM_TARGET), false, OptionsCategory::WALLET); - gArgs.AddArg("-upgradewallet", "Upgrade wallet to latest format on startup", false, OptionsCategory::WALLET); - gArgs.AddArg("-wallet=<path>", "Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)", false, OptionsCategory::WALLET); - gArgs.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), false, OptionsCategory::WALLET); - gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", false, OptionsCategory::WALLET); - gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)", false, OptionsCategory::WALLET); - gArgs.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), false, OptionsCategory::WALLET); + CURRENCY_UNIT, FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK())), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-rescan", "Rescan the block chain for missing wallet transactions on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-salvagewallet", "Attempt to recover private keys from a corrupt wallet on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-spendzeroconfchange", strprintf("Spend unconfirmed change when sending transactions (default: %u)", DEFAULT_SPEND_ZEROCONF_CHANGE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-txconfirmtarget=<n>", strprintf("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)", DEFAULT_TX_CONFIRM_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-upgradewallet", "Upgrade wallet to latest format on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-wallet=<path>", "Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); + gArgs.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); +#if HAVE_SYSTEM + gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); +#endif + gArgs.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); gArgs.AddArg("-zapwallettxes=<mode>", "Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup" - " (1 = keep tx meta data e.g. payment request information, 2 = drop tx meta data)", false, OptionsCategory::WALLET); + " (1 = keep tx meta data e.g. payment request information, 2 = drop tx meta data)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); - gArgs.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE), true, OptionsCategory::WALLET_DEBUG_TEST); - gArgs.AddArg("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET), true, OptionsCategory::WALLET_DEBUG_TEST); - gArgs.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB), true, OptionsCategory::WALLET_DEBUG_TEST); - gArgs.AddArg("-walletrejectlongchains", strprintf("Wallet will not create transactions that violate mempool chain limits (default: %u)", DEFAULT_WALLET_REJECT_LONG_CHAINS), true, OptionsCategory::WALLET_DEBUG_TEST); + gArgs.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); + gArgs.AddArg("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); + gArgs.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); + gArgs.AddArg("-walletrejectlongchains", strprintf("Wallet will not create transactions that violate mempool chain limits (default: %u)", DEFAULT_WALLET_REJECT_LONG_CHAINS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); } bool WalletInit::ParameterInteraction() const @@ -119,7 +122,7 @@ bool WalletInit::ParameterInteraction() const if (gArgs.GetBoolArg("-sysperms", false)) return InitError("-sysperms is not allowed in combination with enabled wallet functionality"); if (gArgs.GetArg("-prune", 0) && gArgs.GetBoolArg("-rescan", false)) - return InitError(_("Rescans are not possible in pruned mode. You will need to use -reindex which will download the whole blockchain again.")); + return InitError(_("Rescans are not possible in pruned mode. You will need to use -reindex which will download the whole blockchain again.").translated); return true; } diff --git a/src/wallet/ismine.cpp b/src/wallet/ismine.cpp index 6138d4ae44..b7ef2d4490 100644 --- a/src/wallet/ismine.cpp +++ b/src/wallet/ismine.cpp @@ -8,6 +8,7 @@ #include <key.h> #include <script/script.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <wallet/wallet.h> typedef std::vector<unsigned char> valtype; diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index 54aa12dba8..b5d3b8c305 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -8,6 +8,7 @@ #include <interfaces/chain.h> #include <scheduler.h> #include <util/system.h> +#include <util/translation.h> #include <wallet/wallet.h> bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) @@ -18,14 +19,14 @@ bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wal // The canonical path cleans the path, preventing >1 Berkeley environment instances for the same directory fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error); if (error || !fs::exists(wallet_dir)) { - chain.initError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string())); + chain.initError(strprintf(_("Specified -walletdir \"%s\" does not exist").translated, wallet_dir.string())); return false; } else if (!fs::is_directory(wallet_dir)) { - chain.initError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), wallet_dir.string())); + chain.initError(strprintf(_("Specified -walletdir \"%s\" is not a directory").translated, wallet_dir.string())); return false; // The canonical path transforms relative paths into absolute ones, so we check the non-canonical version } else if (!wallet_dir.is_absolute()) { - chain.initError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string())); + chain.initError(strprintf(_("Specified -walletdir \"%s\" is a relative path").translated, wallet_dir.string())); return false; } gArgs.ForceSetArg("-walletdir", canonical_wallet_dir.string()); @@ -33,7 +34,7 @@ bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wal LogPrintf("Using wallet directory %s\n", GetWalletDir().string()); - chain.initMessage(_("Verifying wallet(s)...")); + chain.initMessage(_("Verifying wallet(s)...").translated); // Parameter interaction code should have thrown an error if -salvagewallet // was enabled with more than wallet file, so the wallet_files size check @@ -47,7 +48,7 @@ bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wal WalletLocation location(wallet_file); if (!wallet_paths.insert(location.GetPath()).second) { - chain.initError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file)); + chain.initError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified.").translated, wallet_file)); return false; } diff --git a/src/wallet/psbtwallet.cpp b/src/wallet/psbtwallet.cpp index ce4788dee1..721a244afb 100644 --- a/src/wallet/psbtwallet.cpp +++ b/src/wallet/psbtwallet.cpp @@ -44,16 +44,7 @@ TransactionError FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& ps // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change 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 - SignatureData sigdata; - psbt_out.FillSignatureData(sigdata); - - MutableTransactionSignatureCreator creator(psbtx.tx.get_ptr(), 0, out.nValue, 1); - ProduceSignature(HidingSigningProvider(pwallet, true, !bip32derivs), creator, out.scriptPubKey, sigdata); - psbt_out.FromSignatureData(sigdata); + UpdatePSBTOutput(HidingSigningProvider(pwallet, true, !bip32derivs), psbtx, i); } return TransactionError::OK; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 3112dca9f5..f52e4318c8 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -16,9 +16,9 @@ #include <util/bip32.h> #include <util/system.h> #include <util/time.h> -#include <wallet/wallet.h> - +#include <util/translation.h> #include <wallet/rpcwallet.h> +#include <wallet/wallet.h> #include <stdint.h> #include <tuple> @@ -109,8 +109,6 @@ UniValue importprivkey(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) - throw std::runtime_error( RPCHelpMan{"importprivkey", "\nAdds a private key (as returned by dumpprivkey) to your wallet. Requires a new wallet backup.\n" "Hint: use importmulti to import more than one private key.\n" @@ -135,7 +133,7 @@ UniValue importprivkey(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false") }, - }.ToString()); + }.Check(request); if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); @@ -187,19 +185,15 @@ UniValue importprivkey(const JSONRPCRequest& request) } } - // Don't throw error in case a key is already there - if (pwallet->HaveKey(vchAddress)) { - return NullUniValue; + // Use timestamp of 1 to scan the whole chain + if (!pwallet->ImportPrivKeys({{vchAddress, key}}, 1)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } - // whenever a key is imported, we need to scan the whole chain - pwallet->UpdateTimeFirstKey(1); - pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1; - - if (!pwallet->AddKeyPubKey(key, pubkey)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); + // Add the wpkh script for this key if possible + if (pubkey.IsCompressed()) { + pwallet->ImportScripts({GetScriptForDestination(WitnessV0KeyHash(vchAddress))}, 0 /* timestamp */); } - pwallet->LearnAllRelatedScripts(pubkey); } } if (fRescan) { @@ -217,8 +211,6 @@ UniValue abortrescan(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 0) - throw std::runtime_error( RPCHelpMan{"abortrescan", "\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", @@ -232,49 +224,13 @@ UniValue abortrescan(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("abortrescan", "") }, - }.ToString()); + }.Check(request); if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false; pwallet->AbortRescan(); return true; } -static void ImportAddress(CWallet*, const CTxDestination& dest, const std::string& strLabel); -static void ImportScript(CWallet* const pwallet, const CScript& script, const std::string& strLabel, bool isRedeemScript) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) -{ - if (!isRedeemScript && ::IsMine(*pwallet, script) == ISMINE_SPENDABLE) { - throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); - } - - pwallet->MarkDirty(); - - if (!pwallet->HaveWatchOnly(script) && !pwallet->AddWatchOnly(script, 0 /* nCreateTime */)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); - } - - if (isRedeemScript) { - const CScriptID id(script); - if (!pwallet->HaveCScript(id) && !pwallet->AddCScript(script)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); - } - ImportAddress(pwallet, ScriptHash(id), strLabel); - } else { - CTxDestination destination; - if (ExtractDestination(script, destination)) { - pwallet->SetAddressBook(destination, strLabel, "receive"); - } - } -} - -static void ImportAddress(CWallet* const pwallet, const CTxDestination& dest, const std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) -{ - CScript script = GetScriptForDestination(dest); - ImportScript(pwallet, script, strLabel, false); - // add to address book or update label - if (IsValidDestination(dest)) - pwallet->SetAddressBook(dest, strLabel, "receive"); -} - UniValue importaddress(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); @@ -283,8 +239,6 @@ UniValue importaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"importaddress", "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" @@ -309,7 +263,7 @@ UniValue importaddress(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false") }, - }.ToString()); + }.Check(request); std::string strLabel; @@ -347,10 +301,22 @@ UniValue importaddress(const JSONRPCRequest& request) if (fP2SH) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); } - ImportAddress(pwallet, dest, strLabel); + + pwallet->MarkDirty(); + + pwallet->ImportScriptPubKeys(strLabel, {GetScriptForDestination(dest)}, false /* have_solving_data */, true /* apply_label */, 1 /* timestamp */); } else if (IsHex(request.params[0].get_str())) { std::vector<unsigned char> data(ParseHex(request.params[0].get_str())); - ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, fP2SH); + CScript redeem_script(data.begin(), data.end()); + + std::set<CScript> scripts = {redeem_script}; + pwallet->ImportScripts(scripts, 0 /* timestamp */); + + if (fP2SH) { + scripts.insert(GetScriptForDestination(ScriptHash(CScriptID(redeem_script)))); + } + + pwallet->ImportScriptPubKeys(strLabel, scripts, false /* have_solving_data */, true /* apply_label */, 1 /* timestamp */); } else { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); } @@ -376,8 +342,6 @@ UniValue importprunedfunds(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 2) - throw std::runtime_error( RPCHelpMan{"importprunedfunds", "\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", { @@ -386,8 +350,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request) }, RPCResults{}, RPCExamples{""}, - }.ToString() - ); + }.Check(request); CMutableTransaction tx; if (!DecodeHexTx(tx, request.params[0].get_str())) @@ -421,8 +384,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); } - wtx.nIndex = txnIndex; - wtx.hashBlock = merkleBlock.header.GetHash(); + wtx.SetConf(CWalletTx::Status::CONFIRMED, merkleBlock.header.GetHash(), txnIndex); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -443,8 +405,6 @@ UniValue removeprunedfunds(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"removeprunedfunds", "\nDeletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will affect wallet balances.\n", { @@ -456,7 +416,7 @@ UniValue removeprunedfunds(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") }, - }.ToString()); + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -485,8 +445,6 @@ UniValue importpubkey(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) - throw std::runtime_error( RPCHelpMan{"importpubkey", "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" "Hint: use importmulti to import more than one public key.\n" @@ -507,7 +465,7 @@ UniValue importpubkey(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false") }, - }.ToString()); + }.Check(request); std::string strLabel; @@ -542,11 +500,16 @@ UniValue importpubkey(const JSONRPCRequest& request) auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); + std::set<CScript> script_pub_keys; for (const auto& dest : GetAllDestinationsForKey(pubKey)) { - ImportAddress(pwallet, dest, strLabel); + script_pub_keys.insert(GetScriptForDestination(dest)); } - ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false); - pwallet->LearnAllRelatedScripts(pubKey); + + pwallet->MarkDirty(); + + pwallet->ImportScriptPubKeys(strLabel, script_pub_keys, true /* have_solving_data */, true /* apply_label */, 1 /* timestamp */); + + pwallet->ImportPubKeys({pubKey.GetID()}, {{pubKey.GetID(), pubKey}} , {} /* key_origins */, false /* add_keypool */, false /* internal */, 1 /* timestamp */); } if (fRescan) { @@ -570,8 +533,6 @@ UniValue importwallet(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"importwallet", "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", @@ -587,7 +548,7 @@ UniValue importwallet(const JSONRPCRequest& request) "\nImport using the json rpc call\n" + HelpExampleRpc("importwallet", "\"test\"") }, - }.ToString()); + }.Check(request); if (pwallet->chain().havePruned()) { // Exit early and print an error. @@ -622,7 +583,7 @@ UniValue importwallet(const JSONRPCRequest& request) // Use uiInterface.ShowProgress instead of pwallet.ShowProgress because pwallet.ShowProgress has a cancel button tied to AbortRescan which // we don't want for this progress bar showing the import progress. uiInterface.ShowProgress does not have a cancel button. - pwallet->chain().showProgress(strprintf("%s " + _("Importing..."), pwallet->GetDisplayName()), 0, false); // show progress dialog in GUI + pwallet->chain().showProgress(strprintf("%s " + _("Importing...").translated, pwallet->GetDisplayName()), 0, false); // show progress dialog in GUI std::vector<std::tuple<CKey, int64_t, bool, std::string>> keys; std::vector<std::pair<CScript, int64_t>> scripts; while (file.good()) { @@ -679,18 +640,18 @@ UniValue importwallet(const JSONRPCRequest& request) CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); CKeyID keyid = pubkey.GetID(); - if (pwallet->HaveKey(keyid)) { - pwallet->WalletLogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(PKHash(keyid))); - continue; - } + pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(PKHash(keyid))); - if (!pwallet->AddKeyPubKey(key, pubkey)) { + + if (!pwallet->ImportPrivKeys({{keyid, key}}, time)) { + pwallet->WalletLogPrintf("Error importing key for %s\n", EncodeDestination(PKHash(keyid))); fGood = false; continue; } - pwallet->mapKeyMetadata[keyid].nCreateTime = time; + if (has_label) pwallet->SetAddressBook(PKHash(keyid), label, "receive"); + nTimeBegin = std::min(nTimeBegin, time); progress++; } @@ -698,24 +659,19 @@ UniValue importwallet(const JSONRPCRequest& request) pwallet->chain().showProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false); const CScript& script = script_pair.first; int64_t time = script_pair.second; - CScriptID id(script); - if (pwallet->HaveCScript(id)) { - pwallet->WalletLogPrintf("Skipping import of %s (script already present)\n", HexStr(script)); - continue; - } - if(!pwallet->AddCScript(script)) { + + if (!pwallet->ImportScripts({script}, time)) { pwallet->WalletLogPrintf("Error importing script %s\n", HexStr(script)); fGood = false; continue; } if (time > 0) { - pwallet->m_script_metadata[id].nCreateTime = time; nTimeBegin = std::min(nTimeBegin, time); } + progress++; } pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI - pwallet->UpdateTimeFirstKey(nTimeBegin); } pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI RescanWallet(*pwallet, reserver, nTimeBegin, false /* update */); @@ -735,8 +691,6 @@ UniValue dumpprivkey(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"dumpprivkey", "\nReveals the private key corresponding to 'address'.\n" "Then the importprivkey can be used with this output\n", @@ -751,7 +705,7 @@ UniValue dumpprivkey(const JSONRPCRequest& request) + HelpExampleCli("importprivkey", "\"mykey\"") + HelpExampleRpc("dumpprivkey", "\"myaddress\"") }, - }.ToString()); + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -783,8 +737,6 @@ UniValue dumpwallet(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"dumpwallet", "\nDumps all wallet keys in a human-readable format to a server-side file. This does not allow overwriting existing files.\n" "Imported scripts are included in the dumpfile, but corresponding BIP173 addresses, etc. may not be added automatically by importwallet.\n" @@ -802,7 +754,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) HelpExampleCli("dumpwallet", "\"test\"") + HelpExampleRpc("dumpwallet", "\"test\"") }, - }.ToString()); + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -1145,9 +1097,10 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID const std::string& descriptor = data["desc"].get_str(); FlatSigningProvider keys; - auto parsed_desc = Parse(descriptor, keys, /* require_checksum = */ true); + std::string error; + auto parsed_desc = Parse(descriptor, keys, error, /* require_checksum = */ true); if (!parsed_desc) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor is invalid"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } have_solving_data = parsed_desc->IsSolvable(); @@ -1274,7 +1227,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con // All good, time to import pwallet->MarkDirty(); - if (!pwallet->ImportScripts(import_data.import_scripts)) { + if (!pwallet->ImportScripts(import_data.import_scripts, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet"); } if (!pwallet->ImportPrivKeys(privkey_map, timestamp)) { @@ -1283,7 +1236,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con if (!pwallet->ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, internal, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } - if (!pwallet->ImportScriptPubKeys(label, script_pub_keys, have_solving_data, internal, timestamp)) { + if (!pwallet->ImportScriptPubKeys(label, script_pub_keys, have_solving_data, !internal, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } @@ -1322,8 +1275,6 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) return NullUniValue; } - if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"importmulti", "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), optionally rescanning the blockchain from the earliest creation time of the imported scripts. Requires a new wallet backup.\n" "If an address/script is imported without all of the private keys required to spend from that address, it will be watchonly. The 'watchonly' option must be set to true in this case or a warning will be returned.\n" @@ -1384,8 +1335,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'") }, - }.ToString() - ); + }.Check(mainRequest); RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index eae5f876ea..b88aabd0fa 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -52,6 +52,31 @@ static inline bool GetAvoidReuseFlag(CWallet * const pwallet, const UniValue& pa return avoid_reuse; } + +/** Used by RPC commands that have an include_watchonly parameter. + * We default to true for watchonly wallets if include_watchonly isn't + * explicitly set. + */ +static bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& pwallet) +{ + if (include_watchonly.isNull()) { + // if include_watchonly isn't explicitly set, then check if we have a watchonly wallet + return pwallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + } + + // otherwise return whatever include_watchonly was set to + return include_watchonly.get_bool(); +} + + +/** Checks if a CKey is in the given CWallet compressed or otherwise*/ +bool HaveKey(const CWallet& wallet, const CKey& key) +{ + CKey key2; + key2.Set(key.begin(), key.end(), !key.IsCompressed()); + return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID()); +} + bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string& wallet_name) { if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { @@ -109,10 +134,10 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo entry.pushKV("generated", true); if (confirms > 0) { - entry.pushKV("blockhash", wtx.hashBlock.GetHex()); - entry.pushKV("blockindex", wtx.nIndex); + entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex()); + entry.pushKV("blockindex", wtx.m_confirm.nIndex); int64_t block_time; - bool found_block = chain.findBlock(wtx.hashBlock, nullptr /* block */, &block_time); + bool found_block = chain.findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &block_time); assert(found_block); entry.pushKV("blocktime", block_time); } else { @@ -159,8 +184,6 @@ static UniValue getnewaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"getnewaddress", "\nReturns a new Bitcoin address for receiving payments.\n" "If 'label' is specified, it is added to the address book \n" @@ -176,7 +199,7 @@ static UniValue getnewaddress(const JSONRPCRequest& request) HelpExampleCli("getnewaddress", "") + HelpExampleRpc("getnewaddress", "") }, - }.ToString()); + }.Check(request); LOCK(pwallet->cs_wallet); @@ -196,19 +219,11 @@ static UniValue getnewaddress(const JSONRPCRequest& request) } } - if (!pwallet->IsLocked()) { - pwallet->TopUpKeyPool(); - } - - // Generate a new key that is added to wallet - CPubKey newKey; - if (!pwallet->GetKeyFromPool(newKey)) { - throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + CTxDestination dest; + std::string error; + if (!pwallet->GetNewDestination(output_type, label, dest, error)) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error); } - pwallet->LearnRelatedScripts(newKey, output_type); - CTxDestination dest = GetDestinationForKey(newKey, output_type); - - pwallet->SetAddressBook(dest, label, "receive"); return EncodeDestination(dest); } @@ -222,8 +237,6 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"getrawchangeaddress", "\nReturns a new Bitcoin address, for receiving change.\n" "This is for use with raw transactions, NOT normal use.\n", @@ -237,7 +250,7 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request) HelpExampleCli("getrawchangeaddress", "") + HelpExampleRpc("getrawchangeaddress", "") }, - }.ToString()); + }.Check(request); LOCK(pwallet->cs_wallet); @@ -245,10 +258,6 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys"); } - if (!pwallet->IsLocked()) { - pwallet->TopUpKeyPool(); - } - OutputType output_type = pwallet->m_default_change_type != OutputType::CHANGE_AUTO ? pwallet->m_default_change_type : pwallet->m_default_address_type; if (!request.params[0].isNull()) { if (!ParseOutputType(request.params[0].get_str(), output_type)) { @@ -256,16 +265,11 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request) } } - CReserveKey reservekey(pwallet); - CPubKey vchPubKey; - if (!reservekey.GetReservedKey(vchPubKey, true)) - throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); - - reservekey.KeepKey(); - - pwallet->LearnRelatedScripts(vchPubKey, output_type); - CTxDestination dest = GetDestinationForKey(vchPubKey, output_type); - + CTxDestination dest; + std::string error; + if (!pwallet->GetNewChangeDestination(output_type, dest, error)) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error); + } return EncodeDestination(dest); } @@ -279,8 +283,6 @@ static UniValue setlabel(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 2) - throw std::runtime_error( RPCHelpMan{"setlabel", "\nSets the label associated with the given address.\n", { @@ -292,7 +294,7 @@ static UniValue setlabel(const JSONRPCRequest& request) HelpExampleCli("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") + HelpExampleRpc("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"") }, - }.ToString()); + }.Check(request); LOCK(pwallet->cs_wallet); @@ -324,15 +326,10 @@ static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet if (nValue > curBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); - if (pwallet->GetBroadcastTransactions() && !pwallet->chain().p2pEnabled()) { - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - } - // Parse Bitcoin address CScript scriptPubKey = GetScriptForDestination(address); // Create and send the transaction - CReserveKey reservekey(pwallet); CAmount nFeeRequired; std::string strError; std::vector<CRecipient> vecSend; @@ -340,13 +337,13 @@ static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; vecSend.push_back(recipient); CTransactionRef tx; - if (!pwallet->CreateTransaction(locked_chain, vecSend, tx, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) { + if (!pwallet->CreateTransaction(locked_chain, vecSend, tx, 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); } CValidationState state; - if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, reservekey, state)) { + if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, state)) { strError = strprintf("Error: The transaction was rejected! Reason given: %s", FormatStateMessage(state)); throw JSONRPCError(RPC_WALLET_ERROR, strError); } @@ -362,8 +359,6 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 9) - throw std::runtime_error( RPCHelpMan{"sendtoaddress", "\nSend an amount to a given address." + HelpRequiringPassphrase(pwallet) + "\n", @@ -377,8 +372,8 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) " transaction, just kept in your wallet."}, {"subtractfeefromamount", RPCArg::Type::BOOL, /* default */ "false", "The fee will be deducted from the amount being sent.\n" " The recipient will receive less bitcoins than you enter in the amount field."}, - {"replaceable", RPCArg::Type::BOOL, /* default */ "fallback to wallet's default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, - {"conf_target", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, + {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"}, {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" @@ -395,7 +390,7 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"\" \"\" true") + HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.1, \"donation\", \"seans outpost\"") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -460,8 +455,6 @@ static UniValue listaddressgroupings(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"listaddressgroupings", "\nLists groups of addresses which have had their common ownership\n" "made public by common use as inputs or as the resulting change\n" @@ -484,7 +477,7 @@ static UniValue listaddressgroupings(const JSONRPCRequest& request) HelpExampleCli("listaddressgroupings", "") + HelpExampleRpc("listaddressgroupings", "") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -523,8 +516,6 @@ static UniValue signmessage(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 2) - throw std::runtime_error( RPCHelpMan{"signmessage", "\nSign a message with the private key of an address" + HelpRequiringPassphrase(pwallet) + "\n", @@ -545,7 +536,7 @@ static UniValue signmessage(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"") }, - }.ToString()); + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -591,8 +582,6 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"getreceivedbyaddress", "\nReturns the total amount received by the given address in transactions with at least minconf confirmations.\n", { @@ -612,7 +601,7 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -663,8 +652,6 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"getreceivedbylabel", "\nReturns the total amount received by addresses with <label> in transactions with at least [minconf] confirmations.\n", { @@ -684,7 +671,7 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -733,8 +720,6 @@ static UniValue getbalance(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"getbalance", "\nReturns the total available balance.\n" "The available balance is what the wallet considers currently spendable, and is\n" @@ -742,7 +727,7 @@ static UniValue getbalance(const JSONRPCRequest& request) { {"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Remains for backward compatibility. Must be excluded or set to \"*\"."}, {"minconf", RPCArg::Type::NUM, /* default */ "0", "Only include transactions confirmed at least this many times."}, - {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Also include balance in watch-only addresses (see 'importaddress')"}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also include balance in watch-only addresses (see 'importaddress')"}, {"avoid_reuse", RPCArg::Type::BOOL, /* default */ "true", "(only available if avoid_reuse wallet flag is set) Do not include balance in dirty outputs; addresses are considered dirty if they have previously been used in a transaction."}, }, RPCResult{ @@ -756,7 +741,7 @@ static UniValue getbalance(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("getbalance", "\"*\", 6") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -775,10 +760,7 @@ static UniValue getbalance(const JSONRPCRequest& request) min_depth = request.params[1].get_int(); } - bool include_watchonly = false; - if (!request.params[2].isNull() && request.params[2].get_bool()) { - include_watchonly = true; - } + bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet); bool avoid_reuse = GetAvoidReuseFlag(pwallet, request.params[3]); @@ -796,14 +778,12 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request) return NullUniValue; } - if (request.fHelp || request.params.size() > 0) - throw std::runtime_error( RPCHelpMan{"getunconfirmedbalance", "DEPRECATED\nIdentical to getbalances().mine.untrusted_pending\n", {}, RPCResults{}, RPCExamples{""}, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -825,7 +805,7 @@ static UniValue sendmany(const JSONRPCRequest& request) return NullUniValue; } - const RPCHelpMan help{"sendmany", + RPCHelpMan{"sendmany", "\nSend multiple times. Amounts are double-precision floating point numbers." + HelpRequiringPassphrase(pwallet) + "\n", { @@ -845,8 +825,8 @@ static UniValue sendmany(const JSONRPCRequest& request) {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Subtract fee from this address"}, }, }, - {"replaceable", RPCArg::Type::BOOL, /* default */ "fallback to wallet's default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, - {"conf_target", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, + {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"}, {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" @@ -866,11 +846,7 @@ static UniValue sendmany(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("sendmany", "\"\", {\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\":0.01,\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\":0.02}, 6, \"testing\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -879,10 +855,6 @@ static UniValue sendmany(const JSONRPCRequest& request) auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - if (pwallet->GetBroadcastTransactions() && !pwallet->chain().p2pEnabled()) { - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - } - if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"\""); } @@ -948,16 +920,15 @@ static UniValue sendmany(const JSONRPCRequest& request) std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext()); // Send - CReserveKey keyChange(pwallet); CAmount nFeeRequired = 0; int nChangePosRet = -1; std::string strFailReason; CTransactionRef tx; - bool fCreated = pwallet->CreateTransaction(*locked_chain, vecSend, tx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control); + bool fCreated = pwallet->CreateTransaction(*locked_chain, vecSend, tx, nFeeRequired, nChangePosRet, strFailReason, coin_control); if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); CValidationState state; - if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, keyChange, state)) { + if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, state)) { strFailReason = strprintf("Transaction commit failed:: %s", FormatStateMessage(state)); throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } @@ -974,8 +945,6 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { - std::string msg = RPCHelpMan{"addmultisigaddress", "\nAdd a nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n" "Each key is a Bitcoin address or hex-encoded public key.\n" @@ -1004,9 +973,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("addmultisigaddress", "2, \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") }, - }.ToString(); - throw std::runtime_error(msg); - } + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -1070,9 +1037,10 @@ static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, CWallet * co fIncludeEmpty = params[1].get_bool(); isminefilter filter = ISMINE_SPENDABLE; - if(!params[2].isNull()) - if(params[2].get_bool()) - filter = filter | ISMINE_WATCH_ONLY; + + if (ParseIncludeWatchonly(params[2], *pwallet)) { + filter |= ISMINE_WATCH_ONLY; + } bool has_filtered_address = false; CTxDestination filtered_address = CNoDestination(); @@ -1211,14 +1179,12 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"listreceivedbyaddress", "\nList balances by receiving address.\n", { {"minconf", RPCArg::Type::NUM, /* default */ "1", "The minimum number of confirmations before payments are included."}, {"include_empty", RPCArg::Type::BOOL, /* default */ "false", "Whether to include addresses that haven't received any payments."}, - {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses (see 'importaddress')."}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Whether to include watch-only addresses (see 'importaddress')"}, {"address_filter", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If present, only return information on this address."}, }, RPCResult{ @@ -1243,7 +1209,7 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) + HelpExampleRpc("listreceivedbyaddress", "6, true, true") + HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1264,14 +1230,12 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 3) - throw std::runtime_error( RPCHelpMan{"listreceivedbylabel", "\nList received transactions by label.\n", { {"minconf", RPCArg::Type::NUM, /* default */ "1", "The minimum number of confirmations before payments are included."}, {"include_empty", RPCArg::Type::BOOL, /* default */ "false", "Whether to include labels that haven't received any payments."}, - {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses (see 'importaddress')."}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Whether to include watch-only addresses (see 'importaddress')"}, }, RPCResult{ "[\n" @@ -1289,7 +1253,7 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) + HelpExampleCli("listreceivedbylabel", "6 true") + HelpExampleRpc("listreceivedbylabel", "6, true, true") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1404,8 +1368,6 @@ UniValue listtransactions(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"listtransactions", "\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", @@ -1414,7 +1376,7 @@ UniValue listtransactions(const JSONRPCRequest& request) " with the specified label, or \"*\" to disable filtering and return all transactions."}, {"count", RPCArg::Type::NUM, /* default */ "10", "The number of transactions to return"}, {"skip", RPCArg::Type::NUM, /* default */ "0", "The number of transactions to skip"}, - {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Include transactions to watch-only addresses (see 'importaddress')"}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Include transactions to watch-only addresses (see 'importaddress')"}, }, RPCResult{ "[\n" @@ -1457,7 +1419,7 @@ UniValue listtransactions(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("listtransactions", "\"*\", 20, 100") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1477,9 +1439,10 @@ UniValue listtransactions(const JSONRPCRequest& request) if (!request.params[2].isNull()) nFrom = request.params[2].get_int(); isminefilter filter = ISMINE_SPENDABLE; - if(!request.params[3].isNull()) - if(request.params[3].get_bool()) - filter = filter | ISMINE_WATCH_ONLY; + + if (ParseIncludeWatchonly(request.params[3], *pwallet)) { + filter |= ISMINE_WATCH_ONLY; + } if (nCount < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count"); @@ -1538,8 +1501,6 @@ static UniValue listsinceblock(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"listsinceblock", "\nGet all transactions in blocks since block [blockhash], or all transactions if omitted.\n" "If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n" @@ -1547,7 +1508,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request) { {"blockhash", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If set, the block hash to list transactions since, otherwise list all transactions."}, {"target_confirmations", RPCArg::Type::NUM, /* default */ "1", "Return the nth block hash from the main chain. e.g. 1 would mean the best block hash. Note: this is not used as a filter, but only affects [lastblock] in the return value"}, - {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Include transactions to watch-only addresses (see 'importaddress')"}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Include transactions to watch-only addresses (see 'importaddress')"}, {"include_removed", RPCArg::Type::BOOL, /* default */ "true", "Show transactions that were removed due to a reorg in the \"removed\" array\n" " (not guaranteed to work on pruned nodes)"}, }, @@ -1592,7 +1553,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request) + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6") + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1624,8 +1585,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request) } } - if (!request.params[2].isNull() && request.params[2].get_bool()) { - filter = filter | ISMINE_WATCH_ONLY; + if (ParseIncludeWatchonly(request.params[2], *pwallet)) { + filter |= ISMINE_WATCH_ONLY; } bool include_removed = (request.params[3].isNull() || request.params[3].get_bool()); @@ -1683,13 +1644,12 @@ static UniValue gettransaction(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"gettransaction", "\nGet detailed information about in-wallet transaction <txid>\n", { {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"}, - {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses in balance calculation and details[]"}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Whether to include watch-only addresses in balance calculation and details[]"}, + {"decode", RPCArg::Type::BOOL, /* default */ "false", "Whether to add a field with the decoded transaction"}, }, RPCResult{ "{\n" @@ -1725,14 +1685,16 @@ static UniValue gettransaction(const JSONRPCRequest& request) " ,...\n" " ],\n" " \"hex\" : \"data\" (string) Raw data for transaction\n" + " \"decoded\" : transaction (json object) Optional, the decoded transaction\n" "}\n" }, RPCExamples{ HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true") + + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" false true") + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1744,9 +1706,12 @@ static UniValue gettransaction(const JSONRPCRequest& request) uint256 hash(ParseHashV(request.params[0], "txid")); isminefilter filter = ISMINE_SPENDABLE; - if(!request.params[1].isNull()) - if(request.params[1].get_bool()) - filter = filter | ISMINE_WATCH_ONLY; + + if (ParseIncludeWatchonly(request.params[1], *pwallet)) { + filter |= ISMINE_WATCH_ONLY; + } + + bool decode_tx = request.params[2].isNull() ? false : request.params[2].get_bool(); UniValue entry(UniValue::VOBJ); auto it = pwallet->mapWallet.find(hash); @@ -1773,6 +1738,12 @@ static UniValue gettransaction(const JSONRPCRequest& request) std::string strHex = EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags()); entry.pushKV("hex", strHex); + if (decode_tx) { + UniValue decoded(UniValue::VOBJ); + TxToUniv(*wtx.tx, uint256(), decoded, false); + entry.pushKV("decoded", decoded); + } + return entry; } @@ -1785,8 +1756,6 @@ static UniValue abandontransaction(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"abandontransaction", "\nMark in-wallet transaction <txid> as abandoned\n" "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n" @@ -1801,8 +1770,7 @@ static UniValue abandontransaction(const JSONRPCRequest& request) HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") }, - }.ToString()); - } + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1833,8 +1801,6 @@ static UniValue backupwallet(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"backupwallet", "\nSafely copies current wallet file to destination, which can be a directory or a path with filename.\n", { @@ -1845,7 +1811,7 @@ static UniValue backupwallet(const JSONRPCRequest& request) HelpExampleCli("backupwallet", "\"backup.dat\"") + HelpExampleRpc("backupwallet", "\"backup.dat\"") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1872,8 +1838,6 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"keypoolrefill", "\nFills the keypool."+ HelpRequiringPassphrase(pwallet) + "\n", @@ -1885,7 +1849,7 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) HelpExampleCli("keypoolrefill", "") + HelpExampleRpc("keypoolrefill", "") }, - }.ToString()); + }.Check(request); if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); @@ -1922,8 +1886,6 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 2) { - throw std::runtime_error( RPCHelpMan{"walletpassphrase", "\nStores the wallet decryption key in memory for 'timeout' seconds.\n" "This is needed prior to performing transactions related to private keys such as sending bitcoins\n" @@ -1943,8 +1905,7 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60") }, - }.ToString()); - } + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2009,8 +1970,6 @@ static UniValue walletpassphrasechange(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 2) { - throw std::runtime_error( RPCHelpMan{"walletpassphrasechange", "\nChanges the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n", { @@ -2022,8 +1981,7 @@ static UniValue walletpassphrasechange(const JSONRPCRequest& request) HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"") }, - }.ToString()); - } + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2063,8 +2021,6 @@ static UniValue walletlock(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 0) { - throw std::runtime_error( RPCHelpMan{"walletlock", "\nRemoves the wallet encryption key from memory, locking the wallet.\n" "After calling this method, you will need to call walletpassphrase again\n" @@ -2081,8 +2037,7 @@ static UniValue walletlock(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("walletlock", "") }, - }.ToString()); - } + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2107,8 +2062,6 @@ static UniValue encryptwallet(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"encryptwallet", "\nEncrypts the wallet with 'passphrase'. This is for first time encryption.\n" "After this, any calls that interact with private keys such as sending or signing \n" @@ -2131,8 +2084,7 @@ static UniValue encryptwallet(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("encryptwallet", "\"my pass phrase\"") }, - }.ToString()); - } + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2171,8 +2123,6 @@ static UniValue lockunspent(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"lockunspent", "\nUpdates list of temporarily unspendable outputs.\n" "Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.\n" @@ -2209,7 +2159,7 @@ static UniValue lockunspent(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2300,8 +2250,6 @@ static UniValue listlockunspent(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 0) - throw std::runtime_error( RPCHelpMan{"listlockunspent", "\nReturns list of temporarily unspendable outputs.\n" "See the lockunspent call to lock and unlock transactions for spending.\n", @@ -2327,7 +2275,7 @@ static UniValue listlockunspent(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("listlockunspent", "") }, - }.ToString()); + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2357,8 +2305,6 @@ static UniValue settxfee(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 1) { - throw std::runtime_error( RPCHelpMan{"settxfee", "\nSet the transaction fee per kB for this wallet. Overrides the global -paytxfee command line parameter.\n", { @@ -2371,8 +2317,7 @@ static UniValue settxfee(const JSONRPCRequest& request) HelpExampleCli("settxfee", "0.00001") + HelpExampleRpc("settxfee", "0.00001") }, - }.ToString()); - } + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2399,7 +2344,7 @@ static UniValue getbalances(const JSONRPCRequest& request) } CWallet& wallet = *rpc_wallet; - const RPCHelpMan help{ + RPCHelpMan{ "getbalances", "Returns an object with all balances in " + CURRENCY_UNIT + ".\n", {}, @@ -2420,11 +2365,7 @@ static UniValue getbalances(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("getbalances", "") + HelpExampleRpc("getbalances", "")}, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2469,7 +2410,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) return NullUniValue; } - const RPCHelpMan help{"getwalletinfo", + RPCHelpMan{"getwalletinfo", "Returns an object containing various wallet state info.\n", {}, RPCResult{ @@ -2499,11 +2440,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) HelpExampleCli("getwalletinfo", "") + HelpExampleRpc("getwalletinfo", "") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2550,8 +2487,6 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) static UniValue listwalletdir(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) { - throw std::runtime_error( RPCHelpMan{"listwalletdir", "Returns a list of wallets in the wallet directory.\n", {}, @@ -2569,8 +2504,7 @@ static UniValue listwalletdir(const JSONRPCRequest& request) HelpExampleCli("listwalletdir", "") + HelpExampleRpc("listwalletdir", "") }, - }.ToString()); - } + }.Check(request); UniValue wallets(UniValue::VARR); for (const auto& path : ListWalletDir()) { @@ -2586,8 +2520,6 @@ static UniValue listwalletdir(const JSONRPCRequest& request) static UniValue listwallets(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"listwallets", "Returns a list of currently loaded wallets.\n" "For full information on the wallet, use \"getwalletinfo\"\n", @@ -2602,7 +2534,7 @@ static UniValue listwallets(const JSONRPCRequest& request) HelpExampleCli("listwallets", "") + HelpExampleRpc("listwallets", "") }, - }.ToString()); + }.Check(request); UniValue obj(UniValue::VARR); @@ -2621,8 +2553,6 @@ static UniValue listwallets(const JSONRPCRequest& request) static UniValue loadwallet(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"loadwallet", "\nLoads a wallet from a wallet file or directory." "\nNote that all wallet command-line options used when starting bitcoind will be" @@ -2640,7 +2570,7 @@ static UniValue loadwallet(const JSONRPCRequest& request) HelpExampleCli("loadwallet", "\"test.dat\"") + HelpExampleRpc("loadwallet", "\"test.dat\"") }, - }.ToString()); + }.Check(request); WalletLocation location(request.params[0].get_str()); @@ -2674,12 +2604,10 @@ static UniValue setwalletflag(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - std::string flags = ""; - for (auto& it : WALLET_FLAG_MAP) - if (it.second & MUTABLE_WALLET_FLAGS) - flags += (flags == "" ? "" : ", ") + it.first; - throw std::runtime_error( + std::string flags = ""; + for (auto& it : WALLET_FLAG_MAP) + if (it.second & MUTABLE_WALLET_FLAGS) + flags += (flags == "" ? "" : ", ") + it.first; RPCHelpMan{"setwalletflag", "\nChange the state of the given wallet flag for a wallet.\n", { @@ -2697,8 +2625,7 @@ static UniValue setwalletflag(const JSONRPCRequest& request) HelpExampleCli("setwalletflag", "avoid_reuse") + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"") }, - }.ToString()); - } + }.Check(request); std::string flag_str = request.params[0].get_str(); bool value = request.params[1].isNull() || request.params[1].get_bool(); @@ -2737,7 +2664,7 @@ static UniValue setwalletflag(const JSONRPCRequest& request) static UniValue createwallet(const JSONRPCRequest& request) { - const RPCHelpMan help{ + RPCHelpMan{ "createwallet", "\nCreates and loads a new wallet.\n", { @@ -2757,81 +2684,51 @@ static UniValue createwallet(const JSONRPCRequest& request) HelpExampleCli("createwallet", "\"testwallet\"") + HelpExampleRpc("createwallet", "\"testwallet\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } - std::string error; - std::string warning; + }.Check(request); uint64_t flags = 0; if (!request.params[1].isNull() && request.params[1].get_bool()) { flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; } - bool create_blank = false; // Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted if (!request.params[2].isNull() && request.params[2].get_bool()) { - create_blank = true; flags |= WALLET_FLAG_BLANK_WALLET; } SecureString passphrase; passphrase.reserve(100); + std::string warning; if (!request.params[3].isNull()) { passphrase = request.params[3].get_str().c_str(); if (passphrase.empty()) { - // Empty string is invalid - throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Cannot encrypt a wallet with a blank password"); + // Empty string means unencrypted + warning = "Empty string given as passphrase, wallet will not be encrypted."; } - // Born encrypted wallets need to be blank first so that wallet creation doesn't make any unencrypted keys - flags |= WALLET_FLAG_BLANK_WALLET; } if (!request.params[4].isNull() && request.params[4].get_bool()) { flags |= WALLET_FLAG_AVOID_REUSE; } - WalletLocation location(request.params[0].get_str()); - if (location.Exists()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + location.GetName() + " already exists."); - } - - // Wallet::Verify will check if we're trying to create a wallet with a duplication name. - 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(*g_rpc_interfaces->chain, location, flags); - if (!wallet) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); + std::string error; + std::string create_warning; + std::shared_ptr<CWallet> wallet; + WalletCreationStatus status = CreateWallet(*g_rpc_interfaces->chain, passphrase, flags, request.params[0].get_str(), error, create_warning, wallet); + switch (status) { + case WalletCreationStatus::CREATION_FAILED: + throw JSONRPCError(RPC_WALLET_ERROR, error); + case WalletCreationStatus::ENCRYPTION_FAILED: + throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, error); + case WalletCreationStatus::SUCCESS: + break; + // no default case, so the compiler can warn about missing cases } - // Encrypt the wallet if there's a passphrase - if (!passphrase.empty() && !(flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - if (!wallet->EncryptWallet(passphrase)) { - throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Wallet created but failed to encrypt."); - } - - if (!create_blank) { - // Unlock the wallet - if (!wallet->Unlock(passphrase)) { - throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Wallet was encrypted but could not be unlocked"); - } - - // Set a seed for the wallet - CPubKey master_pub_key = wallet->GenerateNewSeed(); - wallet->SetHDSeed(master_pub_key); - wallet->NewKeyPool(); - - // Relock the wallet - wallet->Lock(); - } + if (warning.empty()) { + warning = create_warning; + } else if (!warning.empty() && !create_warning.empty()){ + warning += "; " + create_warning; } - AddWallet(wallet); - - wallet->postInitProcess(); - UniValue obj(UniValue::VOBJ); obj.pushKV("name", wallet->GetName()); obj.pushKV("warning", warning); @@ -2841,8 +2738,6 @@ static UniValue createwallet(const JSONRPCRequest& request) static UniValue unloadwallet(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 1) { - throw std::runtime_error( RPCHelpMan{"unloadwallet", "Unloads the wallet referenced by the request endpoint otherwise unloads the wallet specified in the argument.\n" "Specifying the wallet name on a wallet endpoint is invalid.", @@ -2854,8 +2749,7 @@ static UniValue unloadwallet(const JSONRPCRequest& request) HelpExampleCli("unloadwallet", "wallet_name") + HelpExampleRpc("unloadwallet", "wallet_name") }, - }.ToString()); - } + }.Check(request); std::string wallet_name; if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) { @@ -2892,7 +2786,7 @@ static UniValue listunspent(const JSONRPCRequest& request) return NullUniValue; } - const RPCHelpMan help{ + RPCHelpMan{ "listunspent", "\nReturns array of unspent transaction outputs\n" "with between minconf and maxconf (inclusive) confirmations.\n" @@ -2946,11 +2840,7 @@ static UniValue listunspent(const JSONRPCRequest& request) + HelpExampleCli("listunspent", "6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'") + HelpExampleRpc("listunspent", "6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); int nMinDepth = 1; if (!request.params[0].isNull()) { @@ -3016,9 +2906,11 @@ static UniValue listunspent(const JSONRPCRequest& request) { CCoinControl cctl; cctl.m_avoid_address_reuse = false; + cctl.m_min_depth = nMinDepth; + cctl.m_max_depth = nMaxDepth; auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); + pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); } LOCK(pwallet->cs_wallet); @@ -3151,8 +3043,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f } } - if (options.exists("includeWatching")) - coinControl.fAllowWatchOnly = options["includeWatching"].get_bool(); + coinControl.fAllowWatchOnly = ParseIncludeWatchonly(options["includeWatching"], *pwallet); if (options.exists("lockUnspents")) lockUnspents = options["lockUnspents"].get_bool(); @@ -3184,6 +3075,9 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f } } } + } else { + // if options is null and not a bool + coinControl.fAllowWatchOnly = ParseIncludeWatchonly(NullUniValue, *pwallet); } if (tx.vout.size() == 0) @@ -3219,7 +3113,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) return NullUniValue; } - const RPCHelpMan help{"fundrawtransaction", + RPCHelpMan{"fundrawtransaction", "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" "This will not modify existing inputs, and will add at most one change output to the outputs.\n" "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" @@ -3238,7 +3132,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) {"changeAddress", RPCArg::Type::STR, /* default */ "pool address", "The bitcoin address to receive the change"}, {"changePosition", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"}, {"change_type", RPCArg::Type::STR, /* default */ "set by -changetype", "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - {"includeWatching", RPCArg::Type::BOOL, /* default */ "false", "Also select inputs which are watch only"}, + {"includeWatching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only"}, {"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"}, {"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"}, {"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "A json array of integers.\n" @@ -3249,9 +3143,9 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) {"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."}, }, }, - {"replaceable", RPCArg::Type::BOOL, /* default */ "fallback to wallet's default", "Marks this transaction as BIP125 replaceable.\n" + {"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n" " Allows this transaction to be replaced by a transaction with higher fees"}, - {"conf_target", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"}, {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" @@ -3283,11 +3177,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) "\nSend the transaction\n" + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL}); @@ -3320,8 +3210,6 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) - throw std::runtime_error( RPCHelpMan{"signrawtransactionwithwallet", "\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" @@ -3371,7 +3259,7 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + HelpExampleRpc("signrawtransactionwithwallet", "\"myhex\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true); @@ -3392,7 +3280,10 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) } pwallet->chain().findCoins(coins); - return SignTransaction(mtx, request.params[1], pwallet, coins, false, request.params[2]); + // Parse the prevtxs array + ParsePrevouts(request.params[1], nullptr, coins); + + return SignTransaction(mtx, pwallet, coins, request.params[2]); } static UniValue bumpfee(const JSONRPCRequest& request) @@ -3404,26 +3295,24 @@ static UniValue bumpfee(const JSONRPCRequest& request) if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) return NullUniValue; - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"bumpfee", "\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n" "An opt-in RBF transaction with the given txid must be in the wallet.\n" "The command will pay the additional fee by reducing change outputs or adding inputs when necessary. It may add a new change output if one does not already exist.\n" - "If `totalFee` is given, adding inputs is not supported, so there must be a single change output that is big enough or it will fail.\n" + "If `totalFee` (DEPRECATED) is given, adding inputs is not supported, so there must be a single change output that is big enough or it will fail.\n" "All inputs in the original transaction will be included in the replacement transaction.\n" "The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n" "By default, the new fee will be calculated automatically using estimatesmartfee.\n" "The user can specify a confirmation target for estimatesmartfee.\n" - "Alternatively, the user can specify totalFee, or use RPC settxfee to set a higher fee rate.\n" + "Alternatively, the user can specify totalFee (DEPRECATED), or use RPC settxfee to set a higher fee rate.\n" "At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n" "returned by getnetworkinfo) to enter the node's mempool.\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The txid to be bumped"}, {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", { - {"confTarget", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"}, - {"totalFee", RPCArg::Type::NUM, /* default */ "fallback to 'confTarget'", "Total fee (NOT feerate) to pay, in satoshis.\n" + {"confTarget", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"}, + {"totalFee", RPCArg::Type::NUM, /* default */ "fallback to 'confTarget'", "Total fee (NOT feerate) to pay, in satoshis. (DEPRECATED)\n" " In rare cases, the actual fee paid might be slightly higher than the specified\n" " totalFee if the tx change output has to be removed because it is too close to\n" " the dust threshold."}, @@ -3453,8 +3342,7 @@ static UniValue bumpfee(const JSONRPCRequest& request) "\nBump the fee, get the new transaction\'s txid\n" + HelpExampleCli("bumpfee", "<txid>") }, - }.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ}); uint256 hash(ParseHashV(request.params[0], "txid")); @@ -3479,6 +3367,9 @@ static UniValue bumpfee(const JSONRPCRequest& request) } else if (options.exists("confTarget")) { // TODO: alias this to conf_target coin_control.m_confirm_target = ParseConfirmTarget(options["confTarget"], pwallet->chain().estimateMaxBlocks()); } else if (options.exists("totalFee")) { + if (!pwallet->chain().rpcEnableDeprecated("totalFee")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "totalFee argument has been deprecated and will be removed in 0.20. Please use -deprecatedrpc=totalFee to continue using this argument until removal."); + } totalFee = options["totalFee"].get_int64(); if (totalFee <= 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid totalFee %s (must be greater than 0)", FormatMoney(totalFee))); @@ -3567,8 +3458,6 @@ UniValue rescanblockchain(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"rescanblockchain", "\nRescan the local blockchain for wallet related transactions.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", @@ -3586,8 +3475,7 @@ UniValue rescanblockchain(const JSONRPCRequest& request) HelpExampleCli("rescanblockchain", "100000 120000") + HelpExampleRpc("rescanblockchain", "100000, 120000") }, - }.ToString()); - } + }.Check(request); WalletRescanReserver reserver(pwallet); if (!reserver.reserve()) { @@ -3774,8 +3662,6 @@ UniValue getaddressinfo(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"getaddressinfo", "\nReturn information about the given bitcoin address. Some information requires the address\n" "to be in the wallet.\n", @@ -3824,8 +3710,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request) HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleRpc("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") }, - }.ToString()); - } + }.Check(request); LOCK(pwallet->cs_wallet); @@ -3902,8 +3787,6 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"getaddressesbylabel", "\nReturns the list of addresses assigned the specified label.\n", { @@ -3920,7 +3803,7 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request) HelpExampleCli("getaddressesbylabel", "\"tabby\"") + HelpExampleRpc("getaddressesbylabel", "\"tabby\"") }, - }.ToString()); + }.Check(request); LOCK(pwallet->cs_wallet); @@ -3961,8 +3844,6 @@ static UniValue listlabels(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"listlabels", "\nReturns the list of all labels, or labels that are assigned to addresses with a specific purpose.\n", { @@ -3984,7 +3865,7 @@ static UniValue listlabels(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("listlabels", "receive") }, - }.ToString()); + }.Check(request); LOCK(pwallet->cs_wallet); @@ -4018,8 +3899,6 @@ UniValue sethdseed(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"sethdseed", "\nSet or generate a new HD wallet seed. Non-HD wallets will not be upgraded to being a HD wallet. Wallets that are already\n" "HD will have a new HD seed set so that new keys added to the keypool will be derived from this new seed.\n" @@ -4040,8 +3919,7 @@ UniValue sethdseed(const JSONRPCRequest& request) + HelpExampleCli("sethdseed", "true \"wifkey\"") + HelpExampleRpc("sethdseed", "true, \"wifkey\"") }, - }.ToString()); - } + }.Check(request); if (pwallet->chain().isInitialBlockDownload()) { throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download"); @@ -4097,8 +3975,6 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"walletprocesspsbt", "\nUpdate a PSBT with input information from our wallet and then sign inputs\n" "that we can sign for." + @@ -4125,7 +4001,7 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("walletprocesspsbt", "\"psbt\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR}); @@ -4166,8 +4042,6 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) - throw std::runtime_error( RPCHelpMan{"walletcreatefundedpsbt", "\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", @@ -4206,7 +4080,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) {"changeAddress", RPCArg::Type::STR_HEX, /* default */ "pool address", "The bitcoin address to receive the change"}, {"changePosition", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"}, {"change_type", RPCArg::Type::STR, /* default */ "set by -changetype", "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - {"includeWatching", RPCArg::Type::BOOL, /* default */ "false", "Also select inputs which are watch only"}, + {"includeWatching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only"}, {"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"}, {"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"}, {"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "A json array of integers.\n" @@ -4217,7 +4091,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) {"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."}, }, }, - {"replaceable", RPCArg::Type::BOOL, /* default */ "false", "Marks this transaction as BIP125 replaceable.\n" + {"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n" " Allows this transaction to be replaced by a transaction with higher fees"}, {"conf_target", RPCArg::Type::NUM, /* default */ "Fallback to wallet's confirmation target", "Confirmation target (in blocks)"}, {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n" @@ -4239,7 +4113,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) "\nCreate a transaction with no inputs\n" + HelpExampleCli("walletcreatefundedpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, { UniValue::VARR, @@ -4252,7 +4126,13 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) CAmount fee; int change_position; - CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"]); + bool rbf = pwallet->m_signal_rbf; + const UniValue &replaceable_arg = request.params[3]["replaceable"]; + if (!replaceable_arg.isNull()) { + RPCTypeCheckArgument(replaceable_arg, UniValue::VBOOL); + rbf = replaceable_arg.isTrue(); + } + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf); FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]); // Make a blank psbt @@ -4309,7 +4189,7 @@ static const CRPCCommand commands[] = { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} }, { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} }, { "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} }, - { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} }, + { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly","decode"} }, { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} }, { "wallet", "getbalances", &getbalances, {} }, { "wallet", "getwalletinfo", &getwalletinfo, {} }, diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp index fcd1f3fea8..86ba0013fe 100644 --- a/src/wallet/test/init_test_fixture.cpp +++ b/src/wallet/test/init_test_fixture.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <fs.h> +#include <util/system.h> #include <wallet/test/init_test_fixture.h> diff --git a/src/wallet/test/init_tests.cpp b/src/wallet/test/init_tests.cpp index 67e2847963..279542ffad 100644 --- a/src/wallet/test/init_tests.cpp +++ b/src/wallet/test/init_tests.cpp @@ -4,7 +4,9 @@ #include <boost/test/unit_test.hpp> +#include <noui.h> #include <test/setup_common.h> +#include <util/system.h> #include <wallet/test/init_test_fixture.h> BOOST_FIXTURE_TEST_SUITE(init_tests, InitWalletDirTestingSetup) @@ -32,21 +34,27 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom) BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_does_not_exist) { SetWalletDir(m_walletdir_path_cases["nonexistent"]); + noui_suppress(); bool result = m_chain_client->verify(); + noui_reconnect(); BOOST_CHECK(result == false); } BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_directory) { SetWalletDir(m_walletdir_path_cases["file"]); + noui_suppress(); bool result = m_chain_client->verify(); + noui_reconnect(); BOOST_CHECK(result == false); } BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_relative) { SetWalletDir(m_walletdir_path_cases["relative"]); + noui_suppress(); bool result = m_chain_client->verify(); + noui_reconnect(); BOOST_CHECK(result == false); } diff --git a/src/wallet/test/ismine_tests.cpp b/src/wallet/test/ismine_tests.cpp index 0cae055676..062fef7748 100644 --- a/src/wallet/test/ismine_tests.cpp +++ b/src/wallet/test/ismine_tests.cpp @@ -4,7 +4,6 @@ #include <key.h> #include <script/script.h> -#include <script/script_error.h> #include <script/standard.h> #include <test/setup_common.h> #include <wallet/ismine.h> diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index cdf7113203..0400f1207c 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -3,12 +3,10 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <key_io.h> -#include <script/sign.h> #include <util/bip32.h> #include <util/strencodings.h> #include <wallet/psbtwallet.h> #include <wallet/wallet.h> -#include <univalue.h> #include <boost/test/unit_test.hpp> #include <test/setup_common.h> diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index 7db0bc4249..ba0843f352 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -4,8 +4,6 @@ #include <wallet/test/wallet_test_fixture.h> -#include <wallet/db.h> - WalletTestingSetup::WalletTestingSetup(const std::string& chainName) : TestingSetup(chainName), m_wallet(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()) diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 7f261a6189..fc3be2b6ab 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -5,9 +5,7 @@ #include <wallet/wallet.h> #include <memory> -#include <set> #include <stdint.h> -#include <utility> #include <vector> #include <consensus/validation.h> @@ -251,8 +249,7 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) LockAssertion lock(::cs_main); LOCK(wallet.cs_wallet); - wtx.hashBlock = ::ChainActive().Tip()->GetBlockHash(); - wtx.nIndex = 0; + wtx.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 0); // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. @@ -274,7 +271,7 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 if (blockTime > 0) { auto locked_chain = wallet.chain().lock(); LockAssertion lock(::cs_main); - auto inserted = mapBlockIndex.emplace(GetRandHash(), new CBlockIndex); + auto inserted = ::BlockIndex().emplace(GetRandHash(), new CBlockIndex); assert(inserted.second); const uint256& hash = inserted.first->first; block = inserted.first->second; @@ -283,14 +280,19 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 } CWalletTx wtx(&wallet, MakeTransactionRef(tx)); - if (block) { - wtx.SetMerkleBranch(block->GetBlockHash(), 0); - } - { - LOCK(cs_main); + LOCK(cs_main); + LOCK(wallet.cs_wallet); + // If transaction is already in map, to avoid inconsistencies, unconfirmation + // is needed before confirm again with different block. + std::map<uint256, CWalletTx>::iterator it = wallet.mapWallet.find(wtx.GetHash()); + if (it != wallet.mapWallet.end()) { + wtx.setUnconfirmed(); wallet.AddToWallet(wtx); } - LOCK(wallet.cs_wallet); + if (block) { + wtx.SetConf(CWalletTx::Status::CONFIRMED, block->GetBlockHash(), 0); + } + wallet.AddToWallet(wtx); return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart; } @@ -363,17 +365,16 @@ public: CWalletTx& AddTx(CRecipient recipient) { CTransactionRef tx; - CReserveKey reservekey(wallet.get()); CAmount fee; int changePos = -1; std::string error; CCoinControl dummy; { auto locked_chain = m_chain->lock(); - BOOST_CHECK(wallet->CreateTransaction(*locked_chain, {recipient}, tx, reservekey, fee, changePos, error, dummy)); + BOOST_CHECK(wallet->CreateTransaction(*locked_chain, {recipient}, tx, fee, changePos, error, dummy)); } CValidationState state; - BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservekey, state)); + BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, state)); CMutableTransaction blocktx; { LOCK(wallet->cs_wallet); @@ -385,7 +386,7 @@ public: LOCK(wallet->cs_wallet); auto it = wallet->mapWallet.find(tx->GetHash()); BOOST_CHECK(it != wallet->mapWallet.end()); - it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1); + it->second.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 1); return it->second; } @@ -466,8 +467,9 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) wallet->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); BOOST_CHECK(!wallet->TopUpKeyPool(1000)); - CPubKey pubkey; - BOOST_CHECK(!wallet->GetKeyFromPool(pubkey, false)); + CTxDestination dest; + std::string error; + BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, "", dest, error)); } // Explicit calculation which is used to test the wallet constant @@ -490,7 +492,7 @@ static size_t CalculateNestedKeyhashInputSize(bool use_max_sig) CScript script_pubkey = CScript() << OP_HASH160 << std::vector<unsigned char>(script_id.begin(), script_id.end()) << OP_EQUAL; // Add inner-script to key store and key to watchonly - CBasicKeyStore keystore; + FillableSigningProvider keystore; keystore.AddCScript(inner_script); keystore.AddKeyPubKey(key, pubkey); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0472334bc1..7629a40c5e 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -13,18 +13,19 @@ #include <interfaces/wallet.h> #include <key.h> #include <key_io.h> -#include <keystore.h> #include <policy/fees.h> #include <policy/policy.h> #include <primitives/block.h> #include <primitives/transaction.h> #include <script/descriptor.h> #include <script/script.h> +#include <script/signingprovider.h> #include <util/bip32.h> #include <util/error.h> #include <util/fees.h> #include <util/moneystr.h> #include <util/rbf.h> +#include <util/translation.h> #include <util/validation.h> #include <validation.h> #include <wallet/coincontrol.h> @@ -92,13 +93,14 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name) static Mutex g_wallet_release_mutex; static std::condition_variable g_wallet_release_cv; -static std::set<CWallet*> g_unloading_wallet_set; +static std::set<std::string> g_unloading_wallet_set; // Custom deleter for shared_ptr<CWallet>. static void ReleaseWallet(CWallet* wallet) { // Unregister and delete the wallet right after BlockUntilSyncedToCurrentChain // so that it's in sync with the current chainstate. + const std::string name = wallet->GetName(); wallet->WalletLogPrintf("Releasing wallet\n"); wallet->BlockUntilSyncedToCurrentChain(); wallet->Flush(); @@ -107,7 +109,7 @@ static void ReleaseWallet(CWallet* wallet) // Wallet is now released, notify UnloadWallet, if any. { LOCK(g_wallet_release_mutex); - if (g_unloading_wallet_set.erase(wallet) == 0) { + if (g_unloading_wallet_set.erase(name) == 0) { // UnloadWallet was not called for this wallet, all done. return; } @@ -118,21 +120,21 @@ static void ReleaseWallet(CWallet* wallet) void UnloadWallet(std::shared_ptr<CWallet>&& wallet) { // Mark wallet for unloading. - CWallet* pwallet = wallet.get(); + const std::string name = wallet->GetName(); { LOCK(g_wallet_release_mutex); - auto it = g_unloading_wallet_set.insert(pwallet); + auto it = g_unloading_wallet_set.insert(name); assert(it.second); } // The wallet can be in use so it's not possible to explicitly unload here. // Notify the unload intent so that all remaining shared pointers are // released. - pwallet->NotifyUnload(); + wallet->NotifyUnload(); // Time to ditch our shared_ptr and wait for ReleaseWallet call. wallet.reset(); { WAIT_LOCK(g_wallet_release_mutex, lock); - while (g_unloading_wallet_set.count(pwallet) == 1) { + while (g_unloading_wallet_set.count(name) == 1) { g_wallet_release_cv.wait(lock); } } @@ -160,9 +162,74 @@ std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& return LoadWallet(chain, WalletLocation(name), error, warning); } +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::shared_ptr<CWallet>& result) +{ + // Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted + bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET); + + // Born encrypted wallets need to be created blank first. + if (!passphrase.empty()) { + wallet_creation_flags |= WALLET_FLAG_BLANK_WALLET; + } + + // Check the wallet file location + WalletLocation location(name); + if (location.Exists()) { + error = "Wallet " + location.GetName() + " already exists."; + return WalletCreationStatus::CREATION_FAILED; + } + + // Wallet::Verify will check if we're trying to create a wallet with a duplicate name. + std::string wallet_error; + if (!CWallet::Verify(chain, location, false, wallet_error, warning)) { + error = "Wallet file verification failed: " + wallet_error; + return WalletCreationStatus::CREATION_FAILED; + } + + // Do not allow a passphrase when private keys are disabled + if (!passphrase.empty() && (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + error = "Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled."; + return WalletCreationStatus::CREATION_FAILED; + } + + // Make the wallet + std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(chain, location, wallet_creation_flags); + if (!wallet) { + error = "Wallet creation failed"; + return WalletCreationStatus::CREATION_FAILED; + } + + // Encrypt the wallet + if (!passphrase.empty() && !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (!wallet->EncryptWallet(passphrase)) { + error = "Error: Wallet created but failed to encrypt."; + return WalletCreationStatus::ENCRYPTION_FAILED; + } + if (!create_blank) { + // Unlock the wallet + if (!wallet->Unlock(passphrase)) { + error = "Error: Wallet was encrypted but could not be unlocked"; + return WalletCreationStatus::ENCRYPTION_FAILED; + } + + // Set a seed for the wallet + CPubKey master_pub_key = wallet->GenerateNewSeed(); + wallet->SetHDSeed(master_pub_key); + wallet->NewKeyPool(); + + // Relock the wallet + wallet->Lock(); + } + } + AddWallet(wallet); + wallet->postInitProcess(); + result = wallet; + return WalletCreationStatus::SUCCESS; +} + const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; -const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); +const uint256 CWalletTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); /** @defgroup mapWallet * @@ -294,14 +361,14 @@ bool CWallet::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& secret, const C // Make sure we aren't adding private keys to private key disabled wallets assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); - // CCryptoKeyStore has no concept of wallet databases, but calls AddCryptedKey + // FillableSigningProvider has no concept of wallet databases, but calls AddCryptedKey // which is overridden below. To avoid flushes, the database handle is // tunneled through to it. bool needsDB = !encrypted_batch; if (needsDB) { encrypted_batch = &batch; } - if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) { + if (!AddKeyPubKeyInner(secret, pubkey)) { if (needsDB) encrypted_batch = nullptr; return false; } @@ -336,7 +403,7 @@ bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) { - if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) + if (!AddCryptedKeyInner(vchPubKey, vchCryptedSecret)) return false; { LOCK(cs_wallet); @@ -404,7 +471,7 @@ void CWallet::UpgradeKeyMetadata() bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) { - return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); + return AddCryptedKeyInner(vchPubKey, vchCryptedSecret); } /** @@ -431,7 +498,7 @@ bool CWallet::AddCScript(const CScript& redeemScript) bool CWallet::AddCScriptWithDB(WalletBatch& batch, const CScript& redeemScript) { - if (!CCryptoKeyStore::AddCScript(redeemScript)) + if (!FillableSigningProvider::AddCScript(redeemScript)) return false; if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) { UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); @@ -452,12 +519,31 @@ bool CWallet::LoadCScript(const CScript& redeemScript) return true; } - return CCryptoKeyStore::AddCScript(redeemScript); + return FillableSigningProvider::AddCScript(redeemScript); +} + +static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut) +{ + std::vector<std::vector<unsigned char>> solutions; + return Solver(dest, solutions) == TX_PUBKEY && + (pubKeyOut = CPubKey(solutions[0])).IsFullyValid(); +} + +bool CWallet::AddWatchOnlyInMem(const CScript &dest) +{ + LOCK(cs_KeyStore); + setWatchOnly.insert(dest); + CPubKey pubKey; + if (ExtractPubKey(dest, pubKey)) { + mapWatchKeys[pubKey.GetID()] = pubKey; + ImplicitlyLearnRelatedKeyScripts(pubKey); + } + return true; } bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) { - if (!CCryptoKeyStore::AddWatchOnly(dest)) + if (!AddWatchOnlyInMem(dest)) return false; const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)]; UpdateTimeFirstKey(meta.nCreateTime); @@ -490,8 +576,17 @@ bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime) bool CWallet::RemoveWatchOnly(const CScript &dest) { AssertLockHeld(cs_wallet); - if (!CCryptoKeyStore::RemoveWatchOnly(dest)) - return false; + { + LOCK(cs_KeyStore); + setWatchOnly.erase(dest); + CPubKey pubKey; + if (ExtractPubKey(dest, pubKey)) { + mapWatchKeys.erase(pubKey.GetID()); + } + // Related CScripts are not removed; having superfluous scripts around is + // harmless (see comment in ImplicitlyLearnRelatedKeyScripts). + } + if (!HaveWatchOnly()) NotifyWatchonlyChanged(false); if (!WalletBatch(*database).EraseWatchOnly(dest)) @@ -502,7 +597,19 @@ bool CWallet::RemoveWatchOnly(const CScript &dest) bool CWallet::LoadWatchOnly(const CScript &dest) { - return CCryptoKeyStore::AddWatchOnly(dest); + return AddWatchOnlyInMem(dest); +} + +bool CWallet::HaveWatchOnly(const CScript &dest) const +{ + LOCK(cs_KeyStore); + return setWatchOnly.count(dest) > 0; +} + +bool CWallet::HaveWatchOnly() const +{ + LOCK(cs_KeyStore); + return (!setWatchOnly.empty()); } bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys) @@ -518,7 +625,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_key return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) continue; // try another master key - if (CCryptoKeyStore::Unlock(_vMasterKey, accept_no_keys)) { + if (Unlock(_vMasterKey, accept_no_keys)) { // Now that we've unlocked, upgrade the key metadata UpgradeKeyMetadata(); return true; @@ -544,7 +651,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) return false; - if (CCryptoKeyStore::Unlock(_vMasterKey)) + if (Unlock(_vMasterKey)) { int64_t nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); @@ -1003,22 +1110,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) bool fUpdated = false; if (!fInsertedNew) { - // Merge - if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock) - { - wtx.hashBlock = wtxIn.hashBlock; - fUpdated = true; - } - // If no longer abandoned, update - if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned()) - { - wtx.hashBlock = wtxIn.hashBlock; - fUpdated = true; - } - if (wtxIn.nIndex != -1 && (wtxIn.nIndex != wtx.nIndex)) - { - wtx.nIndex = wtxIn.nIndex; + if (wtxIn.m_confirm.status != wtx.m_confirm.status) { + wtx.m_confirm.status = wtxIn.m_confirm.status; + wtx.m_confirm.nIndex = wtxIn.m_confirm.nIndex; + wtx.m_confirm.hashBlock = wtxIn.m_confirm.hashBlock; fUpdated = true; + } else { + assert(wtx.m_confirm.nIndex == wtxIn.m_confirm.nIndex); + assert(wtx.m_confirm.hashBlock == wtxIn.m_confirm.hashBlock); } if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe) { @@ -1050,6 +1149,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) // Notify UI of new or updated transaction NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED); +#if HAVE_SYSTEM // notify an external script when a wallet transaction comes in or is updated std::string strCmd = gArgs.GetArg("-walletnotify", ""); @@ -1059,12 +1159,24 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) std::thread t(runCommand, strCmd); t.detach(); // thread runs free } +#endif return true; } -void CWallet::LoadToWallet(const CWalletTx& wtxIn) +void CWallet::LoadToWallet(CWalletTx& wtxIn) { + // If wallet doesn't have a chain (e.g wallet-tool), lock can't be taken. + auto locked_chain = LockChain(); + // If tx hasn't been reorged out of chain while wallet being shutdown + // change tx status to UNCONFIRMED and reset hashBlock/nIndex. + if (!wtxIn.m_confirm.hashBlock.IsNull()) { + if (locked_chain && !locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock)) { + wtxIn.setUnconfirmed(); + wtxIn.m_confirm.hashBlock = uint256(); + wtxIn.m_confirm.nIndex = 0; + } + } uint256 hash = wtxIn.GetHash(); const auto& ins = mapWallet.emplace(hash, wtxIn); CWalletTx& wtx = ins.first->second; @@ -1077,14 +1189,14 @@ void CWallet::LoadToWallet(const CWalletTx& wtxIn) auto it = mapWallet.find(txin.prevout.hash); if (it != mapWallet.end()) { CWalletTx& prevtx = it->second; - if (prevtx.nIndex == -1 && !prevtx.hashUnset()) { - MarkConflicted(prevtx.hashBlock, wtx.GetHash()); + if (prevtx.isConflicted()) { + MarkConflicted(prevtx.m_confirm.hashBlock, wtx.GetHash()); } } } } -bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool fUpdate) +bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate) { const CTransaction& tx = *ptx; { @@ -1131,9 +1243,9 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256 CWalletTx wtx(this, ptx); - // Get merkle branch if transaction was found in a block - if (!block_hash.IsNull()) - wtx.SetMerkleBranch(block_hash, posInBlock); + // Block disconnection override an abandoned tx as unconfirmed + // which means user may have to call abandontransaction again + wtx.SetConf(status, block_hash, posInBlock); return AddToWallet(wtx, false); } @@ -1193,7 +1305,7 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui if (currentconfirm == 0 && !wtx.isAbandoned()) { // If the orig tx was not in block/mempool, none of its spends can be in mempool assert(!wtx.InMempool()); - wtx.nIndex = -1; + wtx.m_confirm.nIndex = 0; wtx.setAbandoned(); wtx.MarkDirty(); batch.WriteTx(wtx); @@ -1247,8 +1359,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) if (conflictconfirms < currentconfirm) { // Block is 'more conflicted' than current confirm; update. // Mark transaction as conflicted with this block. - wtx.nIndex = -1; - wtx.hashBlock = hashBlock; + wtx.m_confirm.nIndex = 0; + wtx.m_confirm.hashBlock = hashBlock; + wtx.setConflicted(); wtx.MarkDirty(); batch.WriteTx(wtx); // Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too @@ -1266,8 +1379,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) } } -void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool update_tx) { - if (!AddToWalletIfInvolvingMe(ptx, block_hash, posInBlock, update_tx)) +void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool update_tx) +{ + if (!AddToWalletIfInvolvingMe(ptx, status, block_hash, posInBlock, update_tx)) return; // Not one of ours // If a transaction changes 'conflicted' state, that changes the balance @@ -1279,7 +1393,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_h void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) { auto locked_chain = chain().lock(); LOCK(cs_wallet); - SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); + SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */); auto it = mapWallet.find(ptx->GetHash()); if (it != mapWallet.end()) { @@ -1299,22 +1413,14 @@ void CWallet::BlockConnected(const CBlock& block, const std::vector<CTransaction const uint256& block_hash = block.GetHash(); 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 - // receive another notification and there is a race condition where - // notification of a connected conflict might cause an outside process - // to abandon a transaction and then have it inadvertently cleared by - // the notification that the conflicted transaction was evicted. - for (const CTransactionRef& ptx : vtxConflicted) { - SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); - TransactionRemovedFromMempool(ptx); - } for (size_t i = 0; i < block.vtx.size(); i++) { - SyncTransaction(block.vtx[i], block_hash, i); + SyncTransaction(block.vtx[i], CWalletTx::Status::CONFIRMED, block_hash, i); TransactionRemovedFromMempool(block.vtx[i]); } + for (const CTransactionRef& ptx : vtxConflicted) { + TransactionRemovedFromMempool(ptx); + } m_last_block_processed = block_hash; } @@ -1323,8 +1429,12 @@ void CWallet::BlockDisconnected(const CBlock& block) { auto locked_chain = chain().lock(); LOCK(cs_wallet); + // At block disconnection, this will change an abandoned transaction to + // be unconfirmed, whether or not the transaction is added back to the mempool. + // User may have to call abandontransaction again. It may be addressed in the + // future with a stickier abandoned state or even removing abandontransaction call. for (const CTransactionRef& ptx : block.vtx) { - SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); + SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */); } } @@ -1661,14 +1771,27 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> return true; } -bool CWallet::ImportScripts(const std::set<CScript> scripts) +bool CWallet::ImportScripts(const std::set<CScript> scripts, int64_t timestamp) { WalletBatch batch(*database); for (const auto& entry : scripts) { - if (!HaveCScript(CScriptID(entry)) && !AddCScriptWithDB(batch, entry)) { + CScriptID id(entry); + if (HaveCScript(id)) { + WalletLogPrintf("Already have script %s, skipping\n", HexStr(entry)); + continue; + } + if (!AddCScriptWithDB(batch, entry)) { return false; } + + if (timestamp > 0) { + m_script_metadata[CScriptID(entry)].nCreateTime = timestamp; + } + } + if (timestamp > 0) { + UpdateTimeFirstKey(timestamp); } + return true; } @@ -1680,9 +1803,14 @@ bool CWallet::ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const in CPubKey pubkey = key.GetPubKey(); const CKeyID& id = entry.first; assert(key.VerifyPubKey(pubkey)); + // Skip if we already have the key + if (HaveKey(id)) { + WalletLogPrintf("Already have key with pubkey %s, skipping\n", HexStr(pubkey)); + continue; + } mapKeyMetadata[id].nCreateTime = timestamp; // If the private key is not present in the wallet, insert it. - if (!HaveKey(id) && !AddKeyPubKeyWithDB(batch, key, pubkey)) { + if (!AddKeyPubKeyWithDB(batch, key, pubkey)) { return false; } UpdateTimeFirstKey(timestamp); @@ -1703,7 +1831,12 @@ bool CWallet::ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const st } const CPubKey& pubkey = entry->second; CPubKey temp; - if (!GetPubKey(id, temp) && !AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey), timestamp)) { + if (GetPubKey(id, temp)) { + // Already have pubkey, skipping + WalletLogPrintf("Already have pubkey %s, skipping\n", HexStr(temp)); + continue; + } + if (!AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey), timestamp)) { return false; } mapKeyMetadata[id].nCreateTime = timestamp; @@ -1717,7 +1850,7 @@ bool CWallet::ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const st return true; } -bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool internal, const int64_t timestamp) +bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) { WalletBatch batch(*database); for (const CScript& script : script_pub_keys) { @@ -1728,7 +1861,7 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri } CTxDestination dest; ExtractDestination(script, dest); - if (!internal && IsValidDestination(dest)) { + if (apply_label && IsValidDestination(dest)) { SetAddressBookWithDB(batch, dest, label, "receive"); } } @@ -1898,7 +2031,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString()); fAbortRescan = false; - ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup + ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup uint256 tip_hash; // The way the 'block_height' is initialized is just a workaround for the gcc bug #47679 since version 4.6.0. Optional<int> block_height = MakeOptional(false, int()); @@ -1917,7 +2050,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc while (block_height && !fAbortRescan && !chain().shutdownRequested()) { m_scanning_progress = (progress_current - progress_begin) / (progress_end - progress_begin); if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) { - ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100)))); + ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100)))); } if (GetTime() >= nNow + 60) { nNow = GetTime(); @@ -1938,7 +2071,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc break; } for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { - SyncTransaction(block.vtx[posInBlock], block_hash, posInBlock, fUpdate); + SyncTransaction(block.vtx[posInBlock], CWalletTx::Status::CONFIRMED, block_hash, posInBlock, fUpdate); } // scan succeeded, record block as most recent successfully scanned result.last_scanned_block = block_hash; @@ -1973,7 +2106,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc } } } - ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 100); // hide progress dialog in GUI + ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 100); // hide progress dialog in GUI if (block_height && fAbortRescan) { WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", *block_height, progress_current); result.status = ScanResult::USER_ABORT; @@ -1994,8 +2127,7 @@ void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) std::map<int64_t, CWalletTx*> mapSorted; // Sort pending wallet transactions based on their initial wallet insertion order - for (std::pair<const uint256, CWalletTx>& item : mapWallet) - { + for (std::pair<const uint256, CWalletTx>& item : mapWallet) { const uint256& wtxid = item.first; CWalletTx& wtx = item.second; assert(wtx.GetHash() == wtxid); @@ -2010,32 +2142,37 @@ void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) // Try to add wallet transactions to memory pool for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { CWalletTx& wtx = *(item.second); - CValidationState state; - wtx.AcceptToMemoryPool(locked_chain, state); + std::string unused_err_string; + wtx.SubmitMemoryPoolAndRelay(unused_err_string, false, locked_chain); } } -bool CWalletTx::RelayWalletTransaction(interfaces::Chain::Lock& locked_chain) +bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain) { // Can't relay if wallet is not broadcasting if (!pwallet->GetBroadcastTransactions()) return false; - // Don't relay coinbase transactions outside blocks - if (IsCoinBase()) return false; // Don't relay abandoned transactions if (isAbandoned()) return false; - // Don't relay conflicted or already confirmed transactions + // Don't try to submit coinbase transactions. These would fail anyway but would + // cause log spam. + if (IsCoinBase()) return false; + // Don't try to submit conflicted or confirmed transactions. if (GetDepthInMainChain(locked_chain) != 0) return false; - // Don't relay transactions that aren't accepted to the mempool - CValidationState unused_state; - if (!InMempool() && !AcceptToMemoryPool(locked_chain, unused_state)) return false; - // Don't try to relay if the node is not connected to the p2p network - if (!pwallet->chain().p2pEnabled()) return false; - // Try to relay the transaction - pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString()); - pwallet->chain().relayTransaction(GetHash()); - - return true; + // Submit transaction to mempool for relay + pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString()); + // 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 + // because we think that this newly generated transaction's change is + // unavailable as we're not yet aware that it is in the mempool. + // + // Irrespective of the failure reason, un-marking fInMempool + // out-of-order is incorrect - it should be unmarked when + // TransactionRemovedFromMempool fires. + bool ret = pwallet->chain().broadcastTransaction(tx, err_string, pwallet->m_default_max_tx_fee, relay); + fInMempool |= ret; + return ret; } std::set<uint256> CWalletTx::GetConflicts() const @@ -2226,7 +2363,7 @@ void CWallet::ResendWalletTransactions() if (m_best_block_time < nLastResend) return; nLastResend = GetTime(); - int relayed_tx_count = 0; + int submitted_tx_count = 0; { // locked_chain and cs_wallet scope auto locked_chain = chain().lock(); @@ -2235,15 +2372,17 @@ void CWallet::ResendWalletTransactions() // Relay transactions for (std::pair<const uint256, CWalletTx>& item : mapWallet) { CWalletTx& wtx = item.second; - // only rebroadcast unconfirmed txes older than 5 minutes before the - // last block was found + // Attempt to rebroadcast all txes more than 5 minutes older than + // the last block. SubmitMemoryPoolAndRelay() will not rebroadcast + // any confirmed or conflicting txs. if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; - if (wtx.RelayWalletTransaction(*locked_chain)) ++relayed_tx_count; + std::string unused_err_string; + if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true, *locked_chain)) ++submitted_tx_count; } } // locked_chain and cs_wallet - if (relayed_tx_count > 0) { - WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed_tx_count); + if (submitted_tx_count > 0) { + WalletLogPrintf("%s: resubmit %u unconfirmed transactions\n", __func__, submitted_tx_count); } } @@ -2266,7 +2405,7 @@ void MaybeResendWalletTxs() CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) const { Balance ret; - isminefilter reuse_filter = avoid_reuse ? 0 : ISMINE_USED; + isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED; { auto locked_chain = chain().lock(); LOCK(cs_wallet); @@ -2308,7 +2447,7 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const return balance; } -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 +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 { AssertLockHeld(cs_wallet); @@ -2317,6 +2456,8 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< // Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where // a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses bool allow_used_addresses = !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse); + const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH}; + const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH}; for (const auto& entry : mapWallet) { @@ -2376,8 +2517,9 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< continue; } - if (nDepth < nMinDepth || nDepth > nMaxDepth) + if (nDepth < min_depth || nDepth > max_depth) { continue; + } for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount) @@ -2664,17 +2806,13 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC auto locked_chain = chain().lock(); LOCK(cs_wallet); - CReserveKey reservekey(this); CTransactionRef tx_new; - if (!CreateTransaction(*locked_chain, vecSend, tx_new, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { + if (!CreateTransaction(*locked_chain, vecSend, tx_new, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { return false; } if (nChangePosInOut != -1) { tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]); - // We don't have the normal Create/Commit cycle, and don't want to risk - // reusing change, so just remove the key from the keypool here. - reservekey.KeepKey(); } // Copy output sizes from new transaction; they may have had the fee @@ -2785,17 +2923,18 @@ OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vec return m_default_address_type; } -bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, 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, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) { CAmount nValue = 0; + ReserveDestination reservedest(this); int nChangePosRequest = nChangePosInOut; unsigned int nSubtractFeeFromAmount = 0; for (const auto& recipient : vecSend) { if (nValue < 0 || recipient.nAmount < 0) { - strFailReason = _("Transaction amounts must not be negative"); + strFailReason = _("Transaction amounts must not be negative").translated; return false; } nValue += recipient.nAmount; @@ -2805,7 +2944,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } if (vecSend.empty()) { - strFailReason = _("Transaction must have at least one recipient"); + strFailReason = _("Transaction must have at least one recipient").translated; return false; } @@ -2822,11 +2961,11 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std LOCK(cs_wallet); { std::vector<COutput> vAvailableCoins; - AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0, coin_control.m_min_depth); + AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0); CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy // Create change script that will be used if we need change - // TODO: pass in scriptChange instead of reservekey so + // TODO: pass in scriptChange instead of reservedest so // change transaction isn't always pay-to-bitcoin-address CScript scriptChange; @@ -2843,22 +2982,19 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // Reserve a new key pair from key pool if (!CanGetAddresses(true)) { - strFailReason = _("Can't generate a change-address key. No keys in the internal keypool and can't generate any keys."); + strFailReason = _("Can't generate a change-address key. No keys in the internal keypool and can't generate any keys.").translated; return false; } - CPubKey vchPubKey; - bool ret; - ret = reservekey.GetReservedKey(vchPubKey, true); + CTxDestination dest; + const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); + bool ret = reservedest.GetReservedDestination(change_type, dest, true); if (!ret) { - strFailReason = _("Keypool ran out, please call keypoolrefill first"); + strFailReason = "Keypool ran out, please call keypoolrefill first"; return false; } - const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); - - LearnRelatedScripts(vchPubKey, change_type); - scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, change_type)); + scriptChange = GetScriptForDestination(dest); } CTxOut change_prototype_txout(0, scriptChange); coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout); @@ -2912,12 +3048,12 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std if (recipient.fSubtractFeeFromAmount && nFeeRet > 0) { if (txout.nValue < 0) - strFailReason = _("The transaction amount is too small to pay the fee"); + strFailReason = _("The transaction amount is too small to pay the fee").translated; else - strFailReason = _("The transaction amount is too small to send after the fee has been deducted"); + strFailReason = _("The transaction amount is too small to send after the fee has been deducted").translated; } else - strFailReason = _("Transaction amount too small"); + strFailReason = _("Transaction amount too small").translated; return false; } txNew.vout.push_back(txout); @@ -2945,7 +3081,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std continue; } else { - strFailReason = _("Insufficient funds"); + strFailReason = _("Insufficient funds").translated; return false; } } @@ -2976,7 +3112,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } else if ((unsigned int)nChangePosInOut > txNew.vout.size()) { - strFailReason = _("Change index out of range"); + strFailReason = _("Change index out of range").translated; return false; } @@ -2995,22 +3131,14 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); if (nBytes < 0) { - strFailReason = _("Signing transaction failed"); + strFailReason = _("Signing transaction failed").translated; return false; } nFeeNeeded = GetMinimumFee(*this, nBytes, coin_control, &feeCalc); if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) { // eventually allow a fallback fee - strFailReason = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); - return false; - } - - // If we made it here and we aren't even able to meet the relay fee on the next pass, give up - // because we must be at the maximum allowed fee. - if (nFeeNeeded < chain().relayMinFee().GetFee(nBytes)) - { - strFailReason = _("Transaction too large for fee policy"); + strFailReason = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.").translated; return false; } @@ -3050,7 +3178,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // fee to pay for the new output and still meet nFeeNeeded // Or we should have just subtracted fee from recipients and // nFeeNeeded should not have changed - strFailReason = _("Transaction fee and change calculation failed"); + strFailReason = _("Transaction fee and change calculation failed").translated; return false; } @@ -3079,8 +3207,6 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } } - if (nChangePosInOut == -1) reservekey.ReturnKey(); // Return any reserved key if we don't have change - // Shuffle selected coins and fill in final vin txNew.vin.clear(); std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end()); @@ -3109,7 +3235,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) { - strFailReason = _("Signing transaction failed"); + strFailReason = _("Signing transaction failed").translated; return false; } else { UpdateInput(txNew.vin.at(nIn), sigdata); @@ -3125,19 +3251,28 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // Limit size if (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) { - strFailReason = _("Transaction too large"); + strFailReason = _("Transaction too large").translated; return false; } } + if (nFeeRet > m_default_max_tx_fee) { + strFailReason = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); + return false; + } + if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { // Lastly, ensure this tx will pass the mempool's chain limits if (!chain().checkChainLimits(tx)) { - strFailReason = _("Transaction has too long of a mempool chain"); + strFailReason = _("Transaction has too long of a mempool chain").translated; return false; } } + // Before we return success, we assume any change key will be used to prevent + // accidental re-use. + reservedest.KeepDestination(); + WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Needed:%d Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", nFeeRet, nBytes, nFeeNeeded, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay, feeCalc.est.pass.start, feeCalc.est.pass.end, @@ -3152,7 +3287,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std /** * Call after CreateTransaction unless you want to abort */ -bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CReserveKey& reservekey, CValidationState& state) +bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CValidationState& state) { { auto locked_chain = chain().lock(); @@ -3166,8 +3301,6 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve WalletLogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString()); /* Continued */ { - // Take key pair from key pool so it won't be used again - reservekey.KeepKey(); // Add tx to wallet, because if it has change it's also ours, // otherwise just for transaction history. @@ -3188,12 +3321,10 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve if (fBroadcastTransactions) { - // Broadcast - if (!wtx.AcceptToMemoryPool(*locked_chain, state)) { - WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state)); + std::string err_string; + if (!wtx.SubmitMemoryPoolAndRelay(err_string, true, *locked_chain)) { + WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. - } else { - wtx.RelayWalletTransaction(*locked_chain); } } } @@ -3202,6 +3333,11 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { + // Even if we don't use this lock in this function, we want to preserve + // lock order in LoadToWallet if query of chain state is needed to know + // tx status. If lock can't be taken (e.g wallet-tool), tx confirmation + // status may be not reliable. + auto locked_chain = LockChain(); LOCK(cs_wallet); fFirstRunRet = false; @@ -3418,8 +3554,7 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) { LOCK(cs_wallet); - if (IsLocked()) - return false; + if (IsLocked()) return false; // Top up key pool unsigned int nTargetSize; @@ -3480,8 +3615,7 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe { LOCK(cs_wallet); - if (!IsLocked()) - TopUpKeyPool(); + TopUpKeyPool(); bool fReturningInternal = fRequestedInternal; fReturningInternal &= (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) || IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); @@ -3568,6 +3702,42 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) return true; } +bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error) +{ + LOCK(cs_wallet); + error.clear(); + + TopUpKeyPool(); + + // Generate a new key that is added to wallet + CPubKey new_key; + if (!GetKeyFromPool(new_key)) { + error = "Error: Keypool ran out, please call keypoolrefill first"; + return false; + } + LearnRelatedScripts(new_key, type); + dest = GetDestinationForKey(new_key, type); + + SetAddressBook(dest, label, "receive"); + return true; +} + +bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& dest, std::string& error) +{ + error.clear(); + + TopUpKeyPool(); + + ReserveDestination reservedest(this); + if (!reservedest.GetReservedDestination(type, dest, true)) { + error = "Error: Keypool ran out, please call keypoolrefill first"; + return false; + } + + reservedest.KeepDestination(); + return true; +} + static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, WalletBatch& batch) { if (setKeyPool.empty()) { return GetTime(); @@ -3747,7 +3917,7 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co return result; } -bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal) +bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestination& dest, bool internal) { if (!pwallet->CanGetAddresses(internal)) { return false; @@ -3763,25 +3933,29 @@ bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal) fInternal = keypool.fInternal; } assert(vchPubKey.IsValid()); - pubkey = vchPubKey; + pwallet->LearnRelatedScripts(vchPubKey, type); + address = GetDestinationForKey(vchPubKey, type); + dest = address; return true; } -void CReserveKey::KeepKey() +void ReserveDestination::KeepDestination() { if (nIndex != -1) pwallet->KeepKey(nIndex); nIndex = -1; vchPubKey = CPubKey(); + address = CNoDestination(); } -void CReserveKey::ReturnKey() +void ReserveDestination::ReturnDestination() { if (nIndex != -1) { pwallet->ReturnKey(nIndex, fInternal, vchPubKey); } nIndex = -1; vchPubKey = CPubKey(); + address = CNoDestination(); } void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) @@ -3874,7 +4048,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C for (const auto& entry : mapWallet) { // iterate over all wallet transactions... const CWalletTx &wtx = entry.second; - if (Optional<int> height = locked_chain.getBlockHeight(wtx.hashBlock)) { + if (Optional<int> height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock)) { // ... which are already in a block for (const CTxOut &txout : wtx.tx->vout) { // iterate over all their outputs @@ -3917,9 +4091,9 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const { unsigned int nTimeSmart = wtx.nTimeReceived; - if (!wtx.hashUnset()) { + if (!wtx.isUnconfirmed() && !wtx.isAbandoned()) { int64_t blocktime; - if (chain().findBlock(wtx.hashBlock, nullptr /* block */, &blocktime)) { + if (chain().findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &blocktime)) { int64_t latestNow = wtx.nTimeReceived; int64_t latestEntry = 0; @@ -3947,7 +4121,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); } else { - WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString()); + WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.m_confirm.hashBlock.ToString()); } } return nTimeSmart; @@ -4065,6 +4239,11 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b // Recover readable keypairs: CWallet dummyWallet(&chain, WalletLocation(), WalletDatabase::CreateDummy()); std::string backup_filename; + // Even if we don't use this lock in this function, we want to preserve + // lock order in LoadToWallet if query of chain state is needed to know + // tx status. If lock can't be taken, tx confirmation status may be not + // reliable. + auto locked_chain = dummyWallet.LockChain(); if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) { return false; } @@ -4081,17 +4260,17 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, std::vector<CWalletTx> vWtx; if (gArgs.GetBoolArg("-zapwallettxes", false)) { - chain.initMessage(_("Zapping all transactions from wallet...")); + chain.initMessage(_("Zapping all transactions from wallet...").translated); std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(&chain, location, WalletDatabase::Create(location.GetPath())); DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); if (nZapWalletRet != DBErrors::LOAD_OK) { - chain.initError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); + chain.initError(strprintf(_("Error loading %s: Wallet corrupted").translated, walletFile)); return nullptr; } } - chain.initMessage(_("Loading wallet...")); + chain.initMessage(_("Loading wallet...").translated); int64_t nStart = GetTimeMillis(); bool fFirstRun = true; @@ -4102,26 +4281,26 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, if (nLoadWalletRet != DBErrors::LOAD_OK) { if (nLoadWalletRet == DBErrors::CORRUPT) { - chain.initError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); + chain.initError(strprintf(_("Error loading %s: Wallet corrupted").translated, walletFile)); return nullptr; } else if (nLoadWalletRet == DBErrors::NONCRITICAL_ERROR) { chain.initWarning(strprintf(_("Error reading %s! All keys read correctly, but transaction data" - " or address book entries might be missing or incorrect."), + " or address book entries might be missing or incorrect.").translated, walletFile)); } else if (nLoadWalletRet == DBErrors::TOO_NEW) { - chain.initError(strprintf(_("Error loading %s: Wallet requires newer version of %s"), walletFile, _(PACKAGE_NAME))); + chain.initError(strprintf(_("Error loading %s: Wallet requires newer version of %s").translated, walletFile, PACKAGE_NAME)); return nullptr; } else if (nLoadWalletRet == DBErrors::NEED_REWRITE) { - chain.initError(strprintf(_("Wallet needed to be rewritten: restart %s to complete"), _(PACKAGE_NAME))); + chain.initError(strprintf(_("Wallet needed to be rewritten: restart %s to complete").translated, PACKAGE_NAME)); return nullptr; } else { - chain.initError(strprintf(_("Error loading %s"), walletFile)); + chain.initError(strprintf(_("Error loading %s").translated, walletFile)); return nullptr; } } @@ -4140,7 +4319,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, walletInstance->WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); if (nMaxVersion < walletInstance->GetVersion()) { - chain.initError(_("Cannot downgrade wallet")); + chain.initError(_("Cannot downgrade wallet").translated); return nullptr; } walletInstance->SetMaxVersion(nMaxVersion); @@ -4153,7 +4332,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, // Do not upgrade versions to any version between HD_SPLIT and FEATURE_PRE_SPLIT_KEYPOOL unless already supporting HD_SPLIT int max_version = walletInstance->GetVersion(); if (!walletInstance->CanSupportFeature(FEATURE_HD_SPLIT) && max_version >= FEATURE_HD_SPLIT && max_version < FEATURE_PRE_SPLIT_KEYPOOL) { - chain.initError(_("Cannot upgrade a non HD split wallet without upgrading to support pre split keypool. Please use -upgradewallet=169900 or -upgradewallet with no version specified.")); + chain.initError(_("Cannot upgrade a non HD split wallet without upgrading to support pre split keypool. Please use -upgradewallet=169900 or -upgradewallet with no version specified.").translated); return nullptr; } @@ -4181,7 +4360,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, // Regenerate the keypool if upgraded to HD if (hd_upgrade) { if (!walletInstance->TopUpKeyPool()) { - chain.initError(_("Unable to generate keys")); + chain.initError(_("Unable to generate keys").translated); return nullptr; } } @@ -4201,7 +4380,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, // Top up the keypool if (walletInstance->CanGenerateKeys() && !walletInstance->TopUpKeyPool()) { - chain.initError(_("Unable to generate initial keys")); + chain.initError(_("Unable to generate initial keys").translated); return nullptr; } @@ -4209,48 +4388,48 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, walletInstance->ChainStateFlushed(locked_chain->getTipLocator()); } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { // Make it impossible to disable private keys after creation - chain.initError(strprintf(_("Error loading %s: Private keys can only be disabled during creation"), walletFile)); + chain.initError(strprintf(_("Error loading %s: Private keys can only be disabled during creation").translated, walletFile)); return NULL; } else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { LOCK(walletInstance->cs_KeyStore); if (!walletInstance->mapKeys.empty() || !walletInstance->mapCryptedKeys.empty()) { - chain.initWarning(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys"), walletFile)); + chain.initWarning(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys").translated, walletFile)); } } if (!gArgs.GetArg("-addresstype", "").empty() && !ParseOutputType(gArgs.GetArg("-addresstype", ""), walletInstance->m_default_address_type)) { - chain.initError(strprintf("Unknown address type '%s'", gArgs.GetArg("-addresstype", ""))); + chain.initError(strprintf(_("Unknown address type '%s'").translated, gArgs.GetArg("-addresstype", ""))); return nullptr; } if (!gArgs.GetArg("-changetype", "").empty() && !ParseOutputType(gArgs.GetArg("-changetype", ""), walletInstance->m_default_change_type)) { - chain.initError(strprintf("Unknown change type '%s'", gArgs.GetArg("-changetype", ""))); + chain.initError(strprintf(_("Unknown change type '%s'").translated, gArgs.GetArg("-changetype", ""))); return nullptr; } if (gArgs.IsArgSet("-mintxfee")) { CAmount n = 0; if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || 0 == n) { - chain.initError(AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""))); + chain.initError(AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", "")).translated); return nullptr; } if (n > HIGH_TX_FEE_PER_KB) { - chain.initWarning(AmountHighWarn("-mintxfee") + " " + - _("This is the minimum transaction fee you pay on every transaction.")); + chain.initWarning(AmountHighWarn("-mintxfee").translated + " " + + _("This is the minimum transaction fee you pay on every transaction.").translated); } walletInstance->m_min_fee = CFeeRate(n); } - walletInstance->m_allow_fallback_fee = Params().IsFallbackFeeEnabled(); + walletInstance->m_allow_fallback_fee = Params().IsTestChain(); if (gArgs.IsArgSet("-fallbackfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) { - chain.initError(strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), gArgs.GetArg("-fallbackfee", ""))); + chain.initError(strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'").translated, gArgs.GetArg("-fallbackfee", ""))); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { - chain.initWarning(AmountHighWarn("-fallbackfee") + " " + - _("This is the transaction fee you may pay when fee estimates are not available.")); + chain.initWarning(AmountHighWarn("-fallbackfee").translated + " " + + _("This is the transaction fee you may pay when fee estimates are not available.").translated); } walletInstance->m_fallback_fee = CFeeRate(nFeePerK); walletInstance->m_allow_fallback_fee = nFeePerK != 0; //disable fallback fee in case value was set to 0, enable if non-null value @@ -4258,28 +4437,28 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, if (gArgs.IsArgSet("-discardfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-discardfee", ""), nFeePerK)) { - chain.initError(strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), gArgs.GetArg("-discardfee", ""))); + chain.initError(strprintf(_("Invalid amount for -discardfee=<amount>: '%s'").translated, gArgs.GetArg("-discardfee", ""))); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { - chain.initWarning(AmountHighWarn("-discardfee") + " " + - _("This is the transaction fee you may discard if change is smaller than dust at this level")); + chain.initWarning(AmountHighWarn("-discardfee").translated + " " + + _("This is the transaction fee you may discard if change is smaller than dust at this level").translated); } walletInstance->m_discard_rate = CFeeRate(nFeePerK); } if (gArgs.IsArgSet("-paytxfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) { - chain.initError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""))); + chain.initError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", "")).translated); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { - chain.initWarning(AmountHighWarn("-paytxfee") + " " + - _("This is the transaction fee you will pay if you send a transaction.")); + chain.initWarning(AmountHighWarn("-paytxfee").translated + " " + + _("This is the transaction fee you will pay if you send a transaction.").translated); } walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000); if (walletInstance->m_pay_tx_fee < chain.relayMinFee()) { - chain.initError(strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"), + chain.initError(strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)").translated, gArgs.GetArg("-paytxfee", ""), chain.relayMinFee().ToString())); return nullptr; } @@ -4289,23 +4468,24 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, { CAmount nMaxFee = 0; if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) { - chain.initError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""))); + chain.initError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", "")).translated); return nullptr; } if (nMaxFee > HIGH_MAX_TX_FEE) { - chain.initWarning(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction.")); + chain.initWarning(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction.").translated); } if (CFeeRate(nMaxFee, 1000) < chain.relayMinFee()) { - chain.initError(strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"), + chain.initError(strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)").translated, gArgs.GetArg("-maxtxfee", ""), chain.relayMinFee().ToString())); return nullptr; } walletInstance->m_default_max_tx_fee = nMaxFee; } - if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) - chain.initWarning(AmountHighWarn("-minrelaytxfee") + " " + - _("The wallet will avoid paying less than the minimum relay fee.")); + if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) { + chain.initWarning(AmountHighWarn("-minrelaytxfee").translated + " " + + _("The wallet will avoid paying less than the minimum relay fee.").translated); + } walletInstance->m_confirm_target = gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET); walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); @@ -4353,12 +4533,12 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, } if (rescan_height != block_height) { - chain.initError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)")); + chain.initError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)").translated); return nullptr; } } - chain.initMessage(_("Rescanning...")); + chain.initMessage(_("Rescanning...").translated); walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", *tip_height - rescan_height, rescan_height); // No need to read and scan block if block was created before @@ -4372,7 +4552,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, { WalletRescanReserver reserver(walletInstance.get()); if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(locked_chain->getBlockHash(rescan_height), {} /* stop block */, reserver, true /* update */).status)) { - chain.initError(_("Failed to rescan the wallet during initialization")); + chain.initError(_("Failed to rescan the wallet during initialization").translated); return nullptr; } } @@ -4458,30 +4638,26 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn) m_pre_split = false; } -CWalletKey::CWalletKey(int64_t nExpires) +void CWalletTx::SetConf(Status status, const uint256& block_hash, int posInBlock) { - nTimeCreated = (nExpires ? GetTime() : 0); - nTimeExpires = nExpires; -} + // Update tx status + m_confirm.status = status; -void CMerkleTx::SetMerkleBranch(const uint256& block_hash, int posInBlock) -{ // Update the tx's hashBlock - hashBlock = block_hash; + m_confirm.hashBlock = block_hash; // set the position of the transaction in the block - nIndex = posInBlock; + m_confirm.nIndex = posInBlock; } -int CMerkleTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const +int CWalletTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const { - if (hashUnset()) - return 0; + if (isUnconfirmed() || isAbandoned()) return 0; - return locked_chain.getBlockDepth(hashBlock) * (nIndex == -1 ? -1 : 1); + return locked_chain.getBlockDepth(m_confirm.hashBlock) * (isConflicted() ? -1 : 1); } -int CMerkleTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const +int CWalletTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const { if (!IsCoinBase()) return 0; @@ -4490,24 +4666,12 @@ int CMerkleTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const return std::max(0, (COINBASE_MATURITY+1) - chain_depth); } -bool CMerkleTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const +bool CWalletTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const { // note GetBlocksToMaturity is 0 for non-coinbase tx return GetBlocksToMaturity(locked_chain) > 0; } -bool CWalletTx::AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, CValidationState& state) -{ - // 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 - // because we think that this newly generated transaction's change is - // unavailable as we're not yet aware that it is in the mempool. - bool ret = locked_chain.submitToMemoryPool(tx, pwallet->m_default_max_tx_fee, state); - fInMempool |= ret; - return ret; -} - void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type) { if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) { @@ -4583,3 +4747,203 @@ bool CWallet::AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, cons mapKeyMetadata[pubkey.GetID()].hdKeypath = WriteHDKeypath(info.path); return batch.WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true); } + +bool CWallet::SetCrypted() +{ + LOCK(cs_KeyStore); + if (fUseCrypto) + return true; + if (!mapKeys.empty()) + return false; + fUseCrypto = true; + return true; +} + +bool CWallet::IsLocked() const +{ + if (!IsCrypted()) { + return false; + } + LOCK(cs_KeyStore); + return vMasterKey.empty(); +} + +bool CWallet::Lock() +{ + if (!SetCrypted()) + return false; + + { + LOCK(cs_KeyStore); + vMasterKey.clear(); + } + + NotifyStatusChanged(this); + return true; +} + +bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) +{ + { + LOCK(cs_KeyStore); + if (!SetCrypted()) + return false; + + bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys + bool keyFail = false; + CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); + for (; mi != mapCryptedKeys.end(); ++mi) + { + const CPubKey &vchPubKey = (*mi).second.first; + const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; + CKey key; + if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key)) + { + keyFail = true; + break; + } + keyPass = true; + if (fDecryptionThoroughlyChecked) + break; + } + if (keyPass && keyFail) + { + LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n"); + throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt."); + } + if (keyFail || (!keyPass && !accept_no_keys)) + return false; + vMasterKey = vMasterKeyIn; + fDecryptionThoroughlyChecked = true; + } + NotifyStatusChanged(this); + return true; +} + +bool CWallet::HaveKey(const CKeyID &address) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::HaveKey(address); + } + return mapCryptedKeys.count(address) > 0; +} + +bool CWallet::GetKey(const CKeyID &address, CKey& keyOut) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::GetKey(address, keyOut); + } + + CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); + if (mi != mapCryptedKeys.end()) + { + const CPubKey &vchPubKey = (*mi).second.first; + const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; + return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); + } + return false; +} + +bool CWallet::GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const +{ + LOCK(cs_KeyStore); + WatchKeyMap::const_iterator it = mapWatchKeys.find(address); + if (it != mapWatchKeys.end()) { + pubkey_out = it->second; + return true; + } + return false; +} + +bool CWallet::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + if (!FillableSigningProvider::GetPubKey(address, vchPubKeyOut)) { + return GetWatchPubKey(address, vchPubKeyOut); + } + return true; + } + + CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); + if (mi != mapCryptedKeys.end()) + { + vchPubKeyOut = (*mi).second.first; + return true; + } + // Check for watch-only pubkeys + return GetWatchPubKey(address, vchPubKeyOut); +} + +std::set<CKeyID> CWallet::GetKeys() const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::GetKeys(); + } + std::set<CKeyID> set_address; + for (const auto& mi : mapCryptedKeys) { + set_address.insert(mi.first); + } + return set_address; +} + +bool CWallet::EncryptKeys(CKeyingMaterial& vMasterKeyIn) +{ + LOCK(cs_KeyStore); + if (!mapCryptedKeys.empty() || IsCrypted()) + return false; + + fUseCrypto = true; + for (const KeyMap::value_type& mKey : mapKeys) + { + const CKey &key = mKey.second; + CPubKey vchPubKey = key.GetPubKey(); + CKeyingMaterial vchSecret(key.begin(), key.end()); + std::vector<unsigned char> vchCryptedSecret; + if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) + return false; + if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) + return false; + } + mapKeys.clear(); + return true; +} + +bool CWallet::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::AddKeyPubKey(key, pubkey); + } + + if (IsLocked()) { + return false; + } + + std::vector<unsigned char> vchCryptedSecret; + CKeyingMaterial vchSecret(key.begin(), key.end()); + if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) { + return false; + } + + if (!AddCryptedKey(pubkey, vchCryptedSecret)) { + return false; + } + return true; +} + + +bool CWallet::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) +{ + LOCK(cs_KeyStore); + if (!SetCrypted()) { + return false; + } + + mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); + ImplicitlyLearnRelatedKeyScripts(vchPubKey); + return true; +} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 7b5465c219..3428e8e001 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -12,7 +12,6 @@ #include <outputtype.h> #include <policy/feerate.h> #include <script/sign.h> -#include <streams.h> #include <tinyformat.h> #include <ui_interface.h> #include <util/strencodings.h> @@ -35,6 +34,8 @@ #include <utility> #include <vector> +#include <boost/signals2/signal.hpp> + //! Explicitly unload and delete the wallet. //! Blocks the current thread after signaling the unload intent so that all //! wallet clients release the wallet. @@ -49,6 +50,14 @@ std::vector<std::shared_ptr<CWallet>> GetWallets(); std::shared_ptr<CWallet> GetWallet(const std::string& name); std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, std::string& error, std::string& warning); +enum class WalletCreationStatus { + SUCCESS, + CREATION_FAILED, + ENCRYPTION_FAILED +}; + +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::shared_ptr<CWallet>& result); + //! Default for -keypool static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; //! -paytxfee default @@ -85,11 +94,11 @@ static constexpr size_t DUMMY_NESTED_P2WPKH_INPUT_SIZE = 91; class CCoinControl; class COutput; -class CReserveKey; class CScript; class CWalletTx; struct FeeCalculation; enum class FeeEstimateMode; +class ReserveDestination; /** (client) version numbers for particular wallet features */ enum WalletFeature @@ -254,55 +263,57 @@ public: } }; -/** A wrapper to reserve a key from a wallet keypool +/** A wrapper to reserve an address from a wallet * - * CReserveKey is used to reserve a key from the keypool. It is passed around - * during the CreateTransaction/CommitTransaction procedure. + * ReserveDestination is used to reserve an address. + * It is currently only used inside of CreateTransaction. * - * Instantiating a CReserveKey does not reserve a keypool key. To do so, - * GetReservedKey() needs to be called on the object. Once a key has been - * reserved, call KeepKey() on the CReserveKey object to make sure it is not - * returned to the keypool. Call ReturnKey() to return the key to the keypool - * so it can be re-used (for example, if the key was used in a new transaction + * Instantiating a ReserveDestination does not reserve an address. To do so, + * GetReservedDestination() needs to be called on the object. Once an address has been + * reserved, call KeepDestination() on the ReserveDestination object to make sure it is not + * returned. Call ReturnDestination() to return the address so it can be re-used (for + * example, if the address was used in a new transaction * and that transaction was not completed and needed to be aborted). * - * If a key is reserved and KeepKey() is not called, then the key will be - * returned to the keypool when the CReserveObject goes out of scope. + * If an address is reserved and KeepDestination() is not called, then the address will be + * returned when the ReserveDestination goes out of scope. */ -class CReserveKey +class ReserveDestination { protected: - //! The wallet to reserve the keypool key from + //! The wallet to reserve from CWallet* pwallet; - //! The index of the key in the keypool + //! The index of the address's key in the keypool int64_t nIndex{-1}; - //! The public key + //! The public key for the address CPubKey vchPubKey; + //! The destination + CTxDestination address; //! Whether this is from the internal (change output) keypool bool fInternal{false}; public: - //! Construct a CReserveKey object. This does NOT reserve a key from the keypool yet - explicit CReserveKey(CWallet* pwalletIn) + //! Construct a ReserveDestination object. This does NOT reserve an address yet + explicit ReserveDestination(CWallet* pwalletIn) { pwallet = pwalletIn; } - CReserveKey(const CReserveKey&) = delete; - CReserveKey& operator=(const CReserveKey&) = delete; + ReserveDestination(const ReserveDestination&) = delete; + ReserveDestination& operator=(const ReserveDestination&) = delete; //! Destructor. If a key has been reserved and not KeepKey'ed, it will be returned to the keypool - ~CReserveKey() + ~ReserveDestination() { - ReturnKey(); + ReturnDestination(); } - //! Reserve a key from the keypool - bool GetReservedKey(CPubKey &pubkey, bool internal = false); - //! Return a key to the keypool - void ReturnKey(); - //! Keep the key. Do not return it to the keypool when this object goes out of scope - void KeepKey(); + //! Reserve an address + bool GetReservedDestination(const OutputType type, CTxDestination& pubkey, bool internal); + //! Return reserved address + void ReturnDestination(); + //! Keep the address. Do not return it's key to the keypool when this object goes out of scope + void KeepDestination(); }; /** Address book data */ @@ -353,82 +364,24 @@ struct COutputEntry int vout; }; -/** A transaction with a merkle branch linking it to the block chain. */ +/** Legacy class used for deserializing vtxPrev for backwards compatibility. + * vtxPrev was removed in commit 93a18a3650292afbb441a47d1fa1b94aeb0164e3, + * but old wallet.dat files may still contain vtxPrev vectors of CMerkleTxs. + * These need to get deserialized for field alignment when deserializing + * a CWalletTx, but the deserialized values are discarded.**/ class CMerkleTx { -private: - /** Constant used in hashBlock to indicate tx has been abandoned */ - static const uint256 ABANDON_HASH; - public: - CTransactionRef tx; - uint256 hashBlock; - - /* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest - * block in the chain we know this or any in-wallet dependency conflicts - * with. Older clients interpret nIndex == -1 as unconfirmed for backward - * compatibility. - */ - int nIndex; - - CMerkleTx() - { - SetTx(MakeTransactionRef()); - Init(); - } - - explicit CMerkleTx(CTransactionRef arg) - { - SetTx(std::move(arg)); - Init(); - } - - void Init() + template<typename Stream> + void Unserialize(Stream& s) { - hashBlock = uint256(); - nIndex = -1; - } + CTransactionRef tx; + uint256 hashBlock; + std::vector<uint256> vMerkleBranch; + int nIndex; - void SetTx(CTransactionRef arg) - { - tx = std::move(arg); + s >> tx >> hashBlock >> vMerkleBranch >> nIndex; } - - ADD_SERIALIZE_METHODS; - - template <typename Stream, typename Operation> - inline void SerializationOp(Stream& s, Operation ser_action) { - std::vector<uint256> vMerkleBranch; // For compatibility with older versions. - READWRITE(tx); - READWRITE(hashBlock); - READWRITE(vMerkleBranch); - READWRITE(nIndex); - } - - void SetMerkleBranch(const uint256& block_hash, int posInBlock); - - /** - * Return depth of transaction in blockchain: - * <0 : conflicts with a transaction this deep in the blockchain - * 0 : in memory pool, waiting to be included in a block - * >=1 : this many blocks deep in the main chain - */ - 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(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(interfaces::Chain::Lock& locked_chain) const; }; //Get the marginal bytes of spending the specified output @@ -438,11 +391,16 @@ int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, * A transaction with a bunch of additional info that only the owner cares about. * It includes any unrecorded transactions needed to link it back to the block chain. */ -class CWalletTx : public CMerkleTx +class CWalletTx { private: const CWallet* pwallet; + /** Constant used in hashBlock to indicate tx has been abandoned, only used at + * serialization/deserialization to avoid ambiguity with conflicted. + */ + static const uint256 ABANDON_HASH; + public: /** * Key/value map with information about the transaction. @@ -488,7 +446,7 @@ public: * on this bitcoin node, and set to 0 for transactions that were created * externally and came in through the network or sendrawtransaction RPC. */ - char fFromMe; + bool fFromMe; int64_t nOrderPos; //!< position in ordered transaction list std::multimap<int64_t, CWalletTx*>::const_iterator m_it_wtxOrdered; @@ -500,7 +458,8 @@ public: mutable bool fInMempool; mutable CAmount nChangeCached; - CWalletTx(const CWallet* pwalletIn, CTransactionRef arg) : CMerkleTx(std::move(arg)) + CWalletTx(const CWallet* pwalletIn, CTransactionRef arg) + : tx(std::move(arg)) { Init(pwalletIn); } @@ -518,12 +477,41 @@ public: fInMempool = false; nChangeCached = 0; nOrderPos = -1; + m_confirm = Confirmation{}; } + CTransactionRef tx; + + /* New transactions start as UNCONFIRMED. At BlockConnected, + * they will transition to CONFIRMED. In case of reorg, at BlockDisconnected, + * they roll back to UNCONFIRMED. If we detect a conflicting transaction at + * block connection, we update conflicted tx and its dependencies as CONFLICTED. + * If tx isn't confirmed and outside of mempool, the user may switch it to ABANDONED + * by using the abandontransaction call. This last status may be override by a CONFLICTED + * or CONFIRMED transition. + */ + enum Status { + UNCONFIRMED, + CONFIRMED, + CONFLICTED, + ABANDONED + }; + + /* Confirmation includes tx status and a pair of {block hash/tx index in block} at which tx has been confirmed. + * This pair is both 0 if tx hasn't confirmed yet. Meaning of these fields changes with CONFLICTED state + * where they instead point to block hash and index of the deepest conflicting tx. + */ + struct Confirmation { + Status status = UNCONFIRMED; + uint256 hashBlock = uint256(); + int nIndex = 0; + }; + + Confirmation m_confirm; + template<typename Stream> void Serialize(Stream& s) const { - char fSpent = false; mapValue_t mapValueCopy = mapValue; mapValueCopy["fromaccount"] = ""; @@ -532,20 +520,41 @@ public: mapValueCopy["timesmart"] = strprintf("%u", nTimeSmart); } - s << static_cast<const CMerkleTx&>(*this); - std::vector<CMerkleTx> vUnused; //!< Used to be vtxPrev - s << vUnused << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << fSpent; + std::vector<char> dummy_vector1; //!< Used to be vMerkleBranch + std::vector<char> dummy_vector2; //!< Used to be vtxPrev + bool dummy_bool = false; //!< Used to be fSpent + uint256 serializedHash = isAbandoned() ? ABANDON_HASH : m_confirm.hashBlock; + int serializedIndex = isAbandoned() || isConflicted() ? -1 : m_confirm.nIndex; + s << tx << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool; } template<typename Stream> void Unserialize(Stream& s) { Init(nullptr); - char fSpent; - s >> static_cast<CMerkleTx&>(*this); - std::vector<CMerkleTx> vUnused; //!< Used to be vtxPrev - s >> vUnused >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> fSpent; + std::vector<uint256> dummy_vector1; //!< Used to be vMerkleBranch + std::vector<CMerkleTx> dummy_vector2; //!< Used to be vtxPrev + bool dummy_bool; //! Used to be fSpent + int serializedIndex; + s >> tx >> m_confirm.hashBlock >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool; + + /* At serialization/deserialization, an nIndex == -1 means that hashBlock refers to + * the earliest block in the chain we know this or any in-wallet ancestor conflicts + * with. If nIndex == -1 and hashBlock is ABANDON_HASH, it means transaction is abandoned. + * In same context, an nIndex >= 0 refers to a confirmed transaction (if hashBlock set) or + * unconfirmed one. Older clients interpret nIndex == -1 as unconfirmed for backward + * compatibility (pre-commit 9ac63d6). + */ + if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) { + m_confirm.hashBlock = uint256(); + setAbandoned(); + } else if (serializedIndex == -1) { + setConflicted(); + } else if (!m_confirm.hashBlock.IsNull()) { + m_confirm.nIndex = serializedIndex; + setConfirmed(); + } ReadOrderPos(nOrderPos, mapValue); nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0; @@ -556,6 +565,11 @@ public: mapValue.erase("timesmart"); } + void SetTx(CTransactionRef arg) + { + tx = std::move(arg); + } + //! make sure balances are recalculated void MarkDirty() { @@ -606,11 +620,8 @@ public: int64_t GetTxTime() const; - // Pass this transaction to the node to relay to its peers - bool RelayWalletTransaction(interfaces::Chain::Lock& locked_chain); - - /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */ - bool AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, CValidationState& state); + // Pass this transaction to node for mempool insertion and relay to peers if flag set to true + bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain); // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation @@ -619,6 +630,39 @@ public: // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" // in place. std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; + + void SetConf(Status status, const uint256& block_hash, int posInBlock); + + /** + * Return depth of transaction in blockchain: + * <0 : conflicts with a transaction this deep in the blockchain + * 0 : in memory pool, waiting to be included in a block + * >=1 : this many blocks deep in the main chain + */ + 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(interfaces::Chain::Lock& locked_chain) const; + bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; } + void setAbandoned() + { + m_confirm.status = CWalletTx::ABANDONED; + m_confirm.hashBlock = uint256(); + m_confirm.nIndex = 0; + } + bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; } + void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; } + bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; } + void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; } + void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; } + const uint256& GetHash() const { return tx->GetHash(); } + bool IsCoinBase() const { return tx->IsCoinBase(); } + bool IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const; }; class COutput @@ -665,33 +709,6 @@ public: } }; -/** Private key that includes an expiration date in case it never gets used. */ -class CWalletKey -{ -public: - CPrivKey vchPrivKey; - int64_t nTimeCreated; - int64_t nTimeExpires; - std::string strComment; - // todo: add something to note what created it (user, getnewaddress, change) - // maybe should have a map<string, string> property map - - explicit CWalletKey(int64_t nExpires=0); - - ADD_SERIALIZE_METHODS; - - template <typename Stream, typename Operation> - inline void SerializationOp(Stream& s, Operation ser_action) { - int nVersion = s.GetVersion(); - if (!(s.GetType() & SER_GETHASH)) - READWRITE(nVersion); - READWRITE(vchPrivKey); - READWRITE(nTimeCreated); - READWRITE(nTimeExpires); - READWRITE(LIMITED_STRING(strComment, 65536)); - } -}; - struct CoinSelectionParams { bool use_bnb = true; @@ -709,9 +726,35 @@ class WalletRescanReserver; //forward declarations for ScanForWalletTransactions * A CWallet is an extension of a keystore, which also maintains a set of transactions and balances, * and provides the ability to create new transactions. */ -class CWallet final : public CCryptoKeyStore, private interfaces::Chain::Notifications +class CWallet final : public FillableSigningProvider, private interfaces::Chain::Notifications { private: + CKeyingMaterial vMasterKey GUARDED_BY(cs_KeyStore); + + //! if fUseCrypto is true, mapKeys must be empty + //! if fUseCrypto is false, vMasterKey must be empty + std::atomic<bool> fUseCrypto; + + //! keeps track of whether Unlock has run a thorough check before + bool fDecryptionThoroughlyChecked; + + using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>; + using WatchOnlySet = std::set<CScript>; + using WatchKeyMap = std::map<CKeyID, CPubKey>; + + bool SetCrypted(); + + //! will encrypt previously unencrypted keys + bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); + + bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys = false); + CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); + WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore); + WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore); + + bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); + bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey); + std::atomic<bool> fAbortRescan{false}; std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver std::atomic<int64_t> m_scanning_start{0}; @@ -756,7 +799,7 @@ private: * Abandoned state should probably be more carefully tracked via different * posInBlock signals or by checking mempool presence when necessary. */ - bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */ void MarkConflicted(const uint256& hashBlock, const uint256& hashTx); @@ -768,7 +811,7 @@ private: /* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions. * Should be called with non-zero block_hash and posInBlock if this is for a transaction that is included in a block. */ - void SyncTransaction(const CTransactionRef& tx, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void SyncTransaction(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* the HD chain data model (external chain counters) */ CHDChain hdChain; @@ -794,8 +837,9 @@ private: * of the other AddWatchOnly which accepts a timestamp and sets * nTimeFirstKey more intelligently for more efficient rescans. */ - bool AddWatchOnly(const CScript& dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddWatchOnly(const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddWatchOnlyInMem(const CScript &dest); /** Add a KeyOriginInfo to the wallet */ bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info); @@ -835,6 +879,9 @@ private: */ uint256 m_last_block_processed GUARDED_BY(cs_wallet); + //! Fetches a key from the keypool + bool GetKeyFromPool(CPubKey &key, bool internal = false); + public: /* * Main wallet lock. @@ -879,7 +926,9 @@ public: /** Construct wallet with specified name and database implementation. */ CWallet(interfaces::Chain* chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database) - : m_chain(chain), + : fUseCrypto(false), + fDecryptionThoroughlyChecked(false), + m_chain(chain), m_location(location), database(std::move(database)) { @@ -893,6 +942,13 @@ public: encrypted_batch = nullptr; } + bool IsCrypted() const { return fUseCrypto; } + bool IsLocked() const; + bool Lock(); + + /** Interface to assert chain access and if successful lock it */ + std::unique_ptr<interfaces::Chain::Lock> LockChain() { return m_chain ? m_chain->lock() : nullptr; } + std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet); typedef std::multimap<int64_t, CWalletTx*> TxItems; @@ -922,7 +978,7 @@ public: /** * populate vCoins with vector of available COutputs. */ - 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); + 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 EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Return list of available coins and locked coins grouped by non-change output address. @@ -975,7 +1031,7 @@ public: //! Adds a key to the store, and saves it to disk. bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a key to the store, without saving it to disk (used by LoadWallet) - bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); } + bool LoadKey(const CKey& key, const CPubKey &pubkey) { return AddKeyPubKeyInner(key, pubkey); } //! Load metadata (used by LoadWallet) void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -986,9 +1042,13 @@ public: void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds an encrypted key to the store, and saves it to disk. - bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) override; + bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); //! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet) bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); + bool GetKey(const CKeyID &address, CKey& keyOut) const override; + bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; + bool HaveKey(const CKeyID &address) const override; + std::set<CKeyID> GetKeys() const override; bool AddCScript(const CScript& redeemScript) override; bool LoadCScript(const CScript& redeemScript); @@ -1005,9 +1065,15 @@ public: //! Adds a watch-only address to the store, and saves it to disk. bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool RemoveWatchOnly(const CScript &dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool RemoveWatchOnly(const CScript &dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) bool LoadWatchOnly(const CScript &dest); + //! Returns whether the watch-only script is in the wallet + bool HaveWatchOnly(const CScript &dest) const; + //! Returns whether there are any watch-only things in the wallet + bool HaveWatchOnly() const; + //! Fetches a pubkey from mapWatchKeys if it exists there + bool GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const; //! Holds a timestamp at which point the wallet is scheduled (externally) to be relocked. Caller must arrange for actual relocking to occur via Lock(). int64_t nRelockTime = 0; @@ -1028,7 +1094,7 @@ public: void MarkDirty(); bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true); - void LoadToWallet(const CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void LoadToWallet(CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void TransactionAddedToMempool(const CTransactionRef& tx) override; void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override; void BlockDisconnected(const CBlock& block) override; @@ -1079,9 +1145,9 @@ 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(interfaces::Chain::Lock& locked_chain, 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, 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, CValidationState& state); + bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CValidationState& state); bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const { @@ -1092,10 +1158,10 @@ public: bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig = false) const; bool DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig = false) const; - bool ImportScripts(const std::set<CScript> scripts) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE}; unsigned int m_confirm_target{DEFAULT_TX_CONFIRM_TARGET}; @@ -1136,7 +1202,6 @@ public: bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); void KeepKey(int64_t nIndex); void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey); - bool GetKeyFromPool(CPubKey &key, bool internal = false); int64_t GetOldestKeyPoolTime(); /** * Marks all keys in the keypool up to and including reserve_key as used. @@ -1149,6 +1214,9 @@ public: std::set<CTxDestination> GetLabelAddresses(const std::string& label) const; + bool GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error); + bool GetNewChangeDestination(const OutputType type, CTxDestination& dest, std::string& error); + isminetype IsMine(const CTxIn& txin) const; /** * Returns amount of debit if the input matches the @@ -1232,6 +1300,12 @@ public: /** Keypool has new keys */ boost::signals2::signal<void ()> NotifyCanGetAddressesChanged; + /** + * Wallet status (encrypted, locked) changed. + * Note: Called without locks held. + */ + boost::signals2::signal<void (CWallet* wallet)> NotifyStatusChanged; + /** Inquire whether this wallet broadcasts transactions. */ bool GetBroadcastTransactions() const { return fBroadcastTransactions; } /** Set whether this wallet broadcasts transactions. */ @@ -1296,7 +1370,7 @@ public: /** * Explicitly make the wallet learn the related scripts for outputs to the * given key. This is purely to make the wallet file compatible with older - * software, as CBasicKeyStore automatically does this implicitly for all + * software, as FillableSigningProvider automatically does this implicitly for all * keys now. */ void LearnRelatedScripts(const CPubKey& key, OutputType); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index ece97e2a75..635997afc9 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -21,45 +21,71 @@ #include <boost/thread.hpp> +namespace DBKeys { +const std::string ACENTRY{"acentry"}; +const std::string BESTBLOCK_NOMERKLE{"bestblock_nomerkle"}; +const std::string BESTBLOCK{"bestblock"}; +const std::string CRYPTED_KEY{"ckey"}; +const std::string CSCRIPT{"cscript"}; +const std::string DEFAULTKEY{"defaultkey"}; +const std::string DESTDATA{"destdata"}; +const std::string FLAGS{"flags"}; +const std::string HDCHAIN{"hdchain"}; +const std::string KEYMETA{"keymeta"}; +const std::string KEY{"key"}; +const std::string MASTER_KEY{"mkey"}; +const std::string MINVERSION{"minversion"}; +const std::string NAME{"name"}; +const std::string OLD_KEY{"wkey"}; +const std::string ORDERPOSNEXT{"orderposnext"}; +const std::string POOL{"pool"}; +const std::string PURPOSE{"purpose"}; +const std::string SETTINGS{"settings"}; +const std::string TX{"tx"}; +const std::string VERSION{"version"}; +const std::string WATCHMETA{"watchmeta"}; +const std::string WATCHS{"watchs"}; +} // namespace DBKeys + // // WalletBatch // bool WalletBatch::WriteName(const std::string& strAddress, const std::string& strName) { - return WriteIC(std::make_pair(std::string("name"), strAddress), strName); + return WriteIC(std::make_pair(DBKeys::NAME, strAddress), strName); } bool WalletBatch::EraseName(const std::string& strAddress) { // This should only be used for sending addresses, never for receiving addresses, // receiving addresses must always have an address book entry if they're not change return. - return EraseIC(std::make_pair(std::string("name"), strAddress)); + return EraseIC(std::make_pair(DBKeys::NAME, strAddress)); } bool WalletBatch::WritePurpose(const std::string& strAddress, const std::string& strPurpose) { - return WriteIC(std::make_pair(std::string("purpose"), strAddress), strPurpose); + return WriteIC(std::make_pair(DBKeys::PURPOSE, strAddress), strPurpose); } bool WalletBatch::ErasePurpose(const std::string& strAddress) { - return EraseIC(std::make_pair(std::string("purpose"), strAddress)); + return EraseIC(std::make_pair(DBKeys::PURPOSE, strAddress)); } bool WalletBatch::WriteTx(const CWalletTx& wtx) { - return WriteIC(std::make_pair(std::string("tx"), wtx.GetHash()), wtx); + return WriteIC(std::make_pair(DBKeys::TX, wtx.GetHash()), wtx); } bool WalletBatch::EraseTx(uint256 hash) { - return EraseIC(std::make_pair(std::string("tx"), hash)); + return EraseIC(std::make_pair(DBKeys::TX, hash)); } bool WalletBatch::WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite) { - return WriteIC(std::make_pair(std::string("keymeta"), pubkey), meta, overwrite); + return WriteIC(std::make_pair(DBKeys::KEYMETA, pubkey), meta, overwrite); } bool WalletBatch::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta) @@ -74,7 +100,7 @@ bool WalletBatch::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end()); vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end()); - return WriteIC(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false); + return WriteIC(std::make_pair(DBKeys::KEY, vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false); } bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey, @@ -85,75 +111,74 @@ bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey, return false; } - if (!WriteIC(std::make_pair(std::string("ckey"), vchPubKey), vchCryptedSecret, false)) { + if (!WriteIC(std::make_pair(DBKeys::CRYPTED_KEY, vchPubKey), vchCryptedSecret, false)) { return false; } - EraseIC(std::make_pair(std::string("key"), vchPubKey)); - EraseIC(std::make_pair(std::string("wkey"), vchPubKey)); + EraseIC(std::make_pair(DBKeys::KEY, vchPubKey)); return true; } bool WalletBatch::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey) { - return WriteIC(std::make_pair(std::string("mkey"), nID), kMasterKey, true); + return WriteIC(std::make_pair(DBKeys::MASTER_KEY, nID), kMasterKey, true); } bool WalletBatch::WriteCScript(const uint160& hash, const CScript& redeemScript) { - return WriteIC(std::make_pair(std::string("cscript"), hash), redeemScript, false); + return WriteIC(std::make_pair(DBKeys::CSCRIPT, hash), redeemScript, false); } bool WalletBatch::WriteWatchOnly(const CScript &dest, const CKeyMetadata& keyMeta) { - if (!WriteIC(std::make_pair(std::string("watchmeta"), dest), keyMeta)) { + if (!WriteIC(std::make_pair(DBKeys::WATCHMETA, dest), keyMeta)) { return false; } - return WriteIC(std::make_pair(std::string("watchs"), dest), '1'); + return WriteIC(std::make_pair(DBKeys::WATCHS, dest), '1'); } bool WalletBatch::EraseWatchOnly(const CScript &dest) { - if (!EraseIC(std::make_pair(std::string("watchmeta"), dest))) { + if (!EraseIC(std::make_pair(DBKeys::WATCHMETA, dest))) { return false; } - return EraseIC(std::make_pair(std::string("watchs"), dest)); + return EraseIC(std::make_pair(DBKeys::WATCHS, dest)); } bool WalletBatch::WriteBestBlock(const CBlockLocator& locator) { - WriteIC(std::string("bestblock"), CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan - return WriteIC(std::string("bestblock_nomerkle"), locator); + WriteIC(DBKeys::BESTBLOCK, CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan + return WriteIC(DBKeys::BESTBLOCK_NOMERKLE, locator); } bool WalletBatch::ReadBestBlock(CBlockLocator& locator) { - if (m_batch.Read(std::string("bestblock"), locator) && !locator.vHave.empty()) return true; - return m_batch.Read(std::string("bestblock_nomerkle"), locator); + if (m_batch.Read(DBKeys::BESTBLOCK, locator) && !locator.vHave.empty()) return true; + return m_batch.Read(DBKeys::BESTBLOCK_NOMERKLE, locator); } bool WalletBatch::WriteOrderPosNext(int64_t nOrderPosNext) { - return WriteIC(std::string("orderposnext"), nOrderPosNext); + return WriteIC(DBKeys::ORDERPOSNEXT, nOrderPosNext); } bool WalletBatch::ReadPool(int64_t nPool, CKeyPool& keypool) { - return m_batch.Read(std::make_pair(std::string("pool"), nPool), keypool); + return m_batch.Read(std::make_pair(DBKeys::POOL, nPool), keypool); } bool WalletBatch::WritePool(int64_t nPool, const CKeyPool& keypool) { - return WriteIC(std::make_pair(std::string("pool"), nPool), keypool); + return WriteIC(std::make_pair(DBKeys::POOL, nPool), keypool); } bool WalletBatch::ErasePool(int64_t nPool) { - return EraseIC(std::make_pair(std::string("pool"), nPool)); + return EraseIC(std::make_pair(DBKeys::POOL, nPool)); } bool WalletBatch::WriteMinVersion(int nVersion) { - return WriteIC(std::string("minversion"), nVersion); + return WriteIC(DBKeys::MINVERSION, nVersion); } class CWalletScanState { @@ -165,7 +190,6 @@ public: unsigned int m_unknown_records{0}; bool fIsEncrypted{false}; bool fAnyUnordered{false}; - int nFileVersion{0}; std::vector<uint256> vWalletUpgrade; CWalletScanState() { @@ -181,20 +205,15 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, // Taking advantage of the fact that pair serialization // is just the two items serialized one after the other ssKey >> strType; - if (strType == "name") - { + if (strType == DBKeys::NAME) { std::string strAddress; ssKey >> strAddress; ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].name; - } - else if (strType == "purpose") - { + } else if (strType == DBKeys::PURPOSE) { std::string strAddress; ssKey >> strAddress; ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].purpose; - } - else if (strType == "tx") - { + } else if (strType == DBKeys::TX) { uint256 hash; ssKey >> hash; CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef()); @@ -228,9 +247,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, wss.fAnyUnordered = true; pwallet->LoadToWallet(wtx); - } - else if (strType == "watchs") - { + } else if (strType == DBKeys::WATCHS) { wss.nWatchKeys++; CScript script; ssKey >> script; @@ -238,9 +255,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, ssValue >> fYes; if (fYes == '1') pwallet->LoadWatchOnly(script); - } - else if (strType == "key" || strType == "wkey") - { + } else if (strType == DBKeys::KEY) { CPubKey vchPubKey; ssKey >> vchPubKey; if (!vchPubKey.IsValid()) @@ -252,20 +267,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CPrivKey pkey; uint256 hash; - if (strType == "key") - { - wss.nKeys++; - ssValue >> pkey; - } else { - CWalletKey wkey; - ssValue >> wkey; - pkey = wkey.vchPrivKey; - } + wss.nKeys++; + ssValue >> pkey; - // Old wallets store keys as "key" [pubkey] => [privkey] + // Old wallets store keys as DBKeys::KEY [pubkey] => [privkey] // ... which was slow for wallets with lots of keys, because the public key is re-derived from the private key // using EC operations as a checksum. - // Newer wallets store keys as "key"[pubkey] => [privkey][hash(pubkey,privkey)], which is much faster while + // Newer wallets store keys as DBKeys::KEY [pubkey] => [privkey][hash(pubkey,privkey)], which is much faster while // remaining backwards-compatible. try { @@ -302,9 +310,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, strErr = "Error reading wallet database: LoadKey failed"; return false; } - } - else if (strType == "mkey") - { + } else if (strType == DBKeys::MASTER_KEY) { unsigned int nID; ssKey >> nID; CMasterKey kMasterKey; @@ -317,9 +323,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, pwallet->mapMasterKeys[nID] = kMasterKey; if (pwallet->nMasterKeyMaxID < nID) pwallet->nMasterKeyMaxID = nID; - } - else if (strType == "ckey") - { + } else if (strType == DBKeys::CRYPTED_KEY) { CPubKey vchPubKey; ssKey >> vchPubKey; if (!vchPubKey.IsValid()) @@ -337,27 +341,21 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, return false; } wss.fIsEncrypted = true; - } - else if (strType == "keymeta") - { + } else if (strType == DBKeys::KEYMETA) { CPubKey vchPubKey; ssKey >> vchPubKey; CKeyMetadata keyMeta; ssValue >> keyMeta; wss.nKeyMeta++; pwallet->LoadKeyMetadata(vchPubKey.GetID(), keyMeta); - } - else if (strType == "watchmeta") - { + } else if (strType == DBKeys::WATCHMETA) { CScript script; ssKey >> script; CKeyMetadata keyMeta; ssValue >> keyMeta; wss.nKeyMeta++; pwallet->LoadScriptMetadata(CScriptID(script), keyMeta); - } - else if (strType == "defaultkey") - { + } else if (strType == DBKeys::DEFAULTKEY) { // We don't want or need the default key, but if there is one set, // we want to make sure that it is valid so that we can detect corruption CPubKey vchPubKey; @@ -366,24 +364,14 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, strErr = "Error reading wallet database: Default Key corrupt"; return false; } - } - else if (strType == "pool") - { + } else if (strType == DBKeys::POOL) { int64_t nIndex; ssKey >> nIndex; CKeyPool keypool; ssValue >> keypool; pwallet->LoadKeyPool(nIndex, keypool); - } - else if (strType == "version") - { - ssValue >> wss.nFileVersion; - if (wss.nFileVersion == 10300) - wss.nFileVersion = 300; - } - else if (strType == "cscript") - { + } else if (strType == DBKeys::CSCRIPT) { uint160 hash; ssKey >> hash; CScript script; @@ -393,33 +381,31 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, strErr = "Error reading wallet database: LoadCScript failed"; return false; } - } - else if (strType == "orderposnext") - { + } else if (strType == DBKeys::ORDERPOSNEXT) { ssValue >> pwallet->nOrderPosNext; - } - else if (strType == "destdata") - { + } else if (strType == DBKeys::DESTDATA) { std::string strAddress, strKey, strValue; ssKey >> strAddress; ssKey >> strKey; ssValue >> strValue; pwallet->LoadDestData(DecodeDestination(strAddress), strKey, strValue); - } - else if (strType == "hdchain") - { + } else if (strType == DBKeys::HDCHAIN) { CHDChain chain; ssValue >> chain; pwallet->SetHDChain(chain, true); - } else if (strType == "flags") { + } else if (strType == DBKeys::FLAGS) { uint64_t flags; ssValue >> flags; if (!pwallet->SetWalletFlags(flags, true)) { strErr = "Error reading wallet database: Unknown non-tolerable wallet flags found"; return false; } - } else if (strType != "bestblock" && strType != "bestblock_nomerkle" && - strType != "minversion" && strType != "acentry") { + } else if (strType == DBKeys::OLD_KEY) { + strErr = "Found unsupported 'wkey' record, try loading with version 0.18"; + return false; + } else if (strType != DBKeys::BESTBLOCK && strType != DBKeys::BESTBLOCK_NOMERKLE && + strType != DBKeys::MINVERSION && strType != DBKeys::ACENTRY && + strType != DBKeys::VERSION && strType != DBKeys::SETTINGS) { wss.m_unknown_records++; } } catch (const std::exception& e) { @@ -438,8 +424,8 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, bool WalletBatch::IsKeyType(const std::string& strType) { - return (strType== "key" || strType == "wkey" || - strType == "mkey" || strType == "ckey"); + return (strType == DBKeys::KEY || + strType == DBKeys::MASTER_KEY || strType == DBKeys::CRYPTED_KEY); } DBErrors WalletBatch::LoadWallet(CWallet* pwallet) @@ -451,8 +437,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) LOCK(pwallet->cs_wallet); try { int nMinVersion = 0; - if (m_batch.Read((std::string)"minversion", nMinVersion)) - { + if (m_batch.Read(DBKeys::MINVERSION, nMinVersion)) { if (nMinVersion > FEATURE_LATEST) return DBErrors::TOO_NEW; pwallet->LoadMinVersion(nMinVersion); @@ -486,15 +471,15 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) { // losing keys is considered a catastrophic error, anything else // we assume the user can live with: - if (IsKeyType(strType) || strType == "defaultkey") { + if (IsKeyType(strType) || strType == DBKeys::DEFAULTKEY) { result = DBErrors::CORRUPT; - } else if(strType == "flags") { + } else if (strType == DBKeys::FLAGS) { // reading the wallet flags can only fail if unknown flags are present result = DBErrors::TOO_NEW; } else { // Leave other errors alone, if we try to fix them we might make things worse. fNoncriticalErrors = true; // ... but do warn the user there is something wrong. - if (strType == "tx") + if (strType == DBKeys::TX) // Rescan if there is a bad transaction record: gArgs.SoftSetBoolArg("-rescan", true); } @@ -519,7 +504,12 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) if (result != DBErrors::LOAD_OK) return result; - pwallet->WalletLogPrintf("nFileVersion = %d\n", wss.nFileVersion); + // Last client version to open this wallet, was previously the file version number + int last_client = CLIENT_VERSION; + m_batch.Read(DBKeys::VERSION, last_client); + + int wallet_version = pwallet->GetVersion(); + pwallet->WalletLogPrintf("Wallet File Version = %d\n", wallet_version > 0 ? wallet_version : last_client); pwallet->WalletLogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total. Unknown wallet records: %u\n", wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys, wss.m_unknown_records); @@ -532,11 +522,11 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) WriteTx(pwallet->mapWallet.at(hash)); // Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc: - if (wss.fIsEncrypted && (wss.nFileVersion == 40000 || wss.nFileVersion == 50000)) + if (wss.fIsEncrypted && (last_client == 40000 || last_client == 50000)) return DBErrors::NEED_REWRITE; - if (wss.nFileVersion < CLIENT_VERSION) // Update - WriteVersion(CLIENT_VERSION); + if (last_client < CLIENT_VERSION) // Update + m_batch.Write(DBKeys::VERSION, CLIENT_VERSION); if (wss.fAnyUnordered) result = pwallet->ReorderTransactions(); @@ -558,8 +548,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CW try { int nMinVersion = 0; - if (m_batch.Read((std::string)"minversion", nMinVersion)) - { + if (m_batch.Read(DBKeys::MINVERSION, nMinVersion)) { if (nMinVersion > FEATURE_LATEST) return DBErrors::TOO_NEW; } @@ -588,7 +577,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CW std::string strType; ssKey >> strType; - if (strType == "tx") { + if (strType == DBKeys::TX) { uint256 hash; ssKey >> hash; @@ -723,8 +712,9 @@ bool WalletBatch::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, C fReadOK = ReadKeyValue(dummyWallet, ssKey, ssValue, dummyWss, strType, strErr); } - if (!IsKeyType(strType) && strType != "hdchain") + if (!IsKeyType(strType) && strType != DBKeys::HDCHAIN) { return false; + } if (!fReadOK) { LogPrintf("WARNING: WalletBatch::Recover skipping %s: %s\n", strType, strErr); @@ -746,23 +736,23 @@ bool WalletBatch::VerifyDatabaseFile(const fs::path& wallet_path, std::string& w bool WalletBatch::WriteDestData(const std::string &address, const std::string &key, const std::string &value) { - return WriteIC(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value); + return WriteIC(std::make_pair(DBKeys::DESTDATA, std::make_pair(address, key)), value); } bool WalletBatch::EraseDestData(const std::string &address, const std::string &key) { - return EraseIC(std::make_pair(std::string("destdata"), std::make_pair(address, key))); + return EraseIC(std::make_pair(DBKeys::DESTDATA, std::make_pair(address, key))); } bool WalletBatch::WriteHDChain(const CHDChain& chain) { - return WriteIC(std::string("hdchain"), chain); + return WriteIC(DBKeys::HDCHAIN, chain); } bool WalletBatch::WriteWalletFlags(const uint64_t flags) { - return WriteIC(std::string("flags"), flags); + return WriteIC(DBKeys::FLAGS, flags); } bool WalletBatch::TxnBegin() @@ -779,13 +769,3 @@ bool WalletBatch::TxnAbort() { return m_batch.TxnAbort(); } - -bool WalletBatch::ReadVersion(int& nVersion) -{ - return m_batch.ReadVersion(nVersion); -} - -bool WalletBatch::WriteVersion(int nVersion) -{ - return m_batch.WriteVersion(nVersion); -} diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index d4a3bba97a..0fee35934d 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -55,6 +55,32 @@ enum class DBErrors NEED_REWRITE }; +namespace DBKeys { +extern const std::string ACENTRY; +extern const std::string BESTBLOCK; +extern const std::string BESTBLOCK_NOMERKLE; +extern const std::string CRYPTED_KEY; +extern const std::string CSCRIPT; +extern const std::string DEFAULTKEY; +extern const std::string DESTDATA; +extern const std::string FLAGS; +extern const std::string HDCHAIN; +extern const std::string KEY; +extern const std::string KEYMETA; +extern const std::string MASTER_KEY; +extern const std::string MINVERSION; +extern const std::string NAME; +extern const std::string OLD_KEY; +extern const std::string ORDERPOSNEXT; +extern const std::string POOL; +extern const std::string PURPOSE; +extern const std::string SETTINGS; +extern const std::string TX; +extern const std::string VERSION; +extern const std::string WATCHMETA; +extern const std::string WATCHS; +} // namespace DBKeys + /* simple HD chain data model */ class CHDChain { @@ -249,10 +275,6 @@ public: bool TxnCommit(); //! Abort current transaction bool TxnAbort(); - //! Read wallet version - bool ReadVersion(int& nVersion); - //! Write wallet version - bool WriteVersion(int nVersion); private: BerkeleyBatch m_batch; WalletDatabase& m_database; diff --git a/src/walletinitinterface.h b/src/walletinitinterface.h index 22aca65990..2e1fdf4f3a 100644 --- a/src/walletinitinterface.h +++ b/src/walletinitinterface.h @@ -5,10 +5,6 @@ #ifndef BITCOIN_WALLETINITINTERFACE_H #define BITCOIN_WALLETINITINTERFACE_H -#include <string> - -class CScheduler; -class CRPCTable; struct InitInterfaces; class WalletInitInterface { diff --git a/src/warnings.cpp b/src/warnings.cpp index 5542412a7f..35d2033ba8 100644 --- a/src/warnings.cpp +++ b/src/warnings.cpp @@ -3,9 +3,11 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <warnings.h> + #include <sync.h> #include <util/system.h> -#include <warnings.h> +#include <util/translation.h> static RecursiveMutex cs_warnings; static std::string strMiscWarning GUARDED_BY(cs_warnings); @@ -46,7 +48,7 @@ std::string GetWarnings(const std::string& strFor) if (!CLIENT_VERSION_IS_RELEASE) { strStatusBar = "This is a pre-release test build - use at your own risk - do not use for mining or merchant applications"; - strGUI = _("This is a pre-release test build - use at your own risk - do not use for mining or merchant applications"); + strGUI = _("This is a pre-release test build - use at your own risk - do not use for mining or merchant applications").translated; } // Misc warnings like out of disk space and clock is wrong @@ -59,12 +61,12 @@ std::string GetWarnings(const std::string& strFor) if (fLargeWorkForkFound) { strStatusBar = "Warning: The network does not appear to fully agree! Some miners appear to be experiencing issues."; - strGUI += (strGUI.empty() ? "" : uiAlertSeperator) + _("Warning: The network does not appear to fully agree! Some miners appear to be experiencing issues."); + strGUI += (strGUI.empty() ? "" : uiAlertSeperator) + _("Warning: The network does not appear to fully agree! Some miners appear to be experiencing issues.").translated; } else if (fLargeWorkInvalidChainFound) { strStatusBar = "Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade."; - strGUI += (strGUI.empty() ? "" : uiAlertSeperator) + _("Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade."); + strGUI += (strGUI.empty() ? "" : uiAlertSeperator) + _("Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade.").translated; } if (strFor == "gui") diff --git a/src/zmq/zmqrpc.cpp b/src/zmq/zmqrpc.cpp index a34968ef7d..cf97b7ecce 100644 --- a/src/zmq/zmqrpc.cpp +++ b/src/zmq/zmqrpc.cpp @@ -15,8 +15,6 @@ namespace { UniValue getzmqnotifications(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) { - throw std::runtime_error( RPCHelpMan{"getzmqnotifications", "\nReturns information about the active ZeroMQ notifications.\n", {}, @@ -34,8 +32,7 @@ UniValue getzmqnotifications(const JSONRPCRequest& request) HelpExampleCli("getzmqnotifications", "") + HelpExampleRpc("getzmqnotifications", "") }, - }.ToString()); - } + }.Check(request); UniValue result(UniValue::VARR); if (g_zmq_notification_interface != nullptr) { |