aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.clang-tidy11
-rw-r--r--src/Makefile.am97
-rw-r--r--src/Makefile.bench.include22
-rw-r--r--src/Makefile.qt.include2
-rw-r--r--src/Makefile.qttest.include2
-rw-r--r--src/Makefile.test.include33
-rw-r--r--src/Makefile.test_fuzz.include7
-rw-r--r--src/Makefile.test_util.include28
-rw-r--r--src/addrdb.cpp4
-rw-r--r--src/addrman.cpp10
-rw-r--r--src/addrman.h7
-rw-r--r--src/addrman_impl.h4
-rw-r--r--src/banman.cpp8
-rw-r--r--src/bech32.cpp2
-rw-r--r--src/bench/addrman.cpp10
-rw-r--r--src/bench/base58.cpp6
-rw-r--r--src/bench/bech32.cpp4
-rw-r--r--src/bench/bench.cpp55
-rw-r--r--src/bench/bench.h22
-rw-r--r--src/bench/bench_bitcoin.cpp38
-rw-r--r--src/bench/block_assemble.cpp2
-rw-r--r--src/bench/ccoins_caching.cpp2
-rw-r--r--src/bench/chacha20.cpp6
-rw-r--r--src/bench/chacha_poly_aead.cpp18
-rw-r--r--src/bench/checkblock.cpp4
-rw-r--r--src/bench/checkqueue.cpp2
-rw-r--r--src/bench/coin_selection.cpp6
-rw-r--r--src/bench/crypto_hash.cpp32
-rw-r--r--src/bench/descriptors.cpp36
-rw-r--r--src/bench/duplicate_inputs.cpp2
-rw-r--r--src/bench/examples.cpp2
-rw-r--r--src/bench/gcs_filter.cpp10
-rw-r--r--src/bench/hashpadding.cpp4
-rw-r--r--src/bench/lockedpool.cpp2
-rw-r--r--src/bench/logging.cpp10
-rw-r--r--src/bench/mempool_eviction.cpp2
-rw-r--r--src/bench/mempool_stress.cpp4
-rw-r--r--src/bench/merkle_root.cpp2
-rw-r--r--src/bench/nanobench.h2
-rw-r--r--src/bench/peer_eviction.cpp12
-rw-r--r--src/bench/poly1305.cpp6
-rw-r--r--src/bench/prevector.cpp4
-rw-r--r--src/bench/rollingbloom.cpp4
-rw-r--r--src/bench/rpc_blockchain.cpp4
-rw-r--r--src/bench/rpc_mempool.cpp2
-rw-r--r--src/bench/strencodings.cpp2
-rw-r--r--src/bench/util_time.cpp8
-rw-r--r--src/bench/verify_script.cpp4
-rw-r--r--src/bench/wallet_balance.cpp8
-rw-r--r--src/bench/wallet_create_tx.cpp142
-rw-r--r--src/bench/wallet_loading.cpp9
-rw-r--r--src/bitcoin-chainstate.cpp23
-rw-r--r--src/bitcoin-cli.cpp8
-rw-r--r--src/bitcoin-tx.cpp5
-rw-r--r--src/bitcoin-wallet.cpp21
-rw-r--r--src/bitcoind.cpp4
-rw-r--r--src/blockfilter.cpp18
-rw-r--r--src/blockfilter.h11
-rw-r--r--src/chain.cpp55
-rw-r--r--src/chain.h18
-rw-r--r--src/chainparams.cpp40
-rw-r--r--src/chainparamsseeds.h1174
-rw-r--r--src/checkqueue.h3
-rw-r--r--src/coins.cpp2
-rw-r--r--src/common/run_command.cpp64
-rw-r--r--src/common/run_command.h21
-rw-r--r--src/common/url.cpp (renamed from src/util/url.cpp)2
-rw-r--r--src/common/url.h (renamed from src/util/url.h)6
-rw-r--r--src/compat/compat.h8
-rw-r--r--src/consensus/consensus.h2
-rw-r--r--src/consensus/params.h5
-rw-r--r--src/consensus/validation.h1
-rw-r--r--src/core_read.cpp2
-rw-r--r--src/crc32c/CMakeLists.txt7
-rw-r--r--src/crypto/chacha20.h2
-rw-r--r--src/crypto/hkdf_sha256_32.h2
-rw-r--r--src/crypto/hmac_sha256.h2
-rw-r--r--src/crypto/hmac_sha512.h2
-rw-r--r--src/crypto/poly1305.h2
-rw-r--r--src/crypto/ripemd160.h2
-rw-r--r--src/crypto/sha1.h2
-rw-r--r--src/crypto/sha256.h2
-rw-r--r--src/crypto/sha256_sse4.cpp2
-rw-r--r--src/crypto/sha3.h2
-rw-r--r--src/crypto/sha512.cpp2
-rw-r--r--src/crypto/sha512.h2
-rw-r--r--src/cuckoocache.h23
-rw-r--r--src/dbwrapper.cpp2
-rw-r--r--src/dbwrapper.h18
-rw-r--r--src/external_signer.cpp7
-rw-r--r--src/fs.cpp2
-rw-r--r--src/fs.h4
-rw-r--r--src/hash.cpp2
-rw-r--r--src/headerssync.cpp317
-rw-r--r--src/headerssync.h277
-rw-r--r--src/httpserver.cpp12
-rw-r--r--src/i2p.cpp64
-rw-r--r--src/i2p.h19
-rw-r--r--src/index/base.cpp20
-rw-r--r--src/index/base.h11
-rw-r--r--src/index/blockfilterindex.cpp4
-rw-r--r--src/index/blockfilterindex.h8
-rw-r--r--src/index/coinstatsindex.cpp9
-rw-r--r--src/index/coinstatsindex.h16
-rw-r--r--src/index/txindex.cpp2
-rw-r--r--src/index/txindex.h4
-rw-r--r--src/init.cpp240
-rw-r--r--src/init/common.cpp30
-rw-r--r--src/init/common.h1
-rw-r--r--src/interfaces/chain.h8
-rw-r--r--src/interfaces/node.h2
-rw-r--r--src/interfaces/wallet.h44
-rw-r--r--src/ipc/interfaces.cpp4
-rw-r--r--src/ipc/process.cpp2
-rw-r--r--src/kernel/chainstatemanager_opts.h18
-rw-r--r--src/kernel/mempool_limits.h9
-rw-r--r--src/kernel/mempool_options.h19
-rw-r--r--src/kernel/mempool_persist.cpp2
-rw-r--r--src/kernel/mempool_persist.h4
-rw-r--r--src/kernel/validation_cache_sizes.h20
-rw-r--r--src/key.cpp1
-rw-r--r--src/key.h4
-rw-r--r--src/leveldb/util/env_windows.cc2
-rw-r--r--src/logging.cpp91
-rw-r--r--src/logging.h57
-rw-r--r--src/mempool_args.cpp39
-rw-r--r--src/memusage.h3
-rw-r--r--src/minisketch/configure.ac3
-rw-r--r--src/minisketch/src/bench.cpp4
-rw-r--r--src/minisketch/src/int_utils.h24
-rw-r--r--src/minisketch/src/test.cpp1
-rw-r--r--src/net.cpp162
-rw-r--r--src/net.h76
-rw-r--r--src/net_processing.cpp715
-rw-r--r--src/net_processing.h3
-rw-r--r--src/netbase.cpp28
-rw-r--r--src/netbase.h2
-rw-r--r--src/node/blockstorage.cpp27
-rw-r--r--src/node/blockstorage.h10
-rw-r--r--src/node/caches.cpp2
-rw-r--r--src/node/chainstate.cpp52
-rw-r--r--src/node/chainstatemanager_args.cpp39
-rw-r--r--src/node/chainstatemanager_args.h19
-rw-r--r--src/node/interface_ui.cpp2
-rw-r--r--src/node/interface_ui.h2
-rw-r--r--src/node/interfaces.cpp53
-rw-r--r--src/node/mempool_args.cpp100
-rw-r--r--src/node/mempool_args.h (renamed from src/mempool_args.h)13
-rw-r--r--src/node/miner.cpp30
-rw-r--r--src/node/miner.h6
-rw-r--r--src/node/psbt.cpp4
-rw-r--r--src/node/txreconciliation.cpp184
-rw-r--r--src/node/txreconciliation.h90
-rw-r--r--src/node/utxo_snapshot.cpp91
-rw-r--r--src/node/utxo_snapshot.h35
-rw-r--r--src/node/validation_cache_args.cpp34
-rw-r--r--src/node/validation_cache_args.h17
-rw-r--r--src/outputtype.cpp12
-rw-r--r--src/outputtype.h1
-rw-r--r--src/policy/fees.cpp42
-rw-r--r--src/policy/fees.h11
-rw-r--r--src/policy/policy.cpp13
-rw-r--r--src/policy/policy.h10
-rw-r--r--src/policy/rbf.cpp15
-rw-r--r--src/policy/rbf.h30
-rw-r--r--src/policy/settings.cpp5
-rw-r--r--src/policy/settings.h29
-rw-r--r--src/pow.cpp51
-rw-r--r--src/pow.h14
-rw-r--r--src/prevector.h2
-rw-r--r--src/primitives/block.h8
-rw-r--r--src/protocol.cpp9
-rw-r--r--src/protocol.h8
-rw-r--r--src/psbt.cpp49
-rw-r--r--src/psbt.h33
-rw-r--r--src/pubkey.cpp1
-rw-r--r--src/pubkey.h4
-rw-r--r--src/qt/addresstablemodel.cpp2
-rw-r--r--src/qt/bitcoin.cpp2
-rw-r--r--src/qt/bitcoingui.cpp30
-rw-r--r--src/qt/bitcoingui.h5
-rw-r--r--src/qt/bitcoinstrings.cpp87
-rw-r--r--src/qt/clientmodel.cpp18
-rw-r--r--src/qt/clientmodel.h10
-rw-r--r--src/qt/forms/debugwindow.ui4
-rw-r--r--src/qt/forms/intro.ui2
-rw-r--r--src/qt/forms/optionsdialog.ui2
-rw-r--r--src/qt/guiutil.cpp9
-rw-r--r--src/qt/locale/bitcoin_en.ts701
-rw-r--r--src/qt/locale/bitcoin_en.xlf3644
-rw-r--r--src/qt/main.cpp2
-rw-r--r--src/qt/modaloverlay.cpp12
-rw-r--r--src/qt/modaloverlay.h3
-rw-r--r--src/qt/optionsdialog.cpp4
-rw-r--r--src/qt/overviewpage.cpp23
-rw-r--r--src/qt/overviewpage.h1
-rw-r--r--src/qt/rpcconsole.cpp18
-rw-r--r--src/qt/rpcconsole.h4
-rw-r--r--src/qt/sendcoinsdialog.cpp18
-rw-r--r--src/qt/sendcoinsdialog.h6
-rw-r--r--src/qt/splashscreen.cpp2
-rw-r--r--src/qt/test/wallettests.cpp32
-rw-r--r--src/qt/utilitydialog.cpp2
-rw-r--r--src/qt/walletcontroller.cpp27
-rw-r--r--src/qt/walletmodel.cpp24
-rw-r--r--src/qt/walletmodel.h7
-rw-r--r--src/random.cpp2
-rw-r--r--src/rest.cpp81
-rw-r--r--src/rpc/blockchain.cpp296
-rw-r--r--src/rpc/blockchain.h4
-rw-r--r--src/rpc/client.cpp6
-rw-r--r--src/rpc/fees.cpp5
-rw-r--r--src/rpc/mempool.cpp20
-rw-r--r--src/rpc/mining.cpp34
-rw-r--r--src/rpc/net.cpp35
-rw-r--r--src/rpc/output_script.cpp2
-rw-r--r--src/rpc/rawtransaction.cpp10
-rw-r--r--src/rpc/rawtransaction_util.cpp4
-rw-r--r--src/rpc/request.cpp6
-rw-r--r--src/rpc/server.cpp13
-rw-r--r--src/rpc/txoutproof.cpp2
-rw-r--r--src/rpc/util.cpp19
-rw-r--r--src/rpc/util.h28
-rw-r--r--src/script/descriptor.cpp62
-rw-r--r--src/script/interpreter.h4
-rw-r--r--src/script/miniscript.h284
-rw-r--r--src/script/sigcache.cpp18
-rw-r--r--src/script/sigcache.h11
-rw-r--r--src/script/sign.cpp29
-rw-r--r--src/script/sign.h8
-rw-r--r--src/script/signingprovider.cpp22
-rw-r--r--src/script/signingprovider.h5
-rw-r--r--src/script/standard.cpp3
-rw-r--r--src/script/standard.h13
-rw-r--r--src/streams.h9
-rw-r--r--src/support/cleanse.h2
-rw-r--r--src/sync.h49
-rw-r--r--src/test/addrman_tests.cpp18
-rw-r--r--src/test/banman_tests.cpp4
-rw-r--r--src/test/base58_tests.cpp4
-rw-r--r--src/test/bip32_tests.cpp18
-rw-r--r--src/test/blockchain_tests.cpp4
-rw-r--r--src/test/blockencodings_tests.cpp1
-rw-r--r--src/test/blockfilter_index_tests.cpp8
-rw-r--r--src/test/blockfilter_tests.cpp2
-rw-r--r--src/test/blockmanager_tests.cpp42
-rw-r--r--src/test/coinstatsindex_tests.cpp23
-rw-r--r--src/test/denialofservice_tests.cpp49
-rw-r--r--src/test/descriptor_tests.cpp125
-rw-r--r--src/test/fuzz/bitdeque.cpp541
-rw-r--r--src/test/fuzz/integer.cpp4
-rw-r--r--src/test/fuzz/key.cpp6
-rw-r--r--src/test/fuzz/mempool_utils.h19
-rw-r--r--src/test/fuzz/policy_estimator.cpp1
-rw-r--r--src/test/fuzz/pow.cpp37
-rw-r--r--src/test/fuzz/process_message.cpp8
-rw-r--r--src/test/fuzz/process_messages.cpp7
-rw-r--r--src/test/fuzz/rbf.cpp4
-rw-r--r--src/test/fuzz/rpc.cpp1
-rw-r--r--src/test/fuzz/script.cpp3
-rw-r--r--src/test/fuzz/script_sigcache.cpp11
-rw-r--r--src/test/fuzz/string.cpp2
-rw-r--r--src/test/fuzz/transaction.cpp5
-rw-r--r--src/test/fuzz/tx_pool.cpp18
-rw-r--r--src/test/fuzz/txorphan.cpp3
-rw-r--r--src/test/fuzz/util.cpp40
-rw-r--r--src/test/fuzz/util.h23
-rw-r--r--src/test/fuzz/util/mempool.cpp27
-rw-r--r--src/test/fuzz/util/mempool.h24
-rw-r--r--src/test/fuzz/utxo_snapshot.cpp2
-rw-r--r--src/test/fuzz/validation_load_mempool.cpp5
-rw-r--r--src/test/headers_sync_chainwork_tests.cpp146
-rw-r--r--src/test/i2p_tests.cpp6
-rw-r--r--src/test/interfaces_tests.cpp6
-rw-r--r--src/test/key_io_tests.cpp6
-rw-r--r--src/test/logging_tests.cpp125
-rw-r--r--src/test/mempool_tests.cpp5
-rw-r--r--src/test/miner_tests.cpp478
-rw-r--r--src/test/miniscript_tests.cpp3
-rw-r--r--src/test/minisketch_tests.cpp2
-rw-r--r--src/test/multisig_tests.cpp22
-rw-r--r--src/test/net_tests.cpp9
-rw-r--r--src/test/netbase_tests.cpp23
-rw-r--r--src/test/policyestimator_tests.cpp1
-rw-r--r--src/test/pow_tests.cpp27
-rw-r--r--src/test/raii_event_tests.cpp2
-rw-r--r--src/test/rbf_tests.cpp3
-rw-r--r--src/test/result_tests.cpp96
-rw-r--r--src/test/script_p2sh_tests.cpp14
-rw-r--r--src/test/script_tests.cpp2
-rw-r--r--src/test/sighash_tests.cpp2
-rw-r--r--src/test/sigopcount_tests.cpp1
-rw-r--r--src/test/skiplist_tests.cpp8
-rw-r--r--src/test/system_tests.cpp12
-rw-r--r--src/test/transaction_tests.cpp30
-rw-r--r--src/test/txindex_tests.cpp11
-rw-r--r--src/test/txreconciliation_tests.cpp86
-rw-r--r--src/test/txvalidationcache_tests.cpp5
-rw-r--r--src/test/util/chainstate.h56
-rw-r--r--src/test/util/mining.cpp2
-rw-r--r--src/test/util/net.cpp12
-rw-r--r--src/test/util/net.h10
-rw-r--r--src/test/util/setup_common.cpp88
-rw-r--r--src/test/util/setup_common.h58
-rw-r--r--src/test/util/txmempool.cpp39
-rw-r--r--src/test/util/txmempool.h37
-rw-r--r--src/test/util/validation.h2
-rw-r--r--src/test/util/wallet.cpp9
-rw-r--r--src/test/util/wallet.h5
-rw-r--r--src/test/util_tests.cpp42
-rw-r--r--src/test/validation_block_tests.cpp17
-rw-r--r--src/test/validation_chainstate_tests.cpp24
-rw-r--r--src/test/validation_chainstatemanager_tests.cpp426
-rw-r--r--src/test/validation_flush_tests.cpp13
-rw-r--r--src/threadinterrupt.h1
-rw-r--r--src/timedata.cpp4
-rw-r--r--src/timedata.h3
-rw-r--r--src/txdb.h4
-rw-r--r--src/txmempool.cpp123
-rw-r--r--src/txmempool.h86
-rw-r--r--src/uint256.h11
-rw-r--r--src/univalue/include/univalue.h13
-rw-r--r--src/univalue/lib/univalue.cpp22
-rw-r--r--src/util/bip32.cpp2
-rw-r--r--src/util/bitdeque.h469
-rw-r--r--src/util/error.cpp5
-rw-r--r--src/util/error.h2
-rw-r--r--src/util/message.cpp1
-rw-r--r--src/util/result.h87
-rw-r--r--src/util/sock.cpp32
-rw-r--r--src/util/sock.h12
-rw-r--r--src/util/strencodings.cpp9
-rw-r--r--src/util/strencodings.h11
-rw-r--r--src/util/string.cpp8
-rw-r--r--src/util/string.h39
-rw-r--r--src/util/system.cpp92
-rw-r--r--src/util/system.h12
-rw-r--r--src/util/thread.cpp6
-rw-r--r--src/util/thread.h3
-rw-r--r--src/util/time.cpp16
-rw-r--r--src/util/time.h2
-rw-r--r--src/validation.cpp734
-rw-r--r--src/validation.h163
-rw-r--r--src/wallet/bdb.cpp10
-rw-r--r--src/wallet/coincontrol.h2
-rw-r--r--src/wallet/coinselection.cpp65
-rw-r--r--src/wallet/coinselection.h49
-rw-r--r--src/wallet/external_signer_scriptpubkeyman.cpp3
-rw-r--r--src/wallet/feebumper.cpp104
-rw-r--r--src/wallet/feebumper.h66
-rw-r--r--src/wallet/fees.cpp2
-rw-r--r--src/wallet/interfaces.cpp51
-rw-r--r--src/wallet/load.cpp2
-rw-r--r--src/wallet/receive.cpp8
-rw-r--r--src/wallet/receive.h3
-rw-r--r--src/wallet/rpc/addresses.cpp10
-rw-r--r--src/wallet/rpc/backup.cpp129
-rw-r--r--src/wallet/rpc/coins.cpp18
-rw-r--r--src/wallet/rpc/spend.cpp83
-rw-r--r--src/wallet/rpc/transactions.cpp33
-rw-r--r--src/wallet/rpc/util.cpp36
-rw-r--r--src/wallet/rpc/util.h6
-rw-r--r--src/wallet/rpc/wallet.cpp166
-rw-r--r--src/wallet/scriptpubkeyman.cpp439
-rw-r--r--src/wallet/scriptpubkeyman.h30
-rw-r--r--src/wallet/spend.cpp528
-rw-r--r--src/wallet/spend.h64
-rw-r--r--src/wallet/test/availablecoins_tests.cpp32
-rw-r--r--src/wallet/test/coinselector_tests.cpp197
-rw-r--r--src/wallet/test/feebumper_tests.cpp56
-rw-r--r--src/wallet/test/fuzz/coinselection.cpp10
-rw-r--r--src/wallet/test/fuzz/notifications.cpp5
-rw-r--r--src/wallet/test/fuzz/parse_iso8601.cpp (renamed from src/test/fuzz/parse_iso8601.cpp)5
-rw-r--r--src/wallet/test/ismine_tests.cpp37
-rw-r--r--src/wallet/test/rpc_util_tests.cpp24
-rw-r--r--src/wallet/test/spend_tests.cpp4
-rw-r--r--src/wallet/test/wallet_tests.cpp139
-rw-r--r--src/wallet/test/walletload_tests.cpp54
-rw-r--r--src/wallet/transaction.h7
-rw-r--r--src/wallet/wallet.cpp982
-rw-r--r--src/wallet/wallet.h74
-rw-r--r--src/wallet/walletdb.cpp73
-rw-r--r--src/wallet/walletdb.h9
-rw-r--r--src/wallet/wallettool.cpp2
-rw-r--r--src/wallet/walletutil.h14
-rw-r--r--src/zmq/zmqabstractnotifier.h2
-rw-r--r--src/zmq/zmqnotificationinterface.cpp14
-rw-r--r--src/zmq/zmqnotificationinterface.h4
-rw-r--r--src/zmq/zmqpublishnotifier.cpp19
-rw-r--r--src/zmq/zmqpublishnotifier.h4
-rw-r--r--src/zmq/zmqrpc.cpp5
391 files changed, 14200 insertions, 6143 deletions
diff --git a/src/.clang-tidy b/src/.clang-tidy
index b9371b147b..9d78ccc959 100644
--- a/src/.clang-tidy
+++ b/src/.clang-tidy
@@ -1,17 +1,28 @@
Checks: '
-*,
bugprone-argument-comment,
+bugprone-use-after-move,
misc-unused-using-decls,
modernize-use-default-member-init,
modernize-use-nullptr,
+performance-for-range-copy,
+performance-move-const-arg,
+performance-unnecessary-copy-initialization,
readability-redundant-declaration,
readability-redundant-string-init,
'
WarningsAsErrors: '
bugprone-argument-comment,
+bugprone-use-after-move,
misc-unused-using-decls,
modernize-use-default-member-init,
modernize-use-nullptr,
+performance-for-range-copy,
+performance-move-const-arg,
+performance-unnecessary-copy-initialization,
readability-redundant-declaration,
readability-redundant-string-init,
'
+CheckOptions:
+ - key: performance-move-const-arg.CheckTriviallyCopyableMove
+ value: false
diff --git a/src/Makefile.am b/src/Makefile.am
index 23bc180095..8f22b85a80 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -23,7 +23,7 @@ noinst_PROGRAMS =
TESTS =
BENCHMARKS =
-BITCOIN_INCLUDES=-I$(builddir) -I$(srcdir)/$(MINISKETCH_INCLUDE_DIR_INT) -I$(srcdir)/secp256k1/include -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS)
+BITCOIN_INCLUDES=-I$(builddir) -I$(srcdir)/$(MINISKETCH_INCLUDE_DIR_INT) -I$(srcdir)/secp256k1/include -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) $(LEVELDB_CPPFLAGS)
LIBBITCOIN_NODE=libbitcoin_node.a
LIBBITCOIN_COMMON=libbitcoin_common.a
@@ -133,13 +133,14 @@ BITCOIN_CORE_H = \
clientversion.h \
coins.h \
common/bloom.h \
+ common/run_command.h \
+ common/url.h \
compat/assumptions.h \
compat/byteswap.h \
compat/compat.h \
compat/cpuid.h \
compat/endian.h \
compressor.h \
- node/connection_types.h \
consensus/consensus.h \
consensus/tx_check.h \
consensus/tx_verify.h \
@@ -149,10 +150,10 @@ BITCOIN_CORE_H = \
dbwrapper.h \
deploymentinfo.h \
deploymentstatus.h \
- node/eviction.h \
external_signer.h \
flatfile.h \
fs.h \
+ headerssync.h \
httprpc.h \
httpserver.h \
i2p.h \
@@ -179,12 +180,12 @@ BITCOIN_CORE_H = \
kernel/mempool_limits.h \
kernel/mempool_options.h \
kernel/mempool_persist.h \
+ kernel/validation_cache_sizes.h \
key.h \
key_io.h \
logging.h \
logging/timer.h \
mapport.h \
- mempool_args.h \
memusage.h \
merkleblock.h \
net.h \
@@ -198,15 +199,21 @@ BITCOIN_CORE_H = \
node/blockstorage.h \
node/caches.h \
node/chainstate.h \
+ node/chainstatemanager_args.h \
node/coin.h \
+ node/connection_types.h \
node/context.h \
+ node/eviction.h \
+ node/interface_ui.h \
+ node/mempool_args.h \
node/mempool_persist_args.h \
node/miner.h \
node/minisketchwrapper.h \
node/psbt.h \
node/transaction.h \
- node/interface_ui.h \
+ node/txreconciliation.h \
node/utxo_snapshot.h \
+ node/validation_cache_args.h \
noui.h \
outputtype.h \
policy/feerate.h \
@@ -262,6 +269,7 @@ BITCOIN_CORE_H = \
undo.h \
util/asmap.h \
util/bip32.h \
+ util/bitdeque.h \
util/bytevectorhash.h \
util/check.h \
util/epochguard.h \
@@ -296,7 +304,6 @@ BITCOIN_CORE_H = \
util/translation.h \
util/types.h \
util/ui_change_type.h \
- util/url.h \
util/vector.h \
validation.h \
validationinterface.h \
@@ -340,12 +347,8 @@ obj/build.h: FORCE
"$(abs_top_srcdir)"
libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h
-
-# server: shared between bitcoind and bitcoin-qt
-# Contains code accessing mempool and chain state that is meant to be separated
-# from wallet and gui code (see node/README.md). Shared code should go in
-# libbitcoin_common or libbitcoin_util libraries, instead.
-libbitcoin_node_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS)
+# node #
+libbitcoin_node_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS)
libbitcoin_node_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_node_a_SOURCES = \
addrdb.cpp \
@@ -358,6 +361,7 @@ libbitcoin_node_a_SOURCES = \
dbwrapper.cpp \
deploymentstatus.cpp \
flatfile.cpp \
+ headerssync.cpp \
httprpc.cpp \
httpserver.cpp \
i2p.cpp \
@@ -372,24 +376,28 @@ libbitcoin_node_a_SOURCES = \
kernel/context.cpp \
kernel/mempool_persist.cpp \
mapport.cpp \
- mempool_args.cpp \
net.cpp \
- netgroup.cpp \
net_processing.cpp \
+ netgroup.cpp \
node/blockstorage.cpp \
node/caches.cpp \
node/chainstate.cpp \
+ node/chainstatemanager_args.cpp \
node/coin.cpp \
node/connection_types.cpp \
node/context.cpp \
node/eviction.cpp \
+ node/interface_ui.cpp \
node/interfaces.cpp \
+ node/mempool_args.cpp \
node/mempool_persist_args.cpp \
node/miner.cpp \
node/minisketchwrapper.cpp \
node/psbt.cpp \
node/transaction.cpp \
- node/interface_ui.cpp \
+ node/txreconciliation.cpp \
+ node/utxo_snapshot.cpp \
+ node/validation_cache_args.cpp \
noui.cpp \
policy/fees.cpp \
policy/fees_args.cpp \
@@ -402,8 +410,8 @@ libbitcoin_node_a_SOURCES = \
rpc/fees.cpp \
rpc/mempool.cpp \
rpc/mining.cpp \
- rpc/node.cpp \
rpc/net.cpp \
+ rpc/node.cpp \
rpc/output_script.cpp \
rpc/rawtransaction.cpp \
rpc/server.cpp \
@@ -431,7 +439,9 @@ endif
if !ENABLE_WALLET
libbitcoin_node_a_SOURCES += dummywallet.cpp
endif
+#
+# zmq #
if ENABLE_ZMQ
libbitcoin_zmq_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(ZMQ_CFLAGS)
libbitcoin_zmq_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
@@ -442,11 +452,10 @@ libbitcoin_zmq_a_SOURCES = \
zmq/zmqrpc.cpp \
zmq/zmqutil.cpp
endif
+#
-
-# wallet: shared between bitcoind and bitcoin-qt, but only linked
-# when wallet enabled
-libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BDB_CPPFLAGS) $(SQLITE_CFLAGS)
+# wallet #
+libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) $(BDB_CPPFLAGS) $(SQLITE_CFLAGS)
libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_wallet_a_SOURCES = \
wallet/coincontrol.cpp \
@@ -484,14 +493,17 @@ endif
if USE_BDB
libbitcoin_wallet_a_SOURCES += wallet/bdb.cpp wallet/salvage.cpp
endif
+#
-libbitcoin_wallet_tool_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+# wallet tool #
+libbitcoin_wallet_tool_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS)
libbitcoin_wallet_tool_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_wallet_tool_a_SOURCES = \
wallet/wallettool.cpp \
$(BITCOIN_CORE_H)
+#
-# crypto primitives library
+# crypto #
crypto_libbitcoin_crypto_base_la_CPPFLAGS = $(AM_CPPFLAGS)
# Specify -static in both CXXFLAGS and LDFLAGS so libtool will only build a
@@ -571,8 +583,9 @@ crypto_libbitcoin_crypto_arm_shani_la_CPPFLAGS = $(AM_CPPFLAGS)
crypto_libbitcoin_crypto_arm_shani_la_CXXFLAGS += $(ARM_SHANI_CXXFLAGS)
crypto_libbitcoin_crypto_arm_shani_la_CPPFLAGS += -DENABLE_ARM_SHANI
crypto_libbitcoin_crypto_arm_shani_la_SOURCES = crypto/sha256_arm_shani.cpp
+#
-# consensus: shared between all executables that validate any consensus rules.
+# consensus #
libbitcoin_consensus_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
libbitcoin_consensus_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_consensus_a_SOURCES = \
@@ -608,9 +621,10 @@ libbitcoin_consensus_a_SOURCES = \
util/strencodings.cpp \
util/strencodings.h \
version.h
+#
-# common: shared between bitcoind, and bitcoin-qt and non-server tools
-libbitcoin_common_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+# common #
+libbitcoin_common_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS)
libbitcoin_common_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_common_a_SOURCES = \
base58.cpp \
@@ -618,6 +632,7 @@ libbitcoin_common_a_SOURCES = \
chainparams.cpp \
coins.cpp \
common/bloom.cpp \
+ common/run_command.cpp \
compressor.cpp \
core_read.cpp \
core_write.cpp \
@@ -648,8 +663,14 @@ libbitcoin_common_a_SOURCES = \
warnings.cpp \
$(BITCOIN_CORE_H)
-# util: shared between all executables.
-libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+if USE_LIBEVENT
+libbitcoin_common_a_CPPFLAGS += $(EVENT_CFLAGS)
+libbitcoin_common_a_SOURCES += common/url.cpp
+endif
+#
+
+# util #
+libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS)
libbitcoin_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_util_a_SOURCES = \
support/lockedpool.cpp \
@@ -692,12 +713,9 @@ libbitcoin_util_a_SOURCES = \
util/time.cpp \
util/tokenpipe.cpp \
$(BITCOIN_CORE_H)
+#
-if USE_LIBEVENT
-libbitcoin_util_a_SOURCES += util/url.cpp
-endif
-
-# cli: shared between bitcoin-cli and bitcoin-qt
+# cli #
libbitcoin_cli_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
libbitcoin_cli_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_cli_a_SOURCES = \
@@ -758,6 +776,7 @@ endif
bitcoin_cli_LDADD = \
$(LIBBITCOIN_CLI) \
$(LIBUNIVALUE) \
+ $(LIBBITCOIN_COMMON) \
$(LIBBITCOIN_UTIL) \
$(LIBBITCOIN_CRYPTO)
@@ -827,7 +846,7 @@ bitcoin_util_LDADD = \
# bitcoin-chainstate binary #
bitcoin_chainstate_SOURCES = bitcoin-chainstate.cpp
-bitcoin_chainstate_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+bitcoin_chainstate_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS)
bitcoin_chainstate_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
# $(LIBTOOL_APP_LDFLAGS) deliberately omitted here so that we can test linking
@@ -894,6 +913,7 @@ libbitcoinkernel_la_SOURCES = \
node/blockstorage.cpp \
node/chainstate.cpp \
node/interface_ui.cpp \
+ node/utxo_snapshot.cpp \
policy/feerate.cpp \
policy/fees.cpp \
policy/packages.cpp \
@@ -917,11 +937,9 @@ libbitcoinkernel_la_SOURCES = \
support/cleanse.cpp \
support/lockedpool.cpp \
sync.cpp \
- threadinterrupt.cpp \
txdb.cpp \
txmempool.cpp \
uint256.cpp \
- util/bytevectorhash.cpp \
util/check.cpp \
util/getuniquepath.cpp \
util/hasher.cpp \
@@ -1062,6 +1080,15 @@ nodist_libbitcoin_ipc_a_SOURCES = $(libbitcoin_ipc_mpgen_output)
CLEANFILES += $(libbitcoin_ipc_mpgen_output)
endif
+%.raw.h: %.raw
+ @$(MKDIR_P) $(@D)
+ @{ \
+ 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" "$@"
+ @echo "Generated $@"
+
include Makefile.minisketch.include
include Makefile.crc32c.include
diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include
index 532f668668..0a3f9df463 100644
--- a/src/Makefile.bench.include
+++ b/src/Makefile.bench.include
@@ -27,6 +27,7 @@ bench_bench_bitcoin_SOURCES = \
bench/crypto_hash.cpp \
bench/data.cpp \
bench/data.h \
+ bench/descriptors.cpp \
bench/duplicate_inputs.cpp \
bench/examples.cpp \
bench/gcs_filter.cpp \
@@ -50,8 +51,9 @@ bench_bench_bitcoin_SOURCES = \
nodist_bench_bench_bitcoin_SOURCES = $(GENERATED_BENCH_FILES)
-bench_bench_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) -I$(builddir)/bench/
+bench_bench_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) -I$(builddir)/bench/
bench_bench_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+bench_bench_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS)
bench_bench_bitcoin_LDADD = \
$(LIBTEST_UTIL) \
$(LIBBITCOIN_NODE) \
@@ -65,7 +67,9 @@ bench_bench_bitcoin_LDADD = \
$(LIBSECP256K1) \
$(LIBUNIVALUE) \
$(EVENT_PTHREADS_LIBS) \
- $(EVENT_LIBS)
+ $(EVENT_LIBS) \
+ $(MINIUPNPC_LIBS) \
+ $(NATPMP_LIBS)
if ENABLE_ZMQ
bench_bench_bitcoin_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS)
@@ -75,11 +79,10 @@ if ENABLE_WALLET
bench_bench_bitcoin_SOURCES += bench/coin_selection.cpp
bench_bench_bitcoin_SOURCES += bench/wallet_balance.cpp
bench_bench_bitcoin_SOURCES += bench/wallet_loading.cpp
+bench_bench_bitcoin_SOURCES += bench/wallet_create_tx.cpp
+bench_bench_bitcoin_LDADD += $(BDB_LIBS) $(SQLITE_LIBS)
endif
-bench_bench_bitcoin_LDADD += $(BDB_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(SQLITE_LIBS)
-bench_bench_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS)
-
CLEAN_BITCOIN_BENCH = bench/*.gcda bench/*.gcno $(GENERATED_BENCH_FILES)
CLEANFILES += $(CLEAN_BITCOIN_BENCH)
@@ -93,12 +96,3 @@ bench: $(BENCH_BINARY) FORCE
bitcoin_bench_clean : FORCE
rm -f $(CLEAN_BITCOIN_BENCH) $(bench_bench_bitcoin_OBJECTS) $(BENCH_BINARY)
-
-%.raw.h: %.raw
- @$(MKDIR_P) $(@D)
- @{ \
- 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" "$@"
- @echo "Generated $@"
diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include
index b4acc47aa1..602a118259 100644
--- a/src/Makefile.qt.include
+++ b/src/Makefile.qt.include
@@ -295,7 +295,7 @@ BITCOIN_QT_RC = qt/res/bitcoin-qt-res.rc
BITCOIN_QT_INCLUDES = -DQT_NO_KEYWORDS -DQT_USE_QSTRINGBUILDER
qt_libbitcoinqt_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDES) \
- $(QT_INCLUDES) $(QT_DBUS_INCLUDES) $(QR_CFLAGS)
+ $(QT_INCLUDES) $(QT_DBUS_INCLUDES) $(QR_CFLAGS) $(BOOST_CPPFLAGS)
qt_libbitcoinqt_a_CXXFLAGS = $(AM_CXXFLAGS) $(QT_PIE_FLAGS)
qt_libbitcoinqt_a_OBJCXXFLAGS = $(AM_OBJCXXFLAGS) $(QT_PIE_FLAGS)
diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include
index fa822f2954..89c659d4b9 100644
--- a/src/Makefile.qttest.include
+++ b/src/Makefile.qttest.include
@@ -27,7 +27,7 @@ TEST_QT_H = \
qt/test/wallettests.h
qt_test_test_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDES) \
- $(QT_INCLUDES) $(QT_TEST_INCLUDES)
+ $(QT_INCLUDES) $(QT_TEST_INCLUDES) $(BOOST_CPPFLAGS)
qt_test_test_bitcoin_qt_SOURCES = \
init/bitcoin-qt.cpp \
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index f52964b033..571a85e5c9 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -77,6 +77,7 @@ BITCOIN_TESTS =\
test/blockencodings_tests.cpp \
test/blockfilter_index_tests.cpp \
test/blockfilter_tests.cpp \
+ test/blockmanager_tests.cpp \
test/bloom_tests.cpp \
test/bswap_tests.cpp \
test/checkqueue_tests.cpp \
@@ -93,6 +94,7 @@ BITCOIN_TESTS =\
test/fs_tests.cpp \
test/getarg_tests.cpp \
test/hash_tests.cpp \
+ test/headers_sync_chainwork_tests.cpp \
test/httpserver_tests.cpp \
test/i2p_tests.cpp \
test/interfaces_tests.cpp \
@@ -119,6 +121,7 @@ BITCOIN_TESTS =\
test/random_tests.cpp \
test/rbf_tests.cpp \
test/rest_tests.cpp \
+ test/result_tests.cpp \
test/reverselock_tests.cpp \
test/rpc_tests.cpp \
test/sanity_tests.cpp \
@@ -145,6 +148,7 @@ BITCOIN_TESTS =\
test/transaction_tests.cpp \
test/txindex_tests.cpp \
test/txpackage_tests.cpp \
+ test/txreconciliation_tests.cpp \
test/txrequest_tests.cpp \
test/txvalidation_tests.cpp \
test/txvalidationcache_tests.cpp \
@@ -161,6 +165,7 @@ BITCOIN_TESTS =\
if ENABLE_WALLET
BITCOIN_TESTS += \
+ wallet/test/feebumper_tests.cpp \
wallet/test/psbt_wallet_tests.cpp \
wallet/test/spend_tests.cpp \
wallet/test/wallet_tests.cpp \
@@ -171,7 +176,9 @@ BITCOIN_TESTS += \
wallet/test/availablecoins_tests.cpp \
wallet/test/init_tests.cpp \
wallet/test/ismine_tests.cpp \
- wallet/test/scriptpubkeyman_tests.cpp
+ wallet/test/rpc_util_tests.cpp \
+ wallet/test/scriptpubkeyman_tests.cpp \
+ wallet/test/walletload_tests.cpp
FUZZ_SUITE_LD_COMMON +=\
$(SQLITE_LIBS) \
@@ -182,7 +189,8 @@ BITCOIN_TESTS += wallet/test/db_tests.cpp
endif
FUZZ_WALLET_SRC = \
- wallet/test/fuzz/coinselection.cpp
+ wallet/test/fuzz/coinselection.cpp \
+ wallet/test/fuzz/parse_iso8601.cpp
if USE_SQLITE
FUZZ_WALLET_SRC += \
@@ -199,7 +207,7 @@ BITCOIN_TEST_SUITE += \
endif # ENABLE_WALLET
test_test_bitcoin_SOURCES = $(BITCOIN_TEST_SUITE) $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES)
-test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(TESTDEFS) $(EVENT_CFLAGS)
+test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(TESTDEFS) $(BOOST_CPPFLAGS) $(EVENT_CFLAGS)
test_test_bitcoin_LDADD = $(LIBTEST_UTIL)
if ENABLE_WALLET
test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET)
@@ -219,7 +227,7 @@ FUZZ_SUITE_LD_COMMON += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS)
endif
if ENABLE_FUZZ_BINARY
-test_fuzz_fuzz_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_fuzz_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS)
test_fuzz_fuzz_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_fuzz_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_fuzz_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) $(RUNTIME_LDFLAGS)
@@ -233,6 +241,7 @@ test_fuzz_fuzz_SOURCES = \
test/fuzz/banman.cpp \
test/fuzz/base_encode_decode.cpp \
test/fuzz/bech32.cpp \
+ test/fuzz/bitdeque.cpp \
test/fuzz/block.cpp \
test/fuzz/block_header.cpp \
test/fuzz/blockfilter.cpp \
@@ -283,7 +292,6 @@ test_fuzz_fuzz_SOURCES = \
test/fuzz/node_eviction.cpp \
test/fuzz/p2p_transport_serialization.cpp \
test/fuzz/parse_hd_keypath.cpp \
- test/fuzz/parse_iso8601.cpp \
test/fuzz/parse_numbers.cpp \
test/fuzz/parse_script.cpp \
test/fuzz/parse_univalue.cpp \
@@ -338,7 +346,7 @@ nodist_test_test_bitcoin_SOURCES = $(GENERATED_TEST_FILES)
$(BITCOIN_TESTS): $(GENERATED_TEST_FILES)
-CLEAN_BITCOIN_TEST = test/*.gcda test/*.gcno test/fuzz/*.gcda test/fuzz/*.gcno test/util/*.gcda test/util/*.gcno $(GENERATED_TEST_FILES) $(BITCOIN_TESTS:=.log)
+CLEAN_BITCOIN_TEST = test/*.gcda test/*.gcno test/fuzz/*.gcda test/fuzz/*.gcno test/util/*.gcda test/util/*.gcno $(GENERATED_TEST_FILES) $(addsuffix .log,$(basename $(BITCOIN_TESTS)))
CLEANFILES += $(CLEAN_BITCOIN_TEST)
@@ -368,8 +376,8 @@ endif
if TARGET_WINDOWS
else
if ENABLE_BENCH
- @echo "Running bench/bench_bitcoin (one iteration sanity check)..."
- $(BENCH_BINARY) --sanity-check > /dev/null
+ @echo "Running bench/bench_bitcoin (one iteration sanity check, only high priority)..."
+ $(BENCH_BINARY) -sanity-check -priority-level=high > /dev/null
endif
endif
$(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C secp256k1 check
@@ -414,12 +422,3 @@ endif
echo "};};"; \
} > "$@.new" && mv -f "$@.new" "$@"
@echo "Generated $@"
-
-%.raw.h: %.raw
- @$(MKDIR_P) $(@D)
- @{ \
- 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" "$@"
- @echo "Generated $@"
diff --git a/src/Makefile.test_fuzz.include b/src/Makefile.test_fuzz.include
index b43816636f..b35d713d57 100644
--- a/src/Makefile.test_fuzz.include
+++ b/src/Makefile.test_fuzz.include
@@ -10,12 +10,13 @@ EXTRA_LIBRARIES += \
TEST_FUZZ_H = \
test/fuzz/fuzz.h \
test/fuzz/FuzzedDataProvider.h \
- test/fuzz/mempool_utils.h \
- test/fuzz/util.h
+ test/fuzz/util.h \
+ test/fuzz/util/mempool.h
-libtest_fuzz_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS)
+libtest_fuzz_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS)
libtest_fuzz_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libtest_fuzz_a_SOURCES = \
test/fuzz/fuzz.cpp \
test/fuzz/util.cpp \
+ test/fuzz/util/mempool.cpp \
$(TEST_FUZZ_H)
diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include
index 9306bb6fcc..d142572b27 100644
--- a/src/Makefile.test_util.include
+++ b/src/Makefile.test_util.include
@@ -5,22 +5,23 @@
LIBTEST_UTIL=libtest_util.a
EXTRA_LIBRARIES += \
- $(LIBTEST_UTIL)
+ $(LIBTEST_UTIL)
TEST_UTIL_H = \
- test/util/blockfilter.h \
- test/util/chainstate.h \
- test/util/logging.h \
- test/util/mining.h \
- test/util/net.h \
- test/util/script.h \
- test/util/setup_common.h \
- test/util/str.h \
- test/util/transaction_utils.h \
- test/util/validation.h \
- test/util/wallet.h
+ test/util/blockfilter.h \
+ test/util/chainstate.h \
+ test/util/logging.h \
+ test/util/mining.h \
+ test/util/net.h \
+ test/util/script.h \
+ test/util/setup_common.h \
+ test/util/str.h \
+ test/util/transaction_utils.h \
+ test/util/txmempool.h \
+ test/util/validation.h \
+ test/util/wallet.h
-libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS)
+libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS)
libtest_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libtest_util_a_SOURCES = \
test/util/blockfilter.cpp \
@@ -31,6 +32,7 @@ libtest_util_a_SOURCES = \
test/util/setup_common.cpp \
test/util/str.cpp \
test/util/transaction_utils.cpp \
+ test/util/txmempool.cpp \
test/util/validation.cpp \
test/util/wallet.cpp \
$(TEST_UTIL_H)
diff --git a/src/addrdb.cpp b/src/addrdb.cpp
index 31f8eadf98..7106d819b0 100644
--- a/src/addrdb.cpp
+++ b/src/addrdb.cpp
@@ -187,11 +187,11 @@ std::optional<bilingual_str> LoadAddrman(const NetGroupManager& netgroupman, con
auto check_addrman = std::clamp<int32_t>(args.GetIntArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000);
addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman);
- int64_t nStart = GetTimeMillis();
+ const auto start{SteadyClock::now()};
const auto path_addr{args.GetDataDirNet() / "peers.dat"};
try {
DeserializeFileDB(path_addr, *addrman, CLIENT_VERSION);
- LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->size(), GetTimeMillis() - nStart);
+ LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
} catch (const DbNotFoundError&) {
// Addrman can be in an inconsistent state after failure, reset it
addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman);
diff --git a/src/addrman.cpp b/src/addrman.cpp
index 857ad73b2f..f16ff2230b 100644
--- a/src/addrman.cpp
+++ b/src/addrman.cpp
@@ -14,10 +14,10 @@
#include <random.h>
#include <serialize.h>
#include <streams.h>
-#include <timedata.h>
#include <tinyformat.h>
#include <uint256.h>
#include <util/check.h>
+#include <util/time.h>
#include <cmath>
#include <optional>
@@ -560,7 +560,7 @@ bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, std::c
if (pinfo) {
// periodically update nTime
- const bool currently_online{AdjustedTime() - addr.nTime < 24h};
+ const bool currently_online{NodeClock::now() - addr.nTime < 24h};
const auto update_interval{currently_online ? 1h : 24h};
if (pinfo->nTime < addr.nTime - update_interval - time_penalty) {
pinfo->nTime = std::max(NodeSeconds{0s}, addr.nTime - time_penalty);
@@ -788,7 +788,7 @@ std::vector<CAddress> AddrManImpl::GetAddr_(size_t max_addresses, size_t max_pct
}
// gather a list of random nodes, skipping those of low quality
- const auto now{AdjustedTime()};
+ const auto now{Now<NodeSeconds>()};
std::vector<CAddress> addresses;
for (unsigned int n = 0; n < vRandom.size(); n++) {
if (addresses.size() >= nNodes)
@@ -874,7 +874,7 @@ void AddrManImpl::ResolveCollisions_()
int id_old = vvTried[tried_bucket][tried_bucket_pos];
AddrInfo& info_old = mapInfo[id_old];
- const auto current_time{AdjustedTime()};
+ const auto current_time{Now<NodeSeconds>()};
// Has successfully connected in last X hours
if (current_time - info_old.m_last_success < ADDRMAN_REPLACEMENT) {
@@ -898,7 +898,7 @@ void AddrManImpl::ResolveCollisions_()
erase_collision = true;
}
} else { // Collision is not actually a collision anymore
- Good_(info_new, false, AdjustedTime());
+ Good_(info_new, false, Now<NodeSeconds>());
erase_collision = true;
}
}
diff --git a/src/addrman.h b/src/addrman.h
index b70c6a48ad..5099c8c7a3 100644
--- a/src/addrman.h
+++ b/src/addrman.h
@@ -10,7 +10,6 @@
#include <netgroup.h>
#include <protocol.h>
#include <streams.h>
-#include <timedata.h>
#include <util/time.h>
#include <cstdint>
@@ -121,10 +120,10 @@ public:
* @param[in] time The time that we were last connected to this peer.
* @return true if the address is successfully moved from the new table to the tried table.
*/
- bool Good(const CService& addr, NodeSeconds time = AdjustedTime());
+ bool Good(const CService& addr, NodeSeconds time = Now<NodeSeconds>());
//! Mark an entry as connection attempted to.
- void Attempt(const CService& addr, bool fCountFailure, NodeSeconds time = AdjustedTime());
+ void Attempt(const CService& addr, bool fCountFailure, NodeSeconds time = Now<NodeSeconds>());
//! See if any to-be-evicted tried table entries have been tested and if so resolve the collisions.
void ResolveCollisions();
@@ -169,7 +168,7 @@ public:
* @param[in] addr The address of the peer we were connected to
* @param[in] time The time that we were last connected to this peer
*/
- void Connected(const CService& addr, NodeSeconds time = AdjustedTime());
+ void Connected(const CService& addr, NodeSeconds time = Now<NodeSeconds>());
//! Update an entry's service bits.
void SetServices(const CService& addr, ServiceFlags nServices);
diff --git a/src/addrman_impl.h b/src/addrman_impl.h
index a73a026940..376e79f49f 100644
--- a/src/addrman_impl.h
+++ b/src/addrman_impl.h
@@ -93,10 +93,10 @@ public:
int GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const;
//! Determine whether the statistics about this entry are bad enough so that it can just be deleted
- bool IsTerrible(NodeSeconds now = AdjustedTime()) const;
+ bool IsTerrible(NodeSeconds now = Now<NodeSeconds>()) const;
//! Calculate the relative chance this entry should be given when selecting nodes to connect to
- double GetChance(NodeSeconds now = AdjustedTime()) const;
+ double GetChance(NodeSeconds now = Now<NodeSeconds>()) const;
};
class AddrManImpl
diff --git a/src/banman.cpp b/src/banman.cpp
index 508383d9f2..3cd646c148 100644
--- a/src/banman.cpp
+++ b/src/banman.cpp
@@ -31,12 +31,12 @@ void BanMan::LoadBanlist()
if (m_client_interface) m_client_interface->InitMessage(_("Loading banlist…").translated);
- int64_t n_start = GetTimeMillis();
+ const auto start{SteadyClock::now()};
if (m_ban_db.Read(m_banned)) {
SweepBanned(); // sweep out unused entries
LogPrint(BCLog::NET, "Loaded %d banned node addresses/subnets %dms\n", m_banned.size(),
- GetTimeMillis() - n_start);
+ Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
} else {
LogPrintf("Recreating the banlist database\n");
m_banned = {};
@@ -58,13 +58,13 @@ void BanMan::DumpBanlist()
SetBannedSetDirty(false);
}
- int64_t n_start = GetTimeMillis();
+ const auto start{SteadyClock::now()};
if (!m_ban_db.Write(banmap)) {
SetBannedSetDirty(true);
}
LogPrint(BCLog::NET, "Flushed %d banned node addresses/subnets to disk %dms\n", banmap.size(),
- GetTimeMillis() - n_start);
+ Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
}
void BanMan::ClearBanned()
diff --git a/src/bech32.cpp b/src/bech32.cpp
index dce9b2e4cc..8e0025b8f4 100644
--- a/src/bech32.cpp
+++ b/src/bech32.cpp
@@ -241,7 +241,7 @@ constexpr std::array<uint32_t, 25> GenerateSyndromeConstants() {
std::array<uint32_t, 25> SYNDROME_CONSTS{};
for (int k = 1; k < 6; ++k) {
for (int shift = 0; shift < 5; ++shift) {
- int16_t b = GF1024_LOG.at(1 << shift);
+ int16_t b = GF1024_LOG.at(size_t{1} << shift);
int16_t c0 = GF1024_EXP.at((997*k + b) % 1023);
int16_t c1 = GF1024_EXP.at((998*k + b) % 1023);
int16_t c2 = GF1024_EXP.at((999*k + b) % 1023);
diff --git a/src/bench/addrman.cpp b/src/bench/addrman.cpp
index 2600b03022..019b133345 100644
--- a/src/bench/addrman.cpp
+++ b/src/bench/addrman.cpp
@@ -43,7 +43,7 @@ static void CreateAddresses()
CAddress ret(CService(addr, port), NODE_NETWORK);
- ret.nTime = AdjustedTime();
+ ret.nTime = Now<NodeSeconds>();
return ret;
};
@@ -133,7 +133,7 @@ static void AddrManAddThenGood(benchmark::Bench& bench)
});
}
-BENCHMARK(AddrManAdd);
-BENCHMARK(AddrManSelect);
-BENCHMARK(AddrManGetAddr);
-BENCHMARK(AddrManAddThenGood);
+BENCHMARK(AddrManAdd, benchmark::PriorityLevel::HIGH);
+BENCHMARK(AddrManSelect, benchmark::PriorityLevel::HIGH);
+BENCHMARK(AddrManGetAddr, benchmark::PriorityLevel::HIGH);
+BENCHMARK(AddrManAddThenGood, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/base58.cpp b/src/bench/base58.cpp
index 6f6b4e3bfa..3d08b7201b 100644
--- a/src/bench/base58.cpp
+++ b/src/bench/base58.cpp
@@ -50,6 +50,6 @@ static void Base58Decode(benchmark::Bench& bench)
}
-BENCHMARK(Base58Encode);
-BENCHMARK(Base58CheckEncode);
-BENCHMARK(Base58Decode);
+BENCHMARK(Base58Encode, benchmark::PriorityLevel::HIGH);
+BENCHMARK(Base58CheckEncode, benchmark::PriorityLevel::HIGH);
+BENCHMARK(Base58Decode, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/bech32.cpp b/src/bench/bech32.cpp
index 0f89a8e2b5..1a166e7081 100644
--- a/src/bench/bech32.cpp
+++ b/src/bench/bech32.cpp
@@ -32,5 +32,5 @@ static void Bech32Decode(benchmark::Bench& bench)
}
-BENCHMARK(Bech32Encode);
-BENCHMARK(Bech32Decode);
+BENCHMARK(Bech32Encode, benchmark::PriorityLevel::HIGH);
+BENCHMARK(Bech32Decode, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/bench.cpp b/src/bench/bench.cpp
index 26975bb59d..1a3a006286 100644
--- a/src/bench/bench.cpp
+++ b/src/bench/bench.cpp
@@ -6,6 +6,7 @@
#include <fs.h>
#include <test/util/setup_common.h>
+#include <util/string.h>
#include <chrono>
#include <fstream>
@@ -41,18 +42,42 @@ void GenerateTemplateResults(const std::vector<ankerl::nanobench::Result>& bench
} // namespace
-benchmark::BenchRunner::BenchmarkMap& benchmark::BenchRunner::benchmarks()
+namespace benchmark {
+
+// map a label to one or multiple priority levels
+std::map<std::string, uint8_t> map_label_priority = {
+ {"high", PriorityLevel::HIGH},
+ {"low", PriorityLevel::LOW},
+ {"all", 0xff}
+};
+
+std::string ListPriorities()
{
- static std::map<std::string, BenchFunction> benchmarks_map;
+ using item_t = std::pair<std::string, uint8_t>;
+ auto sort_by_priority = [](item_t a, item_t b){ return a.second < b.second; };
+ std::set<item_t, decltype(sort_by_priority)> sorted_priorities(map_label_priority.begin(), map_label_priority.end(), sort_by_priority);
+ return Join(sorted_priorities, ',', [](const auto& entry){ return entry.first; });
+}
+
+uint8_t StringToPriority(const std::string& str)
+{
+ auto it = map_label_priority.find(str);
+ if (it == map_label_priority.end()) throw std::runtime_error(strprintf("Unknown priority level %s", str));
+ return it->second;
+}
+
+BenchRunner::BenchmarkMap& BenchRunner::benchmarks()
+{
+ static BenchmarkMap benchmarks_map;
return benchmarks_map;
}
-benchmark::BenchRunner::BenchRunner(std::string name, benchmark::BenchFunction func)
+BenchRunner::BenchRunner(std::string name, BenchFunction func, PriorityLevel level)
{
- benchmarks().insert(std::make_pair(name, func));
+ benchmarks().insert(std::make_pair(name, std::make_pair(func, level)));
}
-void benchmark::BenchRunner::RunAll(const Args& args)
+void BenchRunner::RunAll(const Args& args)
{
std::regex reFilter(args.regex_filter);
std::smatch baseMatch;
@@ -62,13 +87,19 @@ void benchmark::BenchRunner::RunAll(const Args& args)
}
std::vector<ankerl::nanobench::Result> benchmarkResults;
- for (const auto& p : benchmarks()) {
- if (!std::regex_match(p.first, baseMatch, reFilter)) {
+ for (const auto& [name, bench_func] : benchmarks()) {
+ const auto& [func, priority_level] = bench_func;
+
+ if (!(priority_level & args.priority)) {
+ continue;
+ }
+
+ if (!std::regex_match(name, baseMatch, reFilter)) {
continue;
}
if (args.is_list_only) {
- std::cout << p.first << std::endl;
+ std::cout << name << std::endl;
continue;
}
@@ -76,7 +107,7 @@ void benchmark::BenchRunner::RunAll(const Args& args)
if (args.sanity_check) {
bench.epochs(1).epochIterations(1);
}
- bench.name(p.first);
+ bench.name(name);
if (args.min_time > 0ms) {
// convert to nanos before dividing to reduce rounding errors
std::chrono::nanoseconds min_time_ns = args.min_time;
@@ -84,11 +115,11 @@ void benchmark::BenchRunner::RunAll(const Args& args)
}
if (args.asymptote.empty()) {
- p.second(bench);
+ func(bench);
} else {
for (auto n : args.asymptote) {
bench.complexityN(n);
- p.second(bench);
+ func(bench);
}
std::cout << bench.complexityBigO() << std::endl;
}
@@ -103,3 +134,5 @@ void benchmark::BenchRunner::RunAll(const Args& args)
"{{/result}}");
GenerateTemplateResults(benchmarkResults, args.output_json, ankerl::nanobench::templates::json());
}
+
+} // namespace benchmark
diff --git a/src/bench/bench.h b/src/bench/bench.h
index 17535e4e81..63e1bf67e2 100644
--- a/src/bench/bench.h
+++ b/src/bench/bench.h
@@ -41,6 +41,16 @@ using ankerl::nanobench::Bench;
typedef std::function<void(Bench&)> BenchFunction;
+enum PriorityLevel : uint8_t
+{
+ LOW = 1 << 0,
+ HIGH = 1 << 2,
+};
+
+// List priority labels, comma-separated and sorted by increasing priority
+std::string ListPriorities();
+uint8_t StringToPriority(const std::string& str);
+
struct Args {
bool is_list_only;
bool sanity_check;
@@ -49,22 +59,24 @@ struct Args {
fs::path output_csv;
fs::path output_json;
std::string regex_filter;
+ uint8_t priority;
};
class BenchRunner
{
- typedef std::map<std::string, BenchFunction> BenchmarkMap;
+ // maps from "name" -> (function, priority_level)
+ typedef std::map<std::string, std::pair<BenchFunction, PriorityLevel>> BenchmarkMap;
static BenchmarkMap& benchmarks();
public:
- BenchRunner(std::string name, BenchFunction func);
+ BenchRunner(std::string name, BenchFunction func, PriorityLevel level);
static void RunAll(const Args& args);
};
} // namespace benchmark
-// BENCHMARK(foo) expands to: benchmark::BenchRunner bench_11foo("foo", foo);
-#define BENCHMARK(n) \
- benchmark::BenchRunner PASTE2(bench_, PASTE2(__LINE__, n))(STRINGIZE(n), n);
+// BENCHMARK(foo) expands to: benchmark::BenchRunner bench_11foo("foo", foo, priority_level);
+#define BENCHMARK(n, priority_level) \
+ benchmark::BenchRunner PASTE2(bench_, PASTE2(__LINE__, n))(STRINGIZE(n), n, priority_level);
#endif // BITCOIN_BENCH_BENCH_H
diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp
index 1bb4d34db9..1ac8db19fd 100644
--- a/src/bench/bench_bitcoin.cpp
+++ b/src/bench/bench_bitcoin.cpp
@@ -18,6 +18,8 @@
static const char* DEFAULT_BENCH_FILTER = ".*";
static constexpr int64_t DEFAULT_MIN_TIME_MS{10};
+/** Priority level default value, run "all" priority levels */
+static const std::string DEFAULT_PRIORITY{"all"};
static void SetupBenchArgs(ArgsManager& argsman)
{
@@ -30,6 +32,8 @@ static void SetupBenchArgs(ArgsManager& argsman)
argsman.AddArg("-output-csv=<output.csv>", "Generate CSV file with the most important benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-output-json=<output.json>", "Generate JSON file with all benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-sanity-check", "Run benchmarks for only one iteration", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+ argsman.AddArg("-priority-level=<l1,l2,l3>", strprintf("Run benchmarks of one or multiple priority level(s) (%s), default: '%s'",
+ benchmark::ListPriorities(), DEFAULT_PRIORITY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
}
// parses a comma separated list like "10,20,30,50"
@@ -45,6 +49,14 @@ static std::vector<double> parseAsymptote(const std::string& str) {
return numbers;
}
+static uint8_t parsePriorityLevel(const std::string& str) {
+ uint8_t levels{0};
+ for (const auto& level: SplitString(str, ',')) {
+ levels |= benchmark::StringToPriority(level);
+ }
+ return levels;
+}
+
int main(int argc, char** argv)
{
ArgsManager argsman;
@@ -106,16 +118,22 @@ int main(int argc, char** argv)
return EXIT_SUCCESS;
}
- benchmark::Args args;
- args.asymptote = parseAsymptote(argsman.GetArg("-asymptote", ""));
- args.is_list_only = argsman.GetBoolArg("-list", false);
- args.min_time = std::chrono::milliseconds(argsman.GetIntArg("-min-time", DEFAULT_MIN_TIME_MS));
- args.output_csv = argsman.GetPathArg("-output-csv");
- args.output_json = argsman.GetPathArg("-output-json");
- args.regex_filter = argsman.GetArg("-filter", DEFAULT_BENCH_FILTER);
- args.sanity_check = argsman.GetBoolArg("-sanity-check", false);
+ try {
+ benchmark::Args args;
+ args.asymptote = parseAsymptote(argsman.GetArg("-asymptote", ""));
+ args.is_list_only = argsman.GetBoolArg("-list", false);
+ args.min_time = std::chrono::milliseconds(argsman.GetIntArg("-min-time", DEFAULT_MIN_TIME_MS));
+ args.output_csv = argsman.GetPathArg("-output-csv");
+ args.output_json = argsman.GetPathArg("-output-json");
+ args.regex_filter = argsman.GetArg("-filter", DEFAULT_BENCH_FILTER);
+ args.sanity_check = argsman.GetBoolArg("-sanity-check", false);
+ args.priority = parsePriorityLevel(argsman.GetArg("-priority-level", DEFAULT_PRIORITY));
- benchmark::BenchRunner::RunAll(args);
+ benchmark::BenchRunner::RunAll(args);
- return EXIT_SUCCESS;
+ return EXIT_SUCCESS;
+ } catch (const std::exception& e) {
+ tfm::format(std::cerr, "Error: %s\n", e.what());
+ return EXIT_FAILURE;
+ }
}
diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp
index 4ed5397330..09be011fda 100644
--- a/src/bench/block_assemble.cpp
+++ b/src/bench/block_assemble.cpp
@@ -47,4 +47,4 @@ static void AssembleBlock(benchmark::Bench& bench)
});
}
-BENCHMARK(AssembleBlock);
+BENCHMARK(AssembleBlock, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/ccoins_caching.cpp b/src/bench/ccoins_caching.cpp
index 22dfb7aa5b..5d55ed9332 100644
--- a/src/bench/ccoins_caching.cpp
+++ b/src/bench/ccoins_caching.cpp
@@ -51,4 +51,4 @@ static void CCoinsCaching(benchmark::Bench& bench)
ECC_Stop();
}
-BENCHMARK(CCoinsCaching);
+BENCHMARK(CCoinsCaching, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/chacha20.cpp b/src/bench/chacha20.cpp
index a6f4eec4ca..9584dd58bb 100644
--- a/src/bench/chacha20.cpp
+++ b/src/bench/chacha20.cpp
@@ -39,6 +39,6 @@ static void CHACHA20_1MB(benchmark::Bench& bench)
CHACHA20(bench, BUFFER_SIZE_LARGE);
}
-BENCHMARK(CHACHA20_64BYTES);
-BENCHMARK(CHACHA20_256BYTES);
-BENCHMARK(CHACHA20_1MB);
+BENCHMARK(CHACHA20_64BYTES, benchmark::PriorityLevel::HIGH);
+BENCHMARK(CHACHA20_256BYTES, benchmark::PriorityLevel::HIGH);
+BENCHMARK(CHACHA20_1MB, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/chacha_poly_aead.cpp b/src/bench/chacha_poly_aead.cpp
index e994279a4d..15b3a4f310 100644
--- a/src/bench/chacha_poly_aead.cpp
+++ b/src/bench/chacha_poly_aead.cpp
@@ -115,12 +115,12 @@ static void HASH_1MB(benchmark::Bench& bench)
HASH(bench, BUFFER_SIZE_LARGE);
}
-BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT);
-BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT);
-BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT);
-BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT);
-BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT);
-BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT);
-BENCHMARK(HASH_64BYTES);
-BENCHMARK(HASH_256BYTES);
-BENCHMARK(HASH_1MB);
+BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH);
+BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH);
+BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH);
+BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH);
+BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH);
+BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH);
+BENCHMARK(HASH_64BYTES, benchmark::PriorityLevel::HIGH);
+BENCHMARK(HASH_256BYTES, benchmark::PriorityLevel::HIGH);
+BENCHMARK(HASH_1MB, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp
index 53aa470042..747279c161 100644
--- a/src/bench/checkblock.cpp
+++ b/src/bench/checkblock.cpp
@@ -50,5 +50,5 @@ static void DeserializeAndCheckBlockTest(benchmark::Bench& bench)
});
}
-BENCHMARK(DeserializeBlockTest);
-BENCHMARK(DeserializeAndCheckBlockTest);
+BENCHMARK(DeserializeBlockTest, benchmark::PriorityLevel::HIGH);
+BENCHMARK(DeserializeAndCheckBlockTest, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/checkqueue.cpp b/src/bench/checkqueue.cpp
index 602081fb9b..8517c9fee2 100644
--- a/src/bench/checkqueue.cpp
+++ b/src/bench/checkqueue.cpp
@@ -70,4 +70,4 @@ static void CCheckQueueSpeedPrevectorJob(benchmark::Bench& bench)
queue.StopWorkerThreads();
ECC_Stop();
}
-BENCHMARK(CCheckQueueSpeedPrevectorJob);
+BENCHMARK(CCheckQueueSpeedPrevectorJob, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp
index a80ec3703c..53d89039a7 100644
--- a/src/bench/coin_selection.cpp
+++ b/src/bench/coin_selection.cpp
@@ -59,7 +59,7 @@ static void CoinSelection(benchmark::Bench& bench)
wallet::CoinsResult available_coins;
for (const auto& wtx : wtxs) {
const auto txout = wtx->tx->vout.at(0);
- available_coins.bech32.emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
+ available_coins.coins[OutputType::BECH32].emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
}
const CoinEligibilityFilter filter_standard(1, 6, 0);
@@ -121,5 +121,5 @@ static void BnBExhaustion(benchmark::Bench& bench)
});
}
-BENCHMARK(CoinSelection);
-BENCHMARK(BnBExhaustion);
+BENCHMARK(CoinSelection, benchmark::PriorityLevel::HIGH);
+BENCHMARK(BnBExhaustion, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/crypto_hash.cpp b/src/bench/crypto_hash.cpp
index d17ec503e7..162b02f344 100644
--- a/src/bench/crypto_hash.cpp
+++ b/src/bench/crypto_hash.cpp
@@ -150,19 +150,19 @@ static void MuHashPrecompute(benchmark::Bench& bench)
});
}
-BENCHMARK(RIPEMD160);
-BENCHMARK(SHA1);
-BENCHMARK(SHA256);
-BENCHMARK(SHA512);
-BENCHMARK(SHA3_256_1M);
-
-BENCHMARK(SHA256_32b);
-BENCHMARK(SipHash_32b);
-BENCHMARK(SHA256D64_1024);
-BENCHMARK(FastRandom_32bit);
-BENCHMARK(FastRandom_1bit);
-
-BENCHMARK(MuHash);
-BENCHMARK(MuHashMul);
-BENCHMARK(MuHashDiv);
-BENCHMARK(MuHashPrecompute);
+BENCHMARK(RIPEMD160, benchmark::PriorityLevel::HIGH);
+BENCHMARK(SHA1, benchmark::PriorityLevel::HIGH);
+BENCHMARK(SHA256, benchmark::PriorityLevel::HIGH);
+BENCHMARK(SHA512, benchmark::PriorityLevel::HIGH);
+BENCHMARK(SHA3_256_1M, benchmark::PriorityLevel::HIGH);
+
+BENCHMARK(SHA256_32b, benchmark::PriorityLevel::HIGH);
+BENCHMARK(SipHash_32b, benchmark::PriorityLevel::HIGH);
+BENCHMARK(SHA256D64_1024, benchmark::PriorityLevel::HIGH);
+BENCHMARK(FastRandom_32bit, benchmark::PriorityLevel::HIGH);
+BENCHMARK(FastRandom_1bit, benchmark::PriorityLevel::HIGH);
+
+BENCHMARK(MuHash, benchmark::PriorityLevel::HIGH);
+BENCHMARK(MuHashMul, benchmark::PriorityLevel::HIGH);
+BENCHMARK(MuHashDiv, benchmark::PriorityLevel::HIGH);
+BENCHMARK(MuHashPrecompute, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/descriptors.cpp b/src/bench/descriptors.cpp
new file mode 100644
index 0000000000..972a6ff953
--- /dev/null
+++ b/src/bench/descriptors.cpp
@@ -0,0 +1,36 @@
+// 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/bench.h>
+#include <key.h>
+#include <pubkey.h>
+#include <script/descriptor.h>
+#include <script/standard.h>
+
+#include <string>
+#include <utility>
+
+static void ExpandDescriptor(benchmark::Bench& bench)
+{
+ const ECCVerifyHandle verify_handle;
+ ECC_Start();
+
+ const auto desc_str = "sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))";
+ const std::pair<int64_t, int64_t> range = {0, 1000};
+ FlatSigningProvider provider;
+ std::string error;
+ auto desc = Parse(desc_str, provider, error);
+
+ bench.run([&] {
+ for (int i = range.first; i <= range.second; ++i) {
+ std::vector<CScript> scripts;
+ bool success = desc->Expand(i, provider, scripts, provider);
+ assert(success);
+ }
+ });
+
+ ECC_Stop();
+}
+
+BENCHMARK(ExpandDescriptor, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/duplicate_inputs.cpp b/src/bench/duplicate_inputs.cpp
index 02a2e689b1..559854ff48 100644
--- a/src/bench/duplicate_inputs.cpp
+++ b/src/bench/duplicate_inputs.cpp
@@ -62,4 +62,4 @@ static void DuplicateInputs(benchmark::Bench& bench)
});
}
-BENCHMARK(DuplicateInputs);
+BENCHMARK(DuplicateInputs, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/examples.cpp b/src/bench/examples.cpp
index 72a9922e94..abef69cc42 100644
--- a/src/bench/examples.cpp
+++ b/src/bench/examples.cpp
@@ -18,4 +18,4 @@ static void Trig(benchmark::Bench& bench)
});
}
-BENCHMARK(Trig);
+BENCHMARK(Trig, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/gcs_filter.cpp b/src/bench/gcs_filter.cpp
index 80babb213b..b795ebff39 100644
--- a/src/bench/gcs_filter.cpp
+++ b/src/bench/gcs_filter.cpp
@@ -81,8 +81,8 @@ static void GCSFilterMatch(benchmark::Bench& bench)
filter.Match(GCSFilter::Element());
});
}
-BENCHMARK(GCSBlockFilterGetHash);
-BENCHMARK(GCSFilterConstruct);
-BENCHMARK(GCSFilterDecode);
-BENCHMARK(GCSFilterDecodeSkipCheck);
-BENCHMARK(GCSFilterMatch);
+BENCHMARK(GCSBlockFilterGetHash, benchmark::PriorityLevel::HIGH);
+BENCHMARK(GCSFilterConstruct, benchmark::PriorityLevel::HIGH);
+BENCHMARK(GCSFilterDecode, benchmark::PriorityLevel::HIGH);
+BENCHMARK(GCSFilterDecodeSkipCheck, benchmark::PriorityLevel::HIGH);
+BENCHMARK(GCSFilterMatch, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/hashpadding.cpp b/src/bench/hashpadding.cpp
index 753c8c2881..ac5aeebe51 100644
--- a/src/bench/hashpadding.cpp
+++ b/src/bench/hashpadding.cpp
@@ -26,7 +26,7 @@ static void PrePadded(benchmark::Bench& bench)
});
}
-BENCHMARK(PrePadded);
+BENCHMARK(PrePadded, benchmark::PriorityLevel::HIGH);
static void RegularPadded(benchmark::Bench& bench)
{
@@ -44,4 +44,4 @@ static void RegularPadded(benchmark::Bench& bench)
});
}
-BENCHMARK(RegularPadded);
+BENCHMARK(RegularPadded, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/lockedpool.cpp b/src/bench/lockedpool.cpp
index b6d8824aba..ac8262654c 100644
--- a/src/bench/lockedpool.cpp
+++ b/src/bench/lockedpool.cpp
@@ -39,4 +39,4 @@ static void BenchLockedPool(benchmark::Bench& bench)
addr.clear();
}
-BENCHMARK(BenchLockedPool);
+BENCHMARK(BenchLockedPool, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/logging.cpp b/src/bench/logging.cpp
index d28777df9e..49a9e59893 100644
--- a/src/bench/logging.cpp
+++ b/src/bench/logging.cpp
@@ -41,8 +41,8 @@ static void LoggingNoFile(benchmark::Bench& bench)
});
}
-BENCHMARK(LoggingYoThreadNames);
-BENCHMARK(LoggingNoThreadNames);
-BENCHMARK(LoggingYoCategory);
-BENCHMARK(LoggingNoCategory);
-BENCHMARK(LoggingNoFile);
+BENCHMARK(LoggingYoThreadNames, benchmark::PriorityLevel::HIGH);
+BENCHMARK(LoggingNoThreadNames, benchmark::PriorityLevel::HIGH);
+BENCHMARK(LoggingYoCategory, benchmark::PriorityLevel::HIGH);
+BENCHMARK(LoggingNoCategory, benchmark::PriorityLevel::HIGH);
+BENCHMARK(LoggingNoFile, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/mempool_eviction.cpp b/src/bench/mempool_eviction.cpp
index 60d991fab9..878e375a7c 100644
--- a/src/bench/mempool_eviction.cpp
+++ b/src/bench/mempool_eviction.cpp
@@ -132,4 +132,4 @@ static void MempoolEviction(benchmark::Bench& bench)
});
}
-BENCHMARK(MempoolEviction);
+BENCHMARK(MempoolEviction, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/mempool_stress.cpp b/src/bench/mempool_stress.cpp
index 725a6f8f5b..9f5b28dca7 100644
--- a/src/bench/mempool_stress.cpp
+++ b/src/bench/mempool_stress.cpp
@@ -114,5 +114,5 @@ static void MempoolCheck(benchmark::Bench& bench)
});
}
-BENCHMARK(ComplexMemPool);
-BENCHMARK(MempoolCheck);
+BENCHMARK(ComplexMemPool, benchmark::PriorityLevel::HIGH);
+BENCHMARK(MempoolCheck, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/merkle_root.cpp b/src/bench/merkle_root.cpp
index ba6629b9f0..4140d67bc7 100644
--- a/src/bench/merkle_root.cpp
+++ b/src/bench/merkle_root.cpp
@@ -23,4 +23,4 @@ static void MerkleRoot(benchmark::Bench& bench)
});
}
-BENCHMARK(MerkleRoot);
+BENCHMARK(MerkleRoot, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/nanobench.h b/src/bench/nanobench.h
index 27df08fb69..916a7d61ef 100644
--- a/src/bench/nanobench.h
+++ b/src/bench/nanobench.h
@@ -858,7 +858,7 @@ public:
* @brief Retrieves all benchmark results collected by the bench object so far.
*
* Each call to run() generates a Result that is stored within the Bench instance. This is mostly for advanced users who want to
- * see all the nitty gritty detials.
+ * see all the nitty gritty details.
*
* @return All results collected so far.
*/
diff --git a/src/bench/peer_eviction.cpp b/src/bench/peer_eviction.cpp
index f05f5e8f64..c3e3670de7 100644
--- a/src/bench/peer_eviction.cpp
+++ b/src/bench/peer_eviction.cpp
@@ -141,15 +141,15 @@ static void EvictionProtection3Networks250Candidates(benchmark::Bench& bench)
// - 250 candidates is the number of peers reported by operators of busy nodes
// No disadvantaged networks, with 250 eviction candidates.
-BENCHMARK(EvictionProtection0Networks250Candidates);
+BENCHMARK(EvictionProtection0Networks250Candidates, benchmark::PriorityLevel::HIGH);
// 1 disadvantaged network (Tor) with 250 eviction candidates.
-BENCHMARK(EvictionProtection1Networks250Candidates);
+BENCHMARK(EvictionProtection1Networks250Candidates, benchmark::PriorityLevel::HIGH);
// 2 disadvantaged networks (I2P, Tor) with 250 eviction candidates.
-BENCHMARK(EvictionProtection2Networks250Candidates);
+BENCHMARK(EvictionProtection2Networks250Candidates, benchmark::PriorityLevel::HIGH);
// 3 disadvantaged networks (I2P/localhost/Tor) with 50/100/250 eviction candidates.
-BENCHMARK(EvictionProtection3Networks050Candidates);
-BENCHMARK(EvictionProtection3Networks100Candidates);
-BENCHMARK(EvictionProtection3Networks250Candidates);
+BENCHMARK(EvictionProtection3Networks050Candidates, benchmark::PriorityLevel::HIGH);
+BENCHMARK(EvictionProtection3Networks100Candidates, benchmark::PriorityLevel::HIGH);
+BENCHMARK(EvictionProtection3Networks250Candidates, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/poly1305.cpp b/src/bench/poly1305.cpp
index cdef97c0ea..ad5a72ffde 100644
--- a/src/bench/poly1305.cpp
+++ b/src/bench/poly1305.cpp
@@ -36,6 +36,6 @@ static void POLY1305_1MB(benchmark::Bench& bench)
POLY1305(bench, BUFFER_SIZE_LARGE);
}
-BENCHMARK(POLY1305_64BYTES);
-BENCHMARK(POLY1305_256BYTES);
-BENCHMARK(POLY1305_1MB);
+BENCHMARK(POLY1305_64BYTES, benchmark::PriorityLevel::HIGH);
+BENCHMARK(POLY1305_256BYTES, benchmark::PriorityLevel::HIGH);
+BENCHMARK(POLY1305_1MB, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/prevector.cpp b/src/bench/prevector.cpp
index b3688bab1b..9e2e7d11c4 100644
--- a/src/bench/prevector.cpp
+++ b/src/bench/prevector.cpp
@@ -85,12 +85,12 @@ static void PrevectorDeserialize(benchmark::Bench& bench)
{ \
Prevector##name<nontrivial_t>(bench); \
} \
- BENCHMARK(Prevector##name##Nontrivial); \
+ BENCHMARK(Prevector##name##Nontrivial, benchmark::PriorityLevel::HIGH); \
static void Prevector##name##Trivial(benchmark::Bench& bench) \
{ \
Prevector##name<trivial_t>(bench); \
} \
- BENCHMARK(Prevector##name##Trivial);
+ BENCHMARK(Prevector##name##Trivial, benchmark::PriorityLevel::HIGH);
PREVECTOR_TEST(Clear)
PREVECTOR_TEST(Destructor)
diff --git a/src/bench/rollingbloom.cpp b/src/bench/rollingbloom.cpp
index 8f05e3bad0..865d99f9e8 100644
--- a/src/bench/rollingbloom.cpp
+++ b/src/bench/rollingbloom.cpp
@@ -32,5 +32,5 @@ static void RollingBloomReset(benchmark::Bench& bench)
});
}
-BENCHMARK(RollingBloom);
-BENCHMARK(RollingBloomReset);
+BENCHMARK(RollingBloom, benchmark::PriorityLevel::HIGH);
+BENCHMARK(RollingBloomReset, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/rpc_blockchain.cpp b/src/bench/rpc_blockchain.cpp
index e6fc8d21f4..5a178f308a 100644
--- a/src/bench/rpc_blockchain.cpp
+++ b/src/bench/rpc_blockchain.cpp
@@ -45,7 +45,7 @@ static void BlockToJsonVerbose(benchmark::Bench& bench)
});
}
-BENCHMARK(BlockToJsonVerbose);
+BENCHMARK(BlockToJsonVerbose, benchmark::PriorityLevel::HIGH);
static void BlockToJsonVerboseWrite(benchmark::Bench& bench)
{
@@ -57,4 +57,4 @@ static void BlockToJsonVerboseWrite(benchmark::Bench& bench)
});
}
-BENCHMARK(BlockToJsonVerboseWrite);
+BENCHMARK(BlockToJsonVerboseWrite, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/rpc_mempool.cpp b/src/bench/rpc_mempool.cpp
index 0e6fdae3d7..4fdc31ae05 100644
--- a/src/bench/rpc_mempool.cpp
+++ b/src/bench/rpc_mempool.cpp
@@ -40,4 +40,4 @@ static void RpcMempool(benchmark::Bench& bench)
});
}
-BENCHMARK(RpcMempool);
+BENCHMARK(RpcMempool, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/strencodings.cpp b/src/bench/strencodings.cpp
index 69b3a83cbf..16d14278a7 100644
--- a/src/bench/strencodings.cpp
+++ b/src/bench/strencodings.cpp
@@ -15,4 +15,4 @@ static void HexStrBench(benchmark::Bench& bench)
});
}
-BENCHMARK(HexStrBench);
+BENCHMARK(HexStrBench, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/util_time.cpp b/src/bench/util_time.cpp
index afc733482e..8256b5f7ba 100644
--- a/src/bench/util_time.cpp
+++ b/src/bench/util_time.cpp
@@ -36,7 +36,7 @@ static void BenchTimeMillisSys(benchmark::Bench& bench)
});
}
-BENCHMARK(BenchTimeDeprecated);
-BENCHMARK(BenchTimeMillis);
-BENCHMARK(BenchTimeMillisSys);
-BENCHMARK(BenchTimeMock);
+BENCHMARK(BenchTimeDeprecated, benchmark::PriorityLevel::HIGH);
+BENCHMARK(BenchTimeMillis, benchmark::PriorityLevel::HIGH);
+BENCHMARK(BenchTimeMillisSys, benchmark::PriorityLevel::HIGH);
+BENCHMARK(BenchTimeMock, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp
index 8e4708f260..f0e9db8ba1 100644
--- a/src/bench/verify_script.cpp
+++ b/src/bench/verify_script.cpp
@@ -96,5 +96,5 @@ static void VerifyNestedIfScript(benchmark::Bench& bench)
});
}
-BENCHMARK(VerifyScriptBench);
-BENCHMARK(VerifyNestedIfScript);
+BENCHMARK(VerifyScriptBench, benchmark::PriorityLevel::HIGH);
+BENCHMARK(VerifyNestedIfScript, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp
index a5dd2f3bb1..22d99c0e29 100644
--- a/src/bench/wallet_balance.cpp
+++ b/src/bench/wallet_balance.cpp
@@ -57,7 +57,7 @@ static void WalletBalanceClean(benchmark::Bench& bench) { WalletBalance(bench, /
static void WalletBalanceMine(benchmark::Bench& bench) { WalletBalance(bench, /*set_dirty=*/false, /*add_mine=*/true); }
static void WalletBalanceWatch(benchmark::Bench& bench) { WalletBalance(bench, /*set_dirty=*/false, /*add_mine=*/false); }
-BENCHMARK(WalletBalanceDirty);
-BENCHMARK(WalletBalanceClean);
-BENCHMARK(WalletBalanceMine);
-BENCHMARK(WalletBalanceWatch);
+BENCHMARK(WalletBalanceDirty, benchmark::PriorityLevel::HIGH);
+BENCHMARK(WalletBalanceClean, benchmark::PriorityLevel::HIGH);
+BENCHMARK(WalletBalanceMine, benchmark::PriorityLevel::HIGH);
+BENCHMARK(WalletBalanceWatch, benchmark::PriorityLevel::HIGH);
diff --git a/src/bench/wallet_create_tx.cpp b/src/bench/wallet_create_tx.cpp
new file mode 100644
index 0000000000..207b22c584
--- /dev/null
+++ b/src/bench/wallet_create_tx.cpp
@@ -0,0 +1,142 @@
+// Copyright (c) 2022 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or https://www.opensource.org/licenses/mit-license.php.
+
+#include <bench/bench.h>
+#include <chainparams.h>
+#include <wallet/coincontrol.h>
+#include <consensus/merkle.h>
+#include <kernel/chain.h>
+#include <node/context.h>
+#include <test/util/setup_common.h>
+#include <test/util/wallet.h>
+#include <validation.h>
+#include <wallet/spend.h>
+#include <wallet/wallet.h>
+
+using wallet::CWallet;
+using wallet::CreateMockWalletDatabase;
+using wallet::DBErrors;
+using wallet::WALLET_FLAG_DESCRIPTORS;
+
+struct TipBlock
+{
+ uint256 prev_block_hash;
+ int64_t prev_block_time;
+ int tip_height;
+};
+
+TipBlock getTip(const CChainParams& params, const node::NodeContext& context)
+{
+ auto tip = WITH_LOCK(::cs_main, return context.chainman->ActiveTip());
+ return (tip) ? TipBlock{tip->GetBlockHash(), tip->GetBlockTime(), tip->nHeight} :
+ TipBlock{params.GenesisBlock().GetHash(), params.GenesisBlock().GetBlockTime(), 0};
+}
+
+void generateFakeBlock(const CChainParams& params,
+ const node::NodeContext& context,
+ CWallet& wallet,
+ const CScript& coinbase_out_script)
+{
+ TipBlock tip{getTip(params, context)};
+
+ // Create block
+ CBlock block;
+ CMutableTransaction coinbase_tx;
+ coinbase_tx.vin.resize(1);
+ coinbase_tx.vin[0].prevout.SetNull();
+ coinbase_tx.vout.resize(2);
+ coinbase_tx.vout[0].scriptPubKey = coinbase_out_script;
+ coinbase_tx.vout[0].nValue = 49 * COIN;
+ coinbase_tx.vin[0].scriptSig = CScript() << ++tip.tip_height << OP_0;
+ coinbase_tx.vout[1].scriptPubKey = coinbase_out_script; // extra output
+ coinbase_tx.vout[1].nValue = 1 * COIN;
+ block.vtx = {MakeTransactionRef(std::move(coinbase_tx))};
+
+ block.nVersion = VERSIONBITS_LAST_OLD_BLOCK_VERSION;
+ block.hashPrevBlock = tip.prev_block_hash;
+ block.hashMerkleRoot = BlockMerkleRoot(block);
+ block.nTime = ++tip.prev_block_time;
+ block.nBits = params.GenesisBlock().nBits;
+ block.nNonce = 0;
+
+ {
+ LOCK(::cs_main);
+ // Add it to the index
+ CBlockIndex* pindex{context.chainman->m_blockman.AddToBlockIndex(block, context.chainman->m_best_header)};
+ // add it to the chain
+ context.chainman->ActiveChain().SetTip(*pindex);
+ }
+
+ // notify wallet
+ const auto& pindex = WITH_LOCK(::cs_main, return context.chainman->ActiveChain().Tip());
+ wallet.blockConnected(kernel::MakeBlockInfo(pindex, &block));
+}
+
+struct PreSelectInputs {
+ // How many coins from the wallet the process should select
+ int num_of_internal_inputs;
+ // future: this could have external inputs as well.
+};
+
+static void WalletCreateTx(benchmark::Bench& bench, const OutputType output_type, bool allow_other_inputs, std::optional<PreSelectInputs> preset_inputs)
+{
+ const auto test_setup = MakeNoLogFileContext<const TestingSetup>();
+
+ CWallet wallet{test_setup->m_node.chain.get(), "", gArgs, CreateMockWalletDatabase()};
+ {
+ LOCK(wallet.cs_wallet);
+ wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
+ wallet.SetupDescriptorScriptPubKeyMans();
+ if (wallet.LoadWallet() != DBErrors::LOAD_OK) assert(false);
+ }
+
+ // Generate destinations
+ CScript dest = GetScriptForDestination(getNewDestination(wallet, output_type));
+
+ // Generate chain; each coinbase will have two outputs to fill-up the wallet
+ const auto& params = Params();
+ unsigned int chain_size = 5000; // 5k blocks means 10k UTXO for the wallet (minus 200 due COINBASE_MATURITY)
+ for (unsigned int i = 0; i < chain_size; ++i) {
+ generateFakeBlock(params, test_setup->m_node, wallet, dest);
+ }
+
+ // Check available balance
+ auto bal = wallet::GetAvailableBalance(wallet); // Cache
+ assert(bal == 50 * COIN * (chain_size - COINBASE_MATURITY));
+
+ wallet::CCoinControl coin_control;
+ coin_control.m_allow_other_inputs = allow_other_inputs;
+
+ CAmount target = 0;
+ if (preset_inputs) {
+ // Select inputs, each has 49 BTC
+ const auto& res = WITH_LOCK(wallet.cs_wallet,
+ return wallet::AvailableCoins(wallet, nullptr, std::nullopt, 1, MAX_MONEY,
+ MAX_MONEY, preset_inputs->num_of_internal_inputs));
+ for (int i=0; i < preset_inputs->num_of_internal_inputs; i++) {
+ const auto& coin{res.coins.at(output_type)[i]};
+ target += coin.txout.nValue;
+ coin_control.Select(coin.outpoint);
+ }
+ }
+
+ // If automatic coin selection is enabled, add the value of another UTXO to the target
+ if (coin_control.m_allow_other_inputs) target += 50 * COIN;
+ std::vector<wallet::CRecipient> recipients = {{dest, target, true}};
+
+ bench.epochIterations(5).run([&] {
+ LOCK(wallet.cs_wallet);
+ const auto& tx_res = CreateTransaction(wallet, recipients, -1, coin_control);
+ assert(tx_res);
+ });
+}
+
+static void WalletCreateTxUseOnlyPresetInputs(benchmark::Bench& bench) { WalletCreateTx(bench, OutputType::BECH32, /*allow_other_inputs=*/false,
+ {{/*num_of_internal_inputs=*/4}}); }
+
+static void WalletCreateTxUsePresetInputsAndCoinSelection(benchmark::Bench& bench) { WalletCreateTx(bench, OutputType::BECH32, /*allow_other_inputs=*/true,
+ {{/*num_of_internal_inputs=*/4}}); }
+
+BENCHMARK(WalletCreateTxUseOnlyPresetInputs, benchmark::PriorityLevel::LOW)
+BENCHMARK(WalletCreateTxUsePresetInputsAndCoinSelection, benchmark::PriorityLevel::LOW)
diff --git a/src/bench/wallet_loading.cpp b/src/bench/wallet_loading.cpp
index a10f7ff7d1..8bfaf3044b 100644
--- a/src/bench/wallet_loading.cpp
+++ b/src/bench/wallet_loading.cpp
@@ -45,11 +45,8 @@ static void BenchUnloadWallet(std::shared_ptr<CWallet>&& wallet)
static void AddTx(CWallet& wallet)
{
- const auto& dest = wallet.GetNewDestination(OutputType::BECH32, "");
- assert(dest.HasRes());
-
CMutableTransaction mtx;
- mtx.vout.push_back({COIN, GetScriptForDestination(dest.GetObj())});
+ mtx.vout.push_back({COIN, GetScriptForDestination(*Assert(wallet.GetNewDestination(OutputType::BECH32, "")))});
mtx.vin.push_back(CTxIn());
wallet.AddToWallet(MakeTransactionRef(mtx), TxStateInactive{});
@@ -121,10 +118,10 @@ static void WalletLoading(benchmark::Bench& bench, bool legacy_wallet)
#ifdef USE_BDB
static void WalletLoadingLegacy(benchmark::Bench& bench) { WalletLoading(bench, /*legacy_wallet=*/true); }
-BENCHMARK(WalletLoadingLegacy);
+BENCHMARK(WalletLoadingLegacy, benchmark::PriorityLevel::HIGH);
#endif
#ifdef USE_SQLITE
static void WalletLoadingDescriptors(benchmark::Bench& bench) { WalletLoading(bench, /*legacy_wallet=*/false); }
-BENCHMARK(WalletLoadingDescriptors);
+BENCHMARK(WalletLoadingDescriptors, benchmark::PriorityLevel::HIGH);
#endif
diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp
index 4656cb23e7..d972b71a65 100644
--- a/src/bitcoin-chainstate.cpp
+++ b/src/bitcoin-chainstate.cpp
@@ -13,6 +13,7 @@
#include <kernel/checks.h>
#include <kernel/context.h>
+#include <kernel/validation_cache_sizes.h>
#include <chainparams.h>
#include <consensus/validation.h>
@@ -62,8 +63,9 @@ int main(int argc, char* argv[])
// Necessary for CheckInputScripts (eventually called by ProcessNewBlock),
// which will try the script cache first and fall back to actually
// performing the check with the signature cache.
- InitSignatureCache();
- InitScriptExecutionCache();
+ kernel::ValidationCacheSizes validation_cache_sizes{};
+ Assert(InitSignatureCache(validation_cache_sizes.signature_cache_bytes));
+ Assert(InitScriptExecutionCache(validation_cache_sizes.script_execution_cache_bytes));
// SETUP: Scheduling and Background Signals
@@ -80,7 +82,7 @@ int main(int argc, char* argv[])
// SETUP: Chainstate
const ChainstateManager::Options chainman_opts{
.chainparams = chainparams,
- .adjusted_time_callback = static_cast<int64_t (*)()>(GetTime),
+ .adjusted_time_callback = NodeClock::now,
};
ChainstateManager chainman{chainman_opts};
@@ -102,7 +104,7 @@ int main(int argc, char* argv[])
}
}
- for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
+ for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(state, nullptr)) {
std::cerr << "Failed to connect best block (" << state.ToString() << ")" << std::endl;
@@ -113,12 +115,14 @@ int main(int argc, char* argv[])
// Main program logic starts here
std::cout
<< "Hello! I'm going to print out some information about your datadir." << std::endl
- << "\t" << "Path: " << gArgs.GetDataDirNet() << std::endl
+ << "\t" << "Path: " << gArgs.GetDataDirNet() << std::endl;
+ {
+ LOCK(chainman.GetMutex());
+ std::cout
<< "\t" << "Reindexing: " << std::boolalpha << node::fReindex.load() << std::noboolalpha << std::endl
<< "\t" << "Snapshot Active: " << std::boolalpha << chainman.IsSnapshotActive() << std::noboolalpha << std::endl
<< "\t" << "Active Height: " << chainman.ActiveHeight() << std::endl
<< "\t" << "Active IBD: " << std::boolalpha << chainman.ActiveChainstate().IsInitialBlockDownload() << std::noboolalpha << std::endl;
- {
CBlockIndex* tip = chainman.ActiveTip();
if (tip) {
std::cout << "\t" << tip->ToString() << std::endl;
@@ -191,7 +195,7 @@ int main(int argc, char* argv[])
bool new_block;
auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash());
RegisterSharedValidationInterface(sc);
- bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*new_block=*/&new_block);
+ bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block);
UnregisterSharedValidationInterface(sc);
if (!new_block && accepted) {
std::cerr << "duplicate" << std::endl;
@@ -206,6 +210,9 @@ int main(int argc, char* argv[])
case BlockValidationResult::BLOCK_RESULT_UNSET:
std::cerr << "initial value. Block has not yet been rejected" << std::endl;
break;
+ case BlockValidationResult::BLOCK_HEADER_LOW_WORK:
+ std::cerr << "the block header may be on a too-little-work chain" << std::endl;
+ break;
case BlockValidationResult::BLOCK_CONSENSUS:
std::cerr << "invalid by consensus rules (excluding any below reasons)" << std::endl;
break;
@@ -246,7 +253,7 @@ epilogue:
GetMainSignals().FlushBackgroundCallbacks();
{
LOCK(cs_main);
- for (CChainState* chainstate : chainman.GetAll()) {
+ for (Chainstate* chainstate : chainman.GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
chainstate->ResetCoinsViews();
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index 7cc956ebda..c06488dbe9 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -9,6 +9,7 @@
#include <chainparamsbase.h>
#include <clientversion.h>
+#include <common/url.h>
#include <compat/compat.h>
#include <compat/stdin.h>
#include <policy/feerate.h>
@@ -21,7 +22,6 @@
#include <util/strencodings.h>
#include <util/system.h>
#include <util/translation.h>
-#include <util/url.h>
#include <algorithm>
#include <chrono>
@@ -642,7 +642,7 @@ public:
" send Time since last message sent to the peer, in seconds\n"
" recv Time since last message received from the peer, in seconds\n"
" txn Time since last novel transaction received from the peer and accepted into our mempool, in minutes\n"
- " \"*\" - the peer requested we not relay transactions to it (relaytxes is false)\n"
+ " \"*\" - whether we relay transactions to this peer (relaytxes is false)\n"
" blk Time since last novel block passing initial validity checks received from the peer, in minutes\n"
" hb High-bandwidth BIP152 compact block relay\n"
" \".\" (to) - we selected the peer as a high-bandwidth peer\n"
@@ -807,7 +807,7 @@ static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, co
if (failedToGetAuthCookie) {
throw std::runtime_error(strprintf(
"Could not locate RPC credentials. No authentication cookie could be found, and RPC password is not set. See -rpcpassword and -stdinrpcpass. Configuration file: (%s)",
- fs::PathToString(GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)))));
+ fs::PathToString(GetConfigFile(gArgs.GetPathArg("-conf", BITCOIN_CONF_FILENAME)))));
} else {
throw std::runtime_error("Authorization failed: Incorrect rpcuser or rpcpassword");
}
@@ -911,7 +911,7 @@ static void GetWalletBalances(UniValue& result)
UniValue balances(UniValue::VOBJ);
for (const UniValue& wallet : wallets.getValues()) {
- const std::string wallet_name = wallet.get_str();
+ const std::string& wallet_name = wallet.get_str();
const UniValue getbalances = ConnectAndCallRPC(&rh, "getbalances", /* args=*/{}, wallet_name);
const UniValue& balance = find_value(getbalances, "result")["mine"]["trusted"];
balances.pushKV(wallet_name, balance);
diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp
index b006353cb0..010cac5920 100644
--- a/src/bitcoin-tx.cpp
+++ b/src/bitcoin-tx.cpp
@@ -15,7 +15,6 @@
#include <key_io.h>
#include <fs.h>
#include <policy/policy.h>
-#include <policy/rbf.h>
#include <primitives/transaction.h>
#include <script/script.h>
#include <script/sign.h>
@@ -28,9 +27,9 @@
#include <util/system.h>
#include <util/translation.h>
+#include <cstdio>
#include <functional>
#include <memory>
-#include <stdio.h>
static bool fCreateBlank;
static std::map<std::string,UniValue> registers;
@@ -596,7 +595,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
UniValue prevtxsObj = registers["prevtxs"];
{
for (unsigned int previdx = 0; previdx < prevtxsObj.size(); previdx++) {
- UniValue prevOut = prevtxsObj[previdx];
+ const UniValue& prevOut = prevtxsObj[previdx];
if (!prevOut.isObject())
throw std::runtime_error("expected prevtxs internal object");
diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp
index a7d49452b0..78c1a2060c 100644
--- a/src/bitcoin-wallet.cpp
+++ b/src/bitcoin-wallet.cpp
@@ -9,6 +9,7 @@
#include <chainparams.h>
#include <chainparamsbase.h>
#include <clientversion.h>
+#include <common/url.h>
#include <compat/compat.h>
#include <interfaces/init.h>
#include <key.h>
@@ -17,7 +18,6 @@
#include <tinyformat.h>
#include <util/system.h>
#include <util/translation.h>
-#include <util/url.h>
#include <wallet/wallettool.h>
#include <exception>
@@ -50,15 +50,16 @@ static void SetupWalletToolArgs(ArgsManager& argsman)
argsman.AddCommand("createfromdump", "Create new wallet file from dumped records");
}
-static bool WalletAppInit(ArgsManager& args, int argc, char* argv[])
+static std::optional<int> WalletAppInit(ArgsManager& args, int argc, char* argv[])
{
SetupWalletToolArgs(args);
std::string error_message;
if (!args.ParseParameters(argc, argv, error_message)) {
tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error_message);
- return false;
+ return EXIT_FAILURE;
}
- if (argc < 2 || HelpRequested(args) || args.IsArgSet("-version")) {
+ const bool missing_args{argc < 2};
+ if (missing_args || HelpRequested(args) || args.IsArgSet("-version")) {
std::string strUsage = strprintf("%s bitcoin-wallet version", PACKAGE_NAME) + " " + FormatFullVersion() + "\n";
if (args.IsArgSet("-version")) {
@@ -73,7 +74,11 @@ static bool WalletAppInit(ArgsManager& args, int argc, char* argv[])
strUsage += "\n" + args.GetHelpMessage();
}
tfm::format(std::cout, "%s", strUsage);
- return false;
+ if (missing_args) {
+ tfm::format(std::cerr, "Error: too few parameters\n");
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
}
// check for printtoconsole, allow -debug
@@ -81,12 +86,12 @@ static bool WalletAppInit(ArgsManager& args, int argc, char* argv[])
if (!CheckDataDirOption()) {
tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", args.GetArg("-datadir", ""));
- return false;
+ return EXIT_FAILURE;
}
// Check for chain settings (Params() calls are only valid after this clause)
SelectParams(args.GetChainName());
- return true;
+ return std::nullopt;
}
MAIN_FUNCTION
@@ -106,7 +111,7 @@ MAIN_FUNCTION
SetupEnvironment();
RandomInit();
try {
- if (!WalletAppInit(args, argc, argv)) return EXIT_FAILURE;
+ if (const auto maybe_exit{WalletAppInit(args, argc, argv)}) return *maybe_exit;
} catch (const std::exception& e) {
PrintExceptionContinue(&e, "WalletAppInit()");
return EXIT_FAILURE;
diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp
index 85ba88f6ab..d8d4e34e47 100644
--- a/src/bitcoind.cpp
+++ b/src/bitcoind.cpp
@@ -9,6 +9,7 @@
#include <chainparams.h>
#include <clientversion.h>
+#include <common/url.h>
#include <compat/compat.h>
#include <init.h>
#include <interfaces/chain.h>
@@ -25,7 +26,6 @@
#include <util/threadnames.h>
#include <util/tokenpipe.h>
#include <util/translation.h>
-#include <util/url.h>
#include <any>
#include <functional>
@@ -216,7 +216,7 @@ static bool AppInit(NodeContext& node, int argc, char* argv[])
if (token) { // Success
exit(EXIT_SUCCESS);
} else { // fRet = false or token read error (premature exit).
- tfm::format(std::cerr, "Error during initializaton - check debug.log for details\n");
+ tfm::format(std::cerr, "Error during initialization - check debug.log for details\n");
exit(EXIT_FAILURE);
}
}
diff --git a/src/blockfilter.cpp b/src/blockfilter.cpp
index 0ff79bb3ca..85929747be 100644
--- a/src/blockfilter.cpp
+++ b/src/blockfilter.cpp
@@ -3,7 +3,6 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <mutex>
-#include <sstream>
#include <set>
#include <blockfilter.h>
@@ -13,6 +12,7 @@
#include <script/script.h>
#include <streams.h>
#include <util/golombrice.h>
+#include <util/string.h>
/// SerType used to serialize parameters in GCS filter encoding.
static constexpr int GCS_SER_TYPE = SER_NETWORK;
@@ -169,7 +169,7 @@ const std::set<BlockFilterType>& AllBlockFilterTypes()
static std::once_flag flag;
std::call_once(flag, []() {
- for (auto entry : g_filter_types) {
+ for (const auto& entry : g_filter_types) {
types.insert(entry.first);
}
});
@@ -179,19 +179,7 @@ const std::set<BlockFilterType>& AllBlockFilterTypes()
const std::string& ListBlockFilterTypes()
{
- static std::string type_list;
-
- static std::once_flag flag;
- std::call_once(flag, []() {
- std::stringstream ret;
- bool first = true;
- for (auto entry : g_filter_types) {
- if (!first) ret << ", ";
- ret << entry.second;
- first = false;
- }
- type_list = ret.str();
- });
+ static std::string type_list{Join(g_filter_types, ", ", [](const auto& entry) { return entry.second; })};
return type_list;
}
diff --git a/src/blockfilter.h b/src/blockfilter.h
index d6a51e95c2..0cb627d9df 100644
--- a/src/blockfilter.h
+++ b/src/blockfilter.h
@@ -11,6 +11,7 @@
#include <unordered_set>
#include <vector>
+#include <attributes.h>
#include <primitives/block.h>
#include <serialize.h>
#include <uint256.h>
@@ -65,8 +66,8 @@ public:
GCSFilter(const Params& params, const ElementSet& elements);
uint32_t GetN() const { return m_N; }
- const Params& GetParams() const { return m_params; }
- const std::vector<unsigned char>& GetEncoded() const { return m_encoded; }
+ const Params& GetParams() const LIFETIMEBOUND { return m_params; }
+ const std::vector<unsigned char>& GetEncoded() const LIFETIMEBOUND { return m_encoded; }
/**
* Checks if the element may be in the set. False positives are possible
@@ -128,10 +129,10 @@ public:
BlockFilter(BlockFilterType filter_type, const CBlock& block, const CBlockUndo& block_undo);
BlockFilterType GetFilterType() const { return m_filter_type; }
- const uint256& GetBlockHash() const { return m_block_hash; }
- const GCSFilter& GetFilter() const { return m_filter; }
+ const uint256& GetBlockHash() const LIFETIMEBOUND { return m_block_hash; }
+ const GCSFilter& GetFilter() const LIFETIMEBOUND { return m_filter; }
- const std::vector<unsigned char>& GetEncodedFilter() const
+ const std::vector<unsigned char>& GetEncodedFilter() const LIFETIMEBOUND
{
return m_filter.GetEncoded();
}
diff --git a/src/chain.cpp b/src/chain.cpp
index 0f898bafd5..66a0830394 100644
--- a/src/chain.cpp
+++ b/src/chain.cpp
@@ -18,11 +18,9 @@ std::string CBlockIndex::ToString() const
pprev, nHeight, hashMerkleRoot.ToString(), GetBlockHash().ToString());
}
-void CChain::SetTip(CBlockIndex *pindex) {
- if (pindex == nullptr) {
- vChain.clear();
- return;
- }
+void CChain::SetTip(CBlockIndex& block)
+{
+ CBlockIndex* pindex = &block;
vChain.resize(pindex->nHeight + 1);
while (pindex && vChain[pindex->nHeight] != pindex) {
vChain[pindex->nHeight] = pindex;
@@ -30,32 +28,33 @@ void CChain::SetTip(CBlockIndex *pindex) {
}
}
-CBlockLocator CChain::GetLocator(const CBlockIndex *pindex) const {
- int nStep = 1;
- std::vector<uint256> vHave;
- vHave.reserve(32);
-
- if (!pindex)
- pindex = Tip();
- while (pindex) {
- vHave.push_back(pindex->GetBlockHash());
- // Stop when we have added the genesis block.
- if (pindex->nHeight == 0)
- break;
+std::vector<uint256> LocatorEntries(const CBlockIndex* index)
+{
+ int step = 1;
+ std::vector<uint256> have;
+ if (index == nullptr) return have;
+
+ have.reserve(32);
+ while (index) {
+ have.emplace_back(index->GetBlockHash());
+ if (index->nHeight == 0) break;
// Exponentially larger steps back, plus the genesis block.
- int nHeight = std::max(pindex->nHeight - nStep, 0);
- if (Contains(pindex)) {
- // Use O(1) CChain index if possible.
- pindex = (*this)[nHeight];
- } else {
- // Otherwise, use O(log n) skiplist.
- pindex = pindex->GetAncestor(nHeight);
- }
- if (vHave.size() > 10)
- nStep *= 2;
+ int height = std::max(index->nHeight - step, 0);
+ // Use skiplist.
+ index = index->GetAncestor(height);
+ if (have.size() > 10) step *= 2;
}
+ return have;
+}
- return CBlockLocator(vHave);
+CBlockLocator GetLocator(const CBlockIndex* index)
+{
+ return CBlockLocator{LocatorEntries(index)};
+}
+
+CBlockLocator CChain::GetLocator() const
+{
+ return ::GetLocator(Tip());
}
const CBlockIndex *CChain::FindFork(const CBlockIndex *pindex) const {
diff --git a/src/chain.h b/src/chain.h
index 627a3dfab2..2d3b084b9b 100644
--- a/src/chain.h
+++ b/src/chain.h
@@ -12,6 +12,7 @@
#include <primitives/block.h>
#include <sync.h>
#include <uint256.h>
+#include <util/time.h>
#include <vector>
@@ -275,6 +276,11 @@ public:
*/
bool HaveTxsDownloaded() const { return nChainTx != 0; }
+ NodeSeconds Time() const
+ {
+ return NodeSeconds{std::chrono::seconds{nTime}};
+ }
+
int64_t GetBlockTime() const
{
return (int64_t)nTime;
@@ -465,10 +471,10 @@ public:
}
/** Set/initialize a chain with a given tip. */
- void SetTip(CBlockIndex* pindex);
+ void SetTip(CBlockIndex& block);
- /** Return a CBlockLocator that refers to a block in this chain (by default the tip). */
- CBlockLocator GetLocator(const CBlockIndex* pindex = nullptr) const;
+ /** Return a CBlockLocator that refers to the tip in of this chain. */
+ CBlockLocator GetLocator() const;
/** Find the last common block between this chain and a block index entry. */
const CBlockIndex* FindFork(const CBlockIndex* pindex) const;
@@ -477,4 +483,10 @@ public:
CBlockIndex* FindEarliestAtLeast(int64_t nTime, int height) const;
};
+/** Get a locator for a block index entry. */
+CBlockLocator GetLocator(const CBlockIndex* index);
+
+/** Construct a list of hash entries to put in a locator. */
+std::vector<uint256> LocatorEntries(const CBlockIndex* index);
+
#endif // BITCOIN_CHAIN_H
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index dd7b93234d..c6d4eee7b9 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -93,8 +93,8 @@ public:
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = 1628640000; // August 11th, 2021
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 709632; // Approximately November 12th, 2021
- consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000002927cdceccbd5209e81e80db");
- consensus.defaultAssumeValid = uint256S("0x000000000000000000052d314a259755ca65944e68df6b12a067ea8f1f5a7091"); // 724466
+ consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000003404ba0801921119f903495e");
+ consensus.defaultAssumeValid = uint256S("0x00000000000000000009c97098b5295f7e5f183ac811fb5d1534040adb93cabd"); // 751565
/**
* The message start string is designed to be unlikely to occur in normal data.
@@ -107,7 +107,7 @@ public:
pchMessageStart[3] = 0xd9;
nDefaultPort = 8333;
nPruneAfterHeight = 100000;
- m_assumed_blockchain_size = 460;
+ m_assumed_blockchain_size = 496;
m_assumed_chain_state_size = 6;
genesis = CreateGenesisBlock(1231006505, 2083236893, 0x1d00ffff, 1, 50 * COIN);
@@ -168,10 +168,10 @@ public:
};
chainTxData = ChainTxData{
- // Data from RPC: getchaintxstats 4096 000000000000000000052d314a259755ca65944e68df6b12a067ea8f1f5a7091
- /* nTime */ 1645542140,
- /* nTxCount */ 712531200,
- /* dTxRate */ 2.891036496010309,
+ // Data from RPC: getchaintxstats 4096 00000000000000000009c97098b5295f7e5f183ac811fb5d1534040adb93cabd
+ .nTime = 1661697692,
+ .nTxCount = 760120522,
+ .dTxRate = 2.925802860942233,
};
}
};
@@ -213,8 +213,8 @@ public:
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = 1628640000; // August 11th, 2021
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay
- consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000064728c7be6fe4b2f961");
- consensus.defaultAssumeValid = uint256S("0x00000000000163cfb1f97c4e4098a3692c8053ad9cab5ad9c86b338b5c00b8b7"); // 2143398
+ consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000076f6e7cbd0beade5d20");
+ consensus.defaultAssumeValid = uint256S("0x0000000000000004877fa2d36316398528de4f347df2f8a96f76613a298ce060"); // 2344474
pchMessageStart[0] = 0x0b;
pchMessageStart[1] = 0x11;
@@ -222,7 +222,7 @@ public:
pchMessageStart[3] = 0x07;
nDefaultPort = 18333;
nPruneAfterHeight = 1000;
- m_assumed_blockchain_size = 40;
+ m_assumed_blockchain_size = 42;
m_assumed_chain_state_size = 2;
genesis = CreateGenesisBlock(1296688602, 414098458, 0x1d00ffff, 1, 50 * COIN);
@@ -264,10 +264,10 @@ public:
};
chainTxData = ChainTxData{
- // Data from RPC: getchaintxstats 4096 00000000d18cfe81cbeea665076807789bd8f831d557632e635bc6e3c003069e
- /* nTime */ 1645635119,
- /* nTxCount */ 62226341,
- /* dTxRate */ 0.07717997442177152,
+ // Data from RPC: getchaintxstats 4096 0000000000000004877fa2d36316398528de4f347df2f8a96f76613a298ce060
+ .nTime = 1661705221,
+ .nTxCount = 63531852,
+ .dTxRate = 0.1079119341520164,
};
}
};
@@ -289,15 +289,15 @@ public:
vSeeds.emplace_back("178.128.221.177");
vSeeds.emplace_back("v7ajjeirttkbnt32wpy3c6w3emwnfr3fkla7hpxcfokr3ysd3kqtzmqd.onion:38333");
- consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000000000de26b0e471");
- consensus.defaultAssumeValid = uint256S("0x00000112852484b5fe3451572368f93cfd2723279af3464e478aee35115256ef"); // 78788
+ consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000000001291fc22898");
+ consensus.defaultAssumeValid = uint256S("0x000000d1a0e224fa4679d2fb2187ba55431c284fa1b74cbc8cfda866fd4d2c09"); // 105495
m_assumed_blockchain_size = 1;
m_assumed_chain_state_size = 0;
chainTxData = ChainTxData{
- // Data from RPC: getchaintxstats 4096 0000003d9144c56ac110ae04a0c271a0acce2f14f426b39fdf0d938c96d2eb09
- /* nTime */ 1645631279,
- /* nTxCount */ 1257429,
- /* dTxRate */ 0.1389638742514995,
+ // Data from RPC: getchaintxstats 4096 000000d1a0e224fa4679d2fb2187ba55431c284fa1b74cbc8cfda866fd4d2c09
+ .nTime = 1661702566,
+ .nTxCount = 1903567,
+ .dTxRate = 0.02336701143027275,
};
} else {
const auto signet_challenge = args.GetArgs("-signetchallenge");
diff --git a/src/chainparamsseeds.h b/src/chainparamsseeds.h
index b9df1c61f0..f065647d49 100644
--- a/src/chainparamsseeds.h
+++ b/src/chainparamsseeds.h
@@ -7,703 +7,945 @@
* Each line contains a BIP155 serialized (networkID, addr, port) tuple.
*/
static const uint8_t chainparams_seed_main[] = {
- 0x01,0x04,0x02,0x25,0x1e,0x90,0x22,0x49,
- 0x01,0x04,0x02,0x8a,0xae,0x9e,0x20,0x8d,
+ 0x01,0x04,0x02,0x03,0x19,0xb5,0x20,0x8d,
0x01,0x04,0x02,0x98,0x4e,0x7c,0x20,0x8d,
- 0x01,0x04,0x05,0x08,0x12,0x9a,0x20,0x8d,
- 0x01,0x04,0x05,0x2d,0x4a,0x32,0x20,0x8d,
- 0x01,0x04,0x05,0x4f,0x7b,0x03,0x20,0x8d,
- 0x01,0x04,0x05,0x66,0xa8,0xd9,0x56,0xcc,
- 0x01,0x04,0x05,0x67,0x89,0x92,0x24,0x75,
+ 0x01,0x04,0x05,0x27,0x4a,0xa6,0x20,0x8d,
+ 0x01,0x04,0x05,0x2d,0x4f,0x51,0x47,0x9c,
+ 0x01,0x04,0x05,0x35,0x10,0x80,0x20,0x8d,
+ 0x01,0x04,0x05,0x5f,0xba,0x4e,0x20,0x8d,
0x01,0x04,0x05,0x80,0x57,0x7e,0x20,0x8d,
- 0x01,0x04,0x05,0xac,0x84,0xc8,0x20,0x8d,
+ 0x01,0x04,0x05,0x85,0x41,0x52,0x20,0x8d,
+ 0x01,0x04,0x05,0x92,0x14,0xe5,0x20,0x8d,
+ 0x01,0x04,0x05,0xb4,0x29,0x77,0x20,0x8d,
0x01,0x04,0x05,0xbc,0x3e,0x12,0x20,0x8d,
- 0x01,0x04,0x05,0xfe,0x65,0xe2,0x20,0x8e,
- 0x01,0x04,0x08,0xd2,0x12,0x38,0x20,0x8d,
- 0x01,0x04,0x08,0xd2,0x5c,0x20,0x20,0x8d,
- 0x01,0x04,0x0e,0x0d,0x22,0xe1,0x3f,0x35,
- 0x01,0x04,0x0e,0x27,0x97,0xa7,0x20,0x8d,
- 0x01,0x04,0x12,0xc4,0x4f,0x6c,0x20,0x8d,
- 0x01,0x04,0x12,0xda,0x8b,0x3a,0xbc,0xcd,
- 0x01,0x04,0x14,0xb8,0x0f,0x74,0x20,0xf1,
- 0x01,0x04,0x17,0xaf,0x00,0xdc,0x20,0x8d,
+ 0x01,0x04,0x05,0xc7,0xad,0x42,0x20,0x8d,
+ 0x01,0x04,0x05,0xff,0x61,0x19,0x20,0x8d,
+ 0x01,0x04,0x05,0xff,0x67,0xb4,0x20,0x8d,
+ 0x01,0x04,0x08,0xd1,0x46,0x4d,0x20,0x8d,
+ 0x01,0x04,0x08,0xd1,0x69,0x8a,0x20,0x8d,
+ 0x01,0x04,0x12,0xa2,0xd0,0x99,0xbc,0xcc,
+ 0x01,0x04,0x17,0xaf,0x00,0xc8,0x20,0x8d,
+ 0x01,0x04,0x17,0xaf,0x00,0xde,0x20,0x8d,
0x01,0x04,0x17,0xe9,0x6b,0x15,0x20,0x8d,
+ 0x01,0x04,0x17,0xec,0x19,0xa9,0x20,0x8d,
0x01,0x04,0x18,0x23,0x44,0xe5,0x20,0x8d,
- 0x01,0x04,0x18,0x25,0x03,0x1a,0x20,0x8d,
- 0x01,0x04,0x18,0x66,0x5b,0xcb,0x20,0x8d,
+ 0x01,0x04,0x18,0x54,0xa4,0x32,0x20,0x8d,
0x01,0x04,0x18,0x74,0x99,0x73,0x20,0x8d,
- 0x01,0x04,0x18,0x86,0x06,0xa5,0x20,0x8d,
- 0x01,0x04,0x18,0x9b,0xda,0x0d,0x20,0x8d,
- 0x01,0x04,0x18,0xa0,0x89,0xad,0x20,0x8d,
- 0x01,0x04,0x18,0xb1,0x6a,0x55,0x20,0x8d,
0x01,0x04,0x18,0xb8,0x00,0x92,0x20,0x8d,
- 0x01,0x04,0x18,0xc2,0xde,0x74,0x20,0x8d,
- 0x01,0x04,0x18,0xcd,0xd7,0xc0,0x20,0x8d,
+ 0x01,0x04,0x1b,0x21,0xa0,0xc4,0x20,0x8d,
0x01,0x04,0x1b,0x7c,0x6c,0x13,0x20,0x8d,
- 0x01,0x04,0x1f,0x0e,0x28,0x40,0x20,0x8d,
+ 0x01,0x04,0x1b,0x94,0xce,0x8c,0x20,0x8d,
+ 0x01,0x04,0x1f,0x11,0x40,0xc0,0x20,0x8d,
+ 0x01,0x04,0x1f,0x12,0x72,0x87,0x20,0x8d,
+ 0x01,0x04,0x1f,0x29,0x17,0xf9,0x20,0x8d,
+ 0x01,0x04,0x1f,0x2a,0xb0,0x8a,0x20,0x8d,
0x01,0x04,0x1f,0x2f,0xca,0x70,0x20,0x8d,
- 0x01,0x04,0x1f,0xa5,0x73,0x07,0x20,0x8d,
0x01,0x04,0x22,0x41,0x2d,0x9d,0x20,0x8d,
- 0x01,0x04,0x22,0x4e,0x30,0x68,0x20,0x8d,
0x01,0x04,0x22,0x50,0x86,0x44,0x20,0x8d,
- 0x01,0x04,0x22,0x65,0x84,0xc6,0x20,0x8d,
- 0x01,0x04,0x22,0xe3,0x44,0xd8,0x20,0x8d,
- 0x01,0x04,0x23,0x89,0xd4,0x16,0x20,0x8d,
- 0x01,0x04,0x23,0xe7,0xbe,0x86,0x20,0x8d,
- 0x01,0x04,0x25,0x01,0xd9,0x23,0x20,0x8d,
- 0x01,0x04,0x25,0x0f,0x3e,0x20,0x20,0x8d,
+ 0x01,0x04,0x22,0x7e,0x73,0x23,0x20,0x8d,
+ 0x01,0x04,0x25,0x01,0xcc,0xe7,0x20,0x8d,
+ 0x01,0x04,0x25,0x78,0x9b,0x22,0x20,0x8d,
0x01,0x04,0x25,0x8f,0x76,0xae,0x20,0x8d,
- 0x01,0x04,0x25,0xc8,0x3b,0x43,0x20,0x8d,
- 0x01,0x04,0x25,0xcd,0x09,0xa5,0x20,0x8d,
- 0x01,0x04,0x26,0x17,0xb4,0xe4,0x20,0x8d,
- 0x01,0x04,0x26,0x41,0x77,0x1a,0x20,0x8d,
+ 0x01,0x04,0x25,0xc1,0xe3,0x10,0x20,0x8d,
+ 0x01,0x04,0x25,0xdc,0x87,0x97,0x20,0x8d,
+ 0x01,0x04,0x25,0xeb,0x92,0xec,0x20,0x8d,
+ 0x01,0x04,0x26,0x7c,0x7e,0x2a,0x20,0x8d,
0x01,0x04,0x26,0x8d,0x86,0x8c,0x20,0x8d,
- 0x01,0x04,0x27,0x6d,0x7a,0x7f,0x20,0xfc,
+ 0x01,0x04,0x26,0x91,0x97,0x96,0x20,0x8d,
+ 0x01,0x04,0x28,0x73,0x89,0x1c,0x20,0x8d,
+ 0x01,0x04,0x29,0x48,0x9a,0x42,0x20,0x8d,
0x01,0x04,0x29,0x4f,0x46,0x92,0x20,0x8d,
- 0x01,0x04,0x29,0xc1,0x7a,0xbf,0x20,0x8d,
+ 0x01,0x04,0x2a,0xc1,0x37,0x87,0x20,0x8d,
0x01,0x04,0x2b,0xe1,0x3e,0x6b,0x20,0x8d,
- 0x01,0x04,0x2d,0x23,0x49,0x98,0x20,0x8d,
0x01,0x04,0x2d,0x2b,0x61,0x67,0x20,0x8d,
- 0x01,0x04,0x2d,0x3f,0x0a,0x34,0x4e,0x28,
- 0x01,0x04,0x2d,0x54,0x99,0x28,0x20,0x8d,
- 0x01,0x04,0x2d,0x5f,0x40,0xe1,0x20,0x8d,
- 0x01,0x04,0x2d,0x81,0xb4,0xd6,0x20,0x8d,
- 0x01,0x04,0x2d,0x9a,0xff,0xa2,0x20,0x8d,
- 0x01,0x04,0x2d,0xe2,0x50,0x66,0x20,0x8d,
- 0x01,0x04,0x2e,0x06,0x0a,0xe6,0x20,0x8d,
+ 0x01,0x04,0x2d,0x55,0x30,0x3a,0x20,0x8d,
+ 0x01,0x04,0x2d,0x7e,0x1a,0xe5,0x20,0x8d,
+ 0x01,0x04,0x2d,0x86,0x8e,0x28,0x20,0x8d,
+ 0x01,0x04,0x2d,0x9a,0xfc,0xa2,0x20,0x8d,
+ 0x01,0x04,0x2e,0x0d,0xd8,0xa9,0x20,0x8d,
0x01,0x04,0x2e,0x17,0x57,0xda,0x20,0x8d,
- 0x01,0x04,0x2e,0x20,0x32,0x62,0x20,0x8d,
- 0x01,0x04,0x2e,0x2f,0x54,0x55,0x20,0x8d,
+ 0x01,0x04,0x2e,0x28,0x7f,0xa4,0x20,0x8d,
0x01,0x04,0x2e,0x30,0x7e,0x3a,0x20,0x8d,
+ 0x01,0x04,0x2e,0x3b,0x0d,0x23,0x20,0x8d,
+ 0x01,0x04,0x2e,0x48,0xee,0x11,0x20,0x8d,
+ 0x01,0x04,0x2e,0x80,0x8d,0xb8,0x20,0x8d,
0x01,0x04,0x2e,0x92,0xf8,0x59,0x20,0x8d,
0x01,0x04,0x2e,0xa5,0xdd,0xd1,0x24,0x75,
0x01,0x04,0x2e,0xa6,0x8e,0x02,0x20,0x8d,
- 0x01,0x04,0x2e,0xa6,0xa2,0x2d,0x4e,0x21,
- 0x01,0x04,0x2e,0xad,0x32,0x3a,0x20,0x8d,
0x01,0x04,0x2e,0xaf,0xb2,0x03,0x20,0x8d,
- 0x01,0x04,0x2e,0xbc,0x1e,0x76,0x20,0x8d,
- 0x01,0x04,0x2e,0xdb,0x78,0x3b,0x0e,0x59,
- 0x01,0x04,0x2e,0xe5,0xee,0xbb,0x20,0x8d,
- 0x01,0x04,0x2f,0x5d,0xe6,0xab,0x20,0x8d,
- 0x01,0x04,0x2f,0x64,0xa2,0xd2,0x47,0x9c,
- 0x01,0x04,0x2f,0x90,0x6a,0xf9,0x20,0x8d,
- 0x01,0x04,0x2f,0xbc,0x46,0xcd,0x20,0x8d,
- 0x01,0x04,0x2f,0xe3,0xe2,0xf2,0x20,0x8d,
+ 0x01,0x04,0x2f,0x24,0x90,0x33,0x20,0x8d,
+ 0x01,0x04,0x2f,0xb4,0x31,0x9e,0x20,0x8d,
+ 0x01,0x04,0x31,0xe4,0x83,0x85,0x08,0xa2,
0x01,0x04,0x32,0x02,0x0d,0xa4,0x20,0x8d,
- 0x01,0x04,0x32,0x05,0x2e,0xc3,0x20,0x8d,
- 0x01,0x04,0x32,0x2d,0x80,0x1c,0x20,0x8d,
- 0x01,0x04,0x33,0x94,0x99,0x3c,0x20,0x8d,
+ 0x01,0x04,0x32,0x23,0x47,0x33,0x20,0x8d,
+ 0x01,0x04,0x32,0x35,0xfa,0xa2,0x20,0x8d,
+ 0x01,0x04,0x33,0x44,0x24,0x39,0x20,0x8d,
+ 0x01,0x04,0x33,0x8a,0x04,0x87,0x75,0x31,
0x01,0x04,0x33,0x9a,0x3e,0x67,0x20,0x8d,
- 0x01,0x04,0x33,0x9a,0x83,0x12,0x20,0x8d,
0x01,0x04,0x33,0x9e,0x96,0x9b,0x20,0x8d,
- 0x01,0x04,0x33,0x9f,0x02,0xda,0x20,0x8d,
- 0x01,0x04,0x36,0xc6,0x13,0x22,0x20,0x8d,
- 0x01,0x04,0x3a,0x69,0xa8,0x29,0x20,0x8d,
+ 0x01,0x04,0x36,0xb0,0x3f,0x10,0x20,0x8d,
0x01,0x04,0x3a,0x9e,0x00,0x56,0x20,0x8d,
- 0x01,0x04,0x3c,0xfb,0x81,0x3d,0x20,0x90,
+ 0x01,0x04,0x3b,0x8a,0x73,0x89,0x20,0x8d,
+ 0x01,0x04,0x3b,0xa7,0xbf,0x3c,0x20,0x8d,
+ 0x01,0x04,0x3c,0xcd,0xcd,0x77,0x20,0x8d,
+ 0x01,0x04,0x3c,0xea,0x7a,0xf5,0x20,0x8d,
+ 0x01,0x04,0x3c,0xf0,0xd2,0x9b,0x20,0x8d,
0x01,0x04,0x3d,0xef,0x5b,0xfa,0x20,0x8d,
- 0x01,0x04,0x3e,0x1c,0xbe,0xc2,0x20,0x8d,
- 0x01,0x04,0x3e,0x98,0x3a,0x10,0x24,0xcd,
+ 0x01,0x04,0x3e,0x4a,0x8f,0x0b,0x20,0x8d,
+ 0x01,0x04,0x3e,0x8a,0xa2,0x0c,0x20,0x8d,
+ 0x01,0x04,0x3e,0xa9,0x4a,0xe9,0x20,0x8d,
0x01,0x04,0x3e,0xab,0x81,0x20,0x20,0x8d,
- 0x01,0x04,0x3e,0xfb,0x36,0xa3,0x20,0x8d,
+ 0x01,0x04,0x3e,0xd1,0xc6,0x41,0x20,0x8d,
0x01,0x04,0x3f,0xf7,0x93,0xa6,0x20,0x8d,
- 0x01,0x04,0x40,0x21,0x44,0xb0,0x20,0x8d,
- 0x01,0x04,0x40,0x9c,0xc0,0x3d,0x20,0x8d,
- 0x01,0x04,0x40,0xbb,0xaf,0xe2,0x20,0x8d,
- 0x01,0x04,0x40,0xe9,0xf5,0x27,0x20,0x8d,
- 0x01,0x04,0x40,0xed,0x52,0x95,0x20,0x8d,
- 0x01,0x04,0x41,0x65,0xf7,0x1a,0x20,0x8d,
+ 0x01,0x04,0x40,0x62,0x4c,0x3e,0x20,0x8d,
0x01,0x04,0x42,0x1d,0x81,0xda,0x20,0x8d,
- 0x01,0x04,0x42,0x31,0xcc,0x0b,0x20,0x8d,
- 0x01,0x04,0x42,0x3a,0xf3,0xd7,0x20,0x8d,
- 0x01,0x04,0x42,0x55,0xea,0x81,0x20,0x8d,
+ 0x01,0x04,0x42,0x60,0xeb,0x1c,0x20,0x8d,
0x01,0x04,0x42,0x82,0x78,0x34,0x20,0x8d,
- 0x01,0x04,0x43,0x0a,0x79,0x91,0x20,0x8d,
- 0x01,0x04,0x43,0xd2,0xe4,0xcb,0x20,0x8d,
- 0x01,0x04,0x43,0xd5,0x57,0x15,0x20,0x8d,
+ 0x01,0x04,0x42,0xc6,0xd1,0xf3,0x20,0x8d,
+ 0x01,0x04,0x42,0xd0,0x40,0x80,0x20,0x8d,
+ 0x01,0x04,0x42,0xe1,0xe7,0x94,0x20,0x8d,
+ 0x01,0x04,0x43,0x37,0x03,0xc8,0x20,0x8d,
+ 0x01,0x04,0x43,0x3a,0xe8,0x6b,0x20,0x8d,
+ 0x01,0x04,0x43,0xd3,0x5c,0x02,0x20,0x8d,
+ 0x01,0x04,0x43,0xdf,0x77,0x7a,0x20,0x8d,
+ 0x01,0x04,0x44,0x30,0x83,0xfb,0x20,0x8d,
0x01,0x04,0x44,0xb5,0x04,0x0c,0x20,0x8d,
- 0x01,0x04,0x45,0x07,0x7c,0x92,0x20,0x8d,
- 0x01,0x04,0x45,0x08,0xaf,0xc9,0x20,0x8d,
+ 0x01,0x04,0x45,0x0e,0xb9,0x09,0x20,0x8d,
+ 0x01,0x04,0x45,0x36,0x1d,0xc1,0x20,0x8d,
0x01,0x04,0x45,0x3b,0x12,0x16,0x20,0x8d,
- 0x01,0x04,0x45,0x77,0xc1,0x09,0x20,0x8d,
- 0x01,0x04,0x45,0x82,0xc9,0x1b,0x20,0x8d,
0x01,0x04,0x45,0x83,0x65,0xb0,0x20,0x8d,
- 0x01,0x04,0x46,0x0f,0xc2,0x20,0x20,0x8d,
- 0x01,0x04,0x46,0x40,0x1b,0x0c,0x20,0x8d,
- 0x01,0x04,0x48,0x1d,0xaa,0x97,0x20,0x8d,
- 0x01,0x04,0x48,0x4a,0x22,0x63,0x20,0x8d,
- 0x01,0x04,0x48,0x85,0xb1,0x77,0x20,0x8d,
- 0x01,0x04,0x49,0xa6,0x54,0xde,0x20,0x8d,
- 0x01,0x04,0x4a,0x43,0xf0,0xcc,0x20,0x8d,
+ 0x01,0x04,0x45,0xa5,0xcd,0x8e,0x22,0x81,
+ 0x01,0x04,0x45,0xe4,0xdb,0x7c,0x20,0x8d,
+ 0x01,0x04,0x46,0x3b,0x7b,0x19,0x20,0x8d,
+ 0x01,0x04,0x46,0x3e,0x0d,0x96,0x20,0x8d,
+ 0x01,0x04,0x46,0x42,0xf8,0xaa,0x20,0x8d,
+ 0x01,0x04,0x46,0x70,0x99,0xe5,0x20,0x8d,
+ 0x01,0x04,0x46,0xa0,0xf0,0x84,0x20,0x8d,
+ 0x01,0x04,0x46,0xbe,0xb1,0xcc,0x20,0x8d,
+ 0x01,0x04,0x47,0x1c,0xbd,0xef,0x20,0x8d,
+ 0x01,0x04,0x47,0xea,0x7d,0xc6,0x20,0x8d,
+ 0x01,0x04,0x48,0x4a,0x7b,0xb3,0x20,0x8d,
+ 0x01,0x04,0x48,0xfd,0xec,0xd9,0x20,0x8d,
+ 0x01,0x04,0x49,0xdb,0xfe,0x78,0x20,0x8d,
0x01,0x04,0x4a,0x5b,0x73,0xe5,0x20,0x8d,
0x01,0x04,0x4a,0x76,0x89,0x77,0x20,0x8d,
- 0x01,0x04,0x4a,0xd5,0xfb,0xcb,0x20,0x8d,
+ 0x01,0x04,0x4a,0xc3,0xa6,0x64,0x20,0x8d,
0x01,0x04,0x4a,0xdc,0xff,0xbe,0x20,0x8d,
- 0x01,0x04,0x4c,0x0b,0x3c,0x9b,0x20,0x8d,
- 0x01,0x04,0x4c,0x42,0x90,0x7f,0x20,0x8d,
+ 0x01,0x04,0x4c,0x43,0xd3,0x6e,0x20,0x8d,
+ 0x01,0x04,0x4c,0xa9,0xa3,0x0e,0x20,0x8d,
+ 0x01,0x04,0x4d,0x20,0x79,0xa2,0x20,0x8d,
+ 0x01,0x04,0x4d,0x35,0x87,0x4a,0x20,0x8d,
0x01,0x04,0x4d,0x46,0x10,0xf5,0x20,0x8d,
0x01,0x04,0x4d,0x55,0xcc,0x95,0x20,0x8d,
- 0x01,0x04,0x4d,0x69,0x57,0x61,0x20,0x8d,
- 0x01,0x04,0x4d,0x78,0x71,0x45,0x20,0xf1,
- 0x01,0x04,0x4d,0x78,0x71,0x47,0x20,0xf1,
- 0x01,0x04,0x4d,0x78,0x7a,0x74,0x20,0xf1,
- 0x01,0x04,0x4d,0x78,0x7a,0x76,0x20,0xf1,
+ 0x01,0x04,0x4d,0x6b,0x26,0xef,0x20,0x8d,
+ 0x01,0x04,0x4d,0x78,0x1a,0x66,0x20,0x8d,
0x01,0x04,0x4d,0xa2,0xbe,0x5a,0x20,0x8d,
- 0x01,0x04,0x4d,0xa7,0xf5,0xef,0xd8,0xf8,
- 0x01,0x04,0x4d,0xe8,0x29,0xbd,0x20,0x8d,
0x01,0x04,0x4e,0x14,0xe3,0xf9,0x20,0x8d,
0x01,0x04,0x4e,0x15,0xa7,0x08,0x20,0x8d,
0x01,0x04,0x4e,0x1b,0x8b,0x0d,0x20,0x8d,
- 0x01,0x04,0x4e,0x2b,0xd0,0x19,0x20,0x8d,
- 0x01,0x04,0x4e,0x3f,0x1c,0x92,0x20,0x8d,
- 0x01,0x04,0x4e,0x48,0xe4,0xef,0x20,0x8d,
- 0x01,0x04,0x4e,0x6c,0x66,0x08,0x20,0x8d,
- 0x01,0x04,0x4e,0x81,0x00,0x27,0x20,0x8d,
- 0x01,0x04,0x4e,0x81,0xa9,0x45,0x20,0x8d,
- 0x01,0x04,0x4f,0x4d,0xb6,0xb4,0x20,0x8d,
+ 0x01,0x04,0x4e,0x5a,0x5b,0xdc,0x20,0x8d,
+ 0x01,0x04,0x4e,0x6c,0x6c,0x19,0x20,0x8d,
+ 0x01,0x04,0x4e,0x6c,0x6c,0x26,0x20,0x8d,
0x01,0x04,0x4f,0x4d,0xb6,0xb7,0x20,0x8d,
- 0x01,0x04,0x4f,0x6b,0xb2,0x3b,0x20,0x8d,
+ 0x01,0x04,0x4f,0x62,0x9f,0x07,0x2c,0x45,
+ 0x01,0x04,0x4f,0xbd,0xd3,0xc9,0x20,0x8d,
0x01,0x04,0x50,0x37,0xe1,0x9e,0x20,0x8d,
- 0x01,0x04,0x50,0x40,0xd3,0x66,0x20,0x8d,
- 0x01,0x04,0x50,0x40,0xd3,0x67,0x20,0x8d,
- 0x01,0x04,0x50,0x47,0x39,0x32,0x20,0x8d,
- 0x01,0x04,0x50,0x51,0x03,0x1b,0x20,0x8d,
- 0x01,0x04,0x50,0x52,0x37,0x2b,0x20,0x8d,
+ 0x01,0x04,0x50,0x53,0xba,0x23,0x20,0x8d,
0x01,0x04,0x50,0x58,0xac,0xe3,0xfb,0x08,
- 0x01,0x04,0x50,0x59,0xcb,0xac,0x1f,0x41,
- 0x01,0x04,0x50,0x5d,0xd5,0xf6,0x20,0x8d,
- 0x01,0x04,0x50,0x93,0x52,0xa5,0x20,0x8d,
+ 0x01,0x04,0x50,0xd1,0x57,0x67,0x24,0x75,
0x01,0x04,0x50,0xe5,0x1c,0x3c,0x20,0x8d,
- 0x01,0x04,0x50,0xf7,0xe9,0x28,0x20,0x8d,
- 0x01,0x04,0x50,0xff,0x08,0x5d,0x20,0x8d,
+ 0x01,0x04,0x51,0x07,0x10,0xb6,0x20,0x8d,
0x01,0x04,0x51,0x07,0x11,0xca,0x20,0x8d,
- 0x01,0x04,0x51,0x0a,0xf1,0xa5,0x20,0x8d,
- 0x01,0x04,0x51,0x15,0x56,0x9d,0x20,0x8d,
+ 0x01,0x04,0x51,0x13,0x0a,0x02,0x20,0x8d,
+ 0x01,0x04,0x51,0x58,0xdd,0xbe,0x20,0x8d,
0x01,0x04,0x51,0xab,0x16,0x8f,0x20,0x8d,
- 0x01,0x04,0x51,0xed,0xce,0xe0,0x20,0x97,
- 0x01,0x04,0x52,0x45,0x17,0xc3,0x20,0x8d,
+ 0x01,0x04,0x51,0xe0,0x2c,0xa4,0x20,0x8d,
+ 0x01,0x04,0x51,0xe0,0xa0,0x51,0x20,0x8d,
+ 0x01,0x04,0x52,0x01,0x44,0x36,0x20,0x8d,
+ 0x01,0x04,0x52,0x15,0xa4,0x2f,0x20,0x8d,
+ 0x01,0x04,0x52,0x40,0x74,0x05,0x20,0x8d,
+ 0x01,0x04,0x52,0x42,0x0a,0x0b,0x20,0x8d,
0x01,0x04,0x52,0x60,0x60,0x28,0x20,0x8d,
0x01,0x04,0x52,0x74,0x32,0x65,0x20,0x8d,
+ 0x01,0x04,0x52,0x81,0x44,0x3e,0x20,0x8d,
0x01,0x04,0x52,0x88,0x63,0x7a,0x20,0x8d,
- 0x01,0x04,0x52,0x95,0x61,0x19,0x44,0x9f,
0x01,0x04,0x52,0x9a,0x18,0xd1,0x20,0x8d,
- 0x01,0x04,0x52,0xa5,0xf1,0x32,0x20,0x8d,
- 0x01,0x04,0x52,0xc5,0xda,0xfd,0x20,0x8d,
- 0x01,0x04,0x52,0xca,0x44,0xe7,0x20,0x8d,
+ 0x01,0x04,0x52,0xc5,0xd7,0x7d,0x20,0x8d,
+ 0x01,0x04,0x53,0x80,0x84,0x5b,0x20,0x8d,
0x01,0x04,0x53,0x89,0x29,0x0a,0x20,0x8d,
0x01,0x04,0x53,0xd0,0x06,0xd3,0x20,0x8d,
- 0x01,0x04,0x53,0xd9,0x08,0x1f,0xad,0x84,
- 0x01,0x04,0x53,0xdc,0x6e,0x30,0x20,0x8d,
+ 0x01,0x04,0x53,0xd0,0xc1,0xf2,0x20,0x8d,
0x01,0x04,0x53,0xde,0x8a,0x55,0x20,0x8d,
+ 0x01,0x04,0x53,0xf0,0x7c,0x44,0x20,0x8d,
0x01,0x04,0x53,0xf3,0xbf,0xc7,0x20,0x8d,
- 0x01,0x04,0x54,0x16,0x8b,0x39,0x20,0x8d,
- 0x01,0x04,0x54,0x1b,0x9b,0x11,0x20,0x8d,
- 0x01,0x04,0x54,0x4b,0x1c,0xf7,0x20,0x8d,
+ 0x01,0x04,0x54,0x09,0x05,0xd3,0x20,0x8d,
+ 0x01,0x04,0x54,0x1c,0x39,0x5a,0x20,0x8d,
+ 0x01,0x04,0x54,0x26,0x03,0xf9,0x20,0x8d,
0x01,0x04,0x54,0x70,0x3c,0x10,0x20,0x8d,
- 0x01,0x04,0x54,0xd3,0x07,0x38,0x20,0x8d,
- 0x01,0x04,0x54,0xed,0x07,0xf9,0x20,0x8d,
- 0x01,0x04,0x55,0x17,0x33,0xb1,0x20,0x8d,
- 0x01,0x04,0x55,0x18,0x91,0xc6,0x20,0x8d,
- 0x01,0x04,0x55,0xb8,0x8a,0x6c,0x20,0x8d,
+ 0x01,0x04,0x54,0xd7,0x38,0x77,0x20,0x8d,
+ 0x01,0x04,0x54,0xe2,0xf3,0xaf,0x20,0x8d,
+ 0x01,0x04,0x54,0xf5,0x0e,0x49,0x20,0x8d,
+ 0x01,0x04,0x54,0xfc,0x9d,0x5a,0x47,0x9d,
+ 0x01,0x04,0x54,0xff,0xf4,0x3d,0x20,0x8d,
+ 0x01,0x04,0x55,0x17,0x18,0x7b,0x20,0x8d,
+ 0x01,0x04,0x55,0x34,0xb9,0x1d,0x21,0xda,
+ 0x01,0x04,0x55,0x3a,0x78,0xc9,0x20,0x8d,
+ 0x01,0x04,0x55,0x5d,0x60,0x12,0x20,0x8d,
+ 0x01,0x04,0x55,0xa5,0x08,0xc5,0x20,0x8d,
+ 0x01,0x04,0x55,0xad,0xa5,0x42,0x20,0x8d,
+ 0x01,0x04,0x55,0xb8,0x8f,0x69,0x20,0x8d,
+ 0x01,0x04,0x55,0xbf,0x4a,0x67,0x20,0x8d,
0x01,0x04,0x55,0xc2,0xee,0x86,0x20,0x8d,
0x01,0x04,0x55,0xc3,0x36,0x6e,0x20,0x8d,
+ 0x01,0x04,0x55,0xc3,0xc4,0x8e,0x20,0x8d,
+ 0x01,0x04,0x55,0xd0,0x45,0x0b,0x20,0x8d,
+ 0x01,0x04,0x55,0xd0,0x45,0x15,0x20,0x8d,
0x01,0x04,0x55,0xd0,0x47,0x24,0x20,0x8d,
0x01,0x04,0x55,0xd0,0x47,0x27,0x20,0x8d,
- 0x01,0x04,0x55,0xd6,0x88,0x2d,0x20,0x8d,
+ 0x01,0x04,0x55,0xd6,0x76,0x47,0x20,0x8d,
0x01,0x04,0x55,0xd6,0xa1,0xfc,0x20,0x8d,
- 0x01,0x04,0x55,0xe3,0xf5,0x80,0x20,0x8d,
- 0x01,0x04,0x56,0x12,0x22,0xf3,0x20,0x8d,
- 0x01,0x04,0x56,0x14,0x32,0xaa,0x20,0x8d,
- 0x01,0x04,0x56,0x31,0x69,0x5a,0x20,0x8d,
- 0x01,0x04,0x56,0x4c,0x07,0x84,0x20,0x8d,
+ 0x01,0x04,0x55,0xd8,0x20,0x49,0x20,0x8d,
+ 0x01,0x04,0x55,0xfe,0x62,0xdd,0x20,0x8d,
+ 0x01,0x04,0x56,0x3a,0x0b,0x98,0x20,0x8d,
+ 0x01,0x04,0x56,0x5f,0x08,0xf9,0x20,0x8d,
0x01,0x04,0x56,0x64,0x1a,0xbc,0x20,0x8d,
0x01,0x04,0x56,0x6a,0x8f,0x8f,0xd8,0x4d,
- 0x01,0x04,0x56,0x78,0x3a,0x42,0x20,0x8d,
+ 0x01,0x04,0x56,0x7c,0x91,0xb8,0x20,0x8d,
0x01,0x04,0x56,0x85,0xfb,0xef,0x22,0xc5,
- 0x01,0x04,0x56,0x95,0x08,0x17,0x22,0xc5,
- 0x01,0x04,0x57,0x4e,0xc5,0xea,0x20,0x8d,
+ 0x01,0x04,0x57,0x4f,0x5e,0xdd,0x20,0x8d,
0x01,0x04,0x57,0x78,0x08,0x05,0x4e,0x28,
- 0x01,0x04,0x57,0x79,0x25,0x9c,0x20,0x8d,
- 0x01,0x04,0x58,0x52,0xb5,0x2c,0x20,0x8d,
- 0x01,0x04,0x58,0x57,0x5d,0x34,0x06,0x9b,
- 0x01,0x04,0x58,0x62,0xeb,0x86,0x20,0x8d,
- 0x01,0x04,0x58,0x88,0xbb,0xd6,0x20,0x8d,
- 0x01,0x04,0x58,0x93,0xf4,0xfa,0x20,0x8d,
- 0x01,0x04,0x58,0x94,0x99,0x94,0x20,0x8d,
+ 0x01,0x04,0x57,0x7d,0x9d,0xdc,0x20,0x8d,
+ 0x01,0x04,0x58,0x09,0x4c,0x85,0x20,0x8d,
+ 0x01,0x04,0x58,0x5a,0xb8,0x44,0x20,0x8d,
+ 0x01,0x04,0x58,0x97,0x65,0x0e,0x13,0x88,
+ 0x01,0x04,0x58,0x97,0x65,0xfd,0x13,0x88,
+ 0x01,0x04,0x58,0xc6,0x5c,0x2f,0x20,0x8d,
+ 0x01,0x04,0x58,0xd0,0x73,0x46,0x20,0x8d,
+ 0x01,0x04,0x58,0xd2,0x0f,0x18,0x20,0x8d,
0x01,0x04,0x58,0xd4,0x2d,0xa6,0x20,0x8d,
- 0x01,0x04,0x58,0xd4,0x37,0x8a,0x20,0x8d,
- 0x01,0x04,0x59,0x26,0x60,0x99,0x24,0x39,
- 0x01,0x04,0x59,0x2f,0xa1,0x87,0x20,0x8d,
- 0x01,0x04,0x59,0x58,0x3e,0xbe,0x20,0x8d,
- 0x01,0x04,0x59,0x9e,0x20,0x2c,0x20,0x8d,
- 0x01,0x04,0x59,0xa3,0x91,0xf0,0x20,0x8d,
- 0x01,0x04,0x59,0xa3,0xf9,0xea,0x0e,0x59,
- 0x01,0x04,0x59,0xb0,0xc4,0x50,0x20,0x8d,
- 0x01,0x04,0x59,0xd8,0x15,0x60,0x20,0x8d,
- 0x01,0x04,0x5a,0x54,0xe3,0xff,0x20,0x8d,
+ 0x01,0x04,0x59,0x66,0xce,0xee,0x20,0x8d,
+ 0x01,0x04,0x59,0x67,0x6f,0x22,0x20,0x8d,
+ 0x01,0x04,0x59,0x72,0x8f,0x71,0x20,0x8d,
+ 0x01,0x04,0x59,0x86,0x3e,0x4a,0x20,0x8d,
+ 0x01,0x04,0x59,0x98,0x08,0xe7,0x20,0x8d,
+ 0x01,0x04,0x59,0xa1,0x1a,0x4e,0x20,0x8d,
+ 0x01,0x04,0x59,0xcf,0x83,0x13,0x20,0x8d,
+ 0x01,0x04,0x59,0xf8,0xc1,0xe5,0x20,0x8d,
+ 0x01,0x04,0x5a,0x03,0x30,0x3e,0x20,0x8d,
+ 0x01,0x04,0x5a,0x92,0x79,0x61,0x20,0x8d,
0x01,0x04,0x5a,0x92,0x82,0xd6,0x20,0x8d,
+ 0x01,0x04,0x5a,0xc4,0xa9,0x3a,0x20,0x8d,
0x01,0x04,0x5a,0xfa,0x09,0x01,0x20,0x8d,
0x01,0x04,0x5b,0x5d,0xc2,0x9a,0x20,0x8d,
- 0x01,0x04,0x5b,0x6a,0xbc,0xe5,0x20,0x8d,
0x01,0x04,0x5b,0x7e,0x28,0x6d,0x20,0x8d,
- 0x01,0x04,0x5b,0x89,0x7f,0x7b,0x20,0x8d,
- 0x01,0x04,0x5b,0x93,0xe8,0x62,0x20,0x8d,
- 0x01,0x04,0x5b,0x98,0x7b,0x12,0x20,0x8d,
- 0x01,0x04,0x5b,0xb2,0x11,0x78,0x20,0x8d,
0x01,0x04,0x5b,0xcc,0x63,0xb2,0x20,0x8d,
- 0x01,0x04,0x5b,0xdf,0xaf,0x0e,0x20,0x8d,
- 0x01,0x04,0x5c,0x2a,0x6e,0xf2,0x20,0x8d,
- 0x01,0x04,0x5c,0x35,0x5a,0x54,0x20,0x8d,
- 0x01,0x04,0x5c,0xdd,0x9b,0xe4,0x20,0x8d,
+ 0x01,0x04,0x5b,0xcc,0x95,0x05,0x20,0x8d,
+ 0x01,0x04,0x5b,0xce,0x11,0xc3,0x20,0x8d,
+ 0x01,0x04,0x5b,0xd1,0x33,0x83,0x20,0x8d,
+ 0x01,0x04,0x5b,0xd7,0x5b,0xfe,0x20,0x8d,
+ 0x01,0x04,0x5c,0x5b,0x1b,0x3c,0x20,0x8d,
+ 0x01,0x04,0x5c,0xdd,0x14,0xe8,0x20,0x8d,
+ 0x01,0x04,0x5c,0xff,0x55,0x1f,0x20,0x8d,
+ 0x01,0x04,0x5d,0x04,0x65,0x25,0x20,0x8d,
+ 0x01,0x04,0x5d,0x2e,0x51,0x05,0x20,0x8d,
0x01,0x04,0x5d,0x39,0x51,0xa2,0x20,0x8d,
+ 0x01,0x04,0x5d,0x49,0x27,0xc4,0x20,0x8d,
+ 0x01,0x04,0x5d,0x5a,0x52,0xe2,0x20,0x8d,
0x01,0x04,0x5d,0x5f,0x58,0x0d,0x20,0x8d,
- 0x01,0x04,0x5d,0x67,0x0d,0x01,0x20,0x8d,
0x01,0x04,0x5d,0x7b,0xb4,0xa4,0x20,0x8d,
- 0x01,0x04,0x5d,0xbe,0x75,0x1a,0x20,0x8d,
- 0x01,0x04,0x5e,0x69,0x7d,0xf0,0x20,0x8d,
- 0x01,0x04,0x5e,0x6e,0x17,0xd7,0x20,0x8d,
+ 0x01,0x04,0x5d,0xbd,0x91,0xa9,0x20,0x8d,
+ 0x01,0x04,0x5e,0x11,0xb9,0x6b,0x20,0x8d,
+ 0x01,0x04,0x5e,0x4b,0xc6,0x78,0x20,0x8d,
+ 0x01,0x04,0x5e,0x72,0xc4,0xa9,0x20,0x8d,
+ 0x01,0x04,0x5e,0x8e,0xd5,0xfa,0xd8,0xf8,
0x01,0x04,0x5e,0x9a,0x9f,0x63,0x20,0x8d,
- 0x01,0x04,0x5e,0xbd,0xa1,0x77,0x20,0x8d,
- 0x01,0x04,0x5e,0xcb,0xff,0x46,0x20,0x8d,
- 0x01,0x04,0x5e,0xe8,0xad,0x5d,0x20,0x8d,
- 0x01,0x04,0x5f,0x4f,0x7a,0x63,0x20,0x8d,
- 0x01,0x04,0x5f,0x50,0x01,0x6e,0x20,0x8d,
- 0x01,0x04,0x5f,0x53,0x49,0x1f,0x20,0x8d,
+ 0x01,0x04,0x5e,0x9e,0xf6,0xb7,0x20,0x8d,
+ 0x01,0x04,0x5e,0xef,0x91,0x20,0x20,0x8d,
+ 0x01,0x04,0x5f,0x1f,0x0c,0x16,0x20,0x8d,
+ 0x01,0x04,0x5f,0x1f,0xc4,0x0f,0x20,0x8d,
0x01,0x04,0x5f,0x6e,0x85,0xdf,0x20,0x8d,
0x01,0x04,0x5f,0x6e,0xea,0x5d,0x20,0x8d,
- 0x01,0x04,0x5f,0xa4,0x41,0xc2,0x20,0x8d,
- 0x01,0x04,0x5f,0xa5,0x08,0xb6,0x20,0x8d,
- 0x01,0x04,0x5f,0xae,0xdb,0x65,0x20,0x8d,
+ 0x01,0x04,0x5f,0xa1,0x0c,0x2d,0x20,0x8d,
0x01,0x04,0x5f,0xbf,0x82,0x64,0x20,0x8d,
+ 0x01,0x04,0x5f,0xd0,0x9e,0xa1,0x20,0x8d,
+ 0x01,0x04,0x5f,0xd5,0x91,0xda,0x20,0x8d,
0x01,0x04,0x5f,0xd6,0x35,0x9a,0x20,0x8d,
- 0x01,0x04,0x5f,0xd7,0xcd,0xb4,0x20,0x8d,
- 0x01,0x04,0x60,0x2b,0x82,0xea,0x20,0x8d,
- 0x01,0x04,0x62,0x19,0xc9,0x1f,0x20,0x8d,
- 0x01,0x04,0x62,0x80,0xf7,0xb6,0x20,0x8d,
- 0x01,0x04,0x62,0xab,0x15,0x81,0x20,0x8d,
- 0x01,0x04,0x63,0x93,0x87,0xa1,0x20,0x8d,
- 0x01,0x04,0x65,0x64,0xa3,0x76,0x20,0x87,
- 0x01,0x04,0x66,0x84,0xf5,0x10,0x20,0x8d,
- 0x01,0x04,0x66,0xb6,0xcc,0x60,0x20,0x8d,
- 0x01,0x04,0x66,0xb6,0xeb,0xf5,0x20,0x8d,
+ 0x01,0x04,0x5f,0xd6,0x35,0xa0,0x20,0x8d,
+ 0x01,0x04,0x60,0x2c,0x9c,0xc7,0x20,0x8d,
+ 0x01,0x04,0x61,0x4b,0x91,0x0c,0x20,0x8d,
+ 0x01,0x04,0x66,0x84,0xc0,0x8d,0x20,0x8d,
0x01,0x04,0x67,0x0e,0xf5,0xfa,0x20,0x8d,
- 0x01,0x04,0x67,0x2f,0xc0,0x0f,0x20,0x8d,
- 0x01,0x04,0x67,0x54,0x54,0xfa,0x20,0x8f,
- 0x01,0x04,0x67,0x63,0xa8,0x82,0x20,0x8d,
+ 0x01,0x04,0x67,0x55,0x26,0xcd,0x20,0x8d,
+ 0x01,0x04,0x67,0x58,0x5c,0x4e,0x20,0x8c,
+ 0x01,0x04,0x67,0x63,0xa8,0x64,0x20,0x8d,
0x01,0x04,0x67,0x63,0xa8,0x8c,0x20,0x8d,
- 0x01,0x04,0x67,0xc6,0xc0,0x0e,0x4e,0x28,
- 0x01,0x04,0x67,0xe8,0x68,0xe3,0x20,0x8d,
- 0x01,0x04,0x68,0x8f,0x02,0xc3,0x20,0x8d,
- 0x01,0x04,0x68,0xac,0xeb,0xe3,0x20,0x8d,
+ 0x01,0x04,0x67,0x63,0xaa,0xd2,0x20,0x8d,
+ 0x01,0x04,0x67,0x63,0xaa,0xdc,0x20,0x8d,
+ 0x01,0x04,0x67,0x64,0x2c,0x46,0x20,0x8d,
+ 0x01,0x04,0x67,0xb2,0xec,0x1b,0x20,0x8d,
+ 0x01,0x04,0x67,0xd1,0x0c,0x90,0x20,0x8d,
+ 0x01,0x04,0x68,0x3b,0x93,0x0f,0x20,0x8d,
+ 0x01,0x04,0x68,0x81,0xab,0x79,0x20,0x8d,
+ 0x01,0x04,0x68,0xc8,0x41,0xea,0x20,0x8d,
0x01,0x04,0x68,0xee,0xdc,0xc7,0x20,0x8d,
- 0x01,0x04,0x6b,0x0b,0x73,0x44,0x20,0x8d,
+ 0x01,0x04,0x68,0xf4,0x49,0x06,0x20,0x8d,
+ 0x01,0x04,0x6a,0x47,0x77,0xe6,0x20,0x8d,
0x01,0x04,0x6b,0xad,0xa6,0x2b,0x20,0x8d,
- 0x01,0x04,0x6c,0x04,0xd4,0x53,0x20,0x8d,
- 0x01,0x04,0x6d,0x88,0x49,0x61,0x20,0x8d,
- 0x01,0x04,0x6d,0xad,0x62,0x17,0x20,0x8d,
- 0x01,0x04,0x6d,0xbe,0x44,0x74,0x20,0x8d,
- 0x01,0x04,0x6d,0xeb,0xf6,0x3c,0x20,0x8d,
+ 0x01,0x04,0x6c,0xa1,0x16,0x4e,0x20,0x8d,
+ 0x01,0x04,0x6c,0xae,0x3f,0xea,0x20,0x8d,
+ 0x01,0x04,0x6d,0x63,0x3f,0x9f,0x20,0x8d,
+ 0x01,0x04,0x6d,0x69,0x28,0xf7,0x20,0x8d,
+ 0x01,0x04,0x6d,0x6b,0xb9,0x82,0x20,0x8d,
+ 0x01,0x04,0x6d,0x6e,0xef,0x04,0x20,0x8d,
+ 0x01,0x04,0x6d,0xad,0x29,0x2b,0x20,0x8d,
+ 0x01,0x04,0x6d,0xec,0x5a,0x75,0x20,0x8d,
0x01,0x04,0x6d,0xf8,0xce,0x0d,0x20,0x8d,
- 0x01,0x04,0x6e,0x0c,0x40,0x60,0x20,0x8d,
+ 0x01,0x04,0x6d,0xff,0x6a,0xce,0x20,0x8d,
+ 0x01,0x04,0x6f,0x5a,0x8c,0x17,0x20,0x8d,
0x01,0x04,0x6f,0x5a,0x8c,0x2e,0x20,0x8d,
- 0x01,0x04,0x6f,0x5a,0x9f,0xb8,0xc3,0x51,
- 0x01,0x04,0x71,0x6b,0xc9,0x83,0x20,0x8d,
+ 0x01,0x04,0x6f,0x5a,0x9f,0xf6,0x20,0x8d,
+ 0x01,0x04,0x70,0x76,0xbc,0x32,0x20,0x8d,
0x01,0x04,0x73,0x2f,0x8d,0xfa,0x22,0xb5,
0x01,0x04,0x74,0x3a,0xab,0x43,0x20,0x8d,
- 0x01,0x04,0x74,0x57,0x39,0xda,0x20,0x8d,
- 0x01,0x04,0x74,0xca,0xa1,0x38,0x20,0x8d,
- 0x01,0x04,0x75,0x33,0x9f,0x82,0x20,0x8d,
- 0x01,0x04,0x76,0x67,0x7e,0x8c,0x6e,0xad,
- 0x01,0x04,0x79,0x2d,0xbe,0xd2,0x20,0x8d,
- 0x01,0x04,0x79,0x63,0xc1,0x19,0x20,0x8d,
- 0x01,0x04,0x7a,0x70,0x94,0x99,0x20,0x93,
- 0x01,0x04,0x7a,0x94,0x87,0xea,0x20,0x8d,
+ 0x01,0x04,0x76,0x5c,0x6b,0x6c,0x20,0x8d,
+ 0x01,0x04,0x77,0x2a,0x37,0xcb,0x20,0x8d,
+ 0x01,0x04,0x78,0x4f,0x47,0x48,0x20,0x8d,
+ 0x01,0x04,0x79,0x63,0xf0,0x57,0x20,0x8d,
+ 0x01,0x04,0x7b,0x3c,0xd5,0xc0,0x20,0x8d,
+ 0x01,0x04,0x7c,0x9c,0x9e,0x64,0x20,0x8d,
+ 0x01,0x04,0x7c,0xde,0x7b,0xee,0x20,0x8d,
+ 0x01,0x04,0x7d,0xb2,0x06,0x74,0x20,0x8d,
0x01,0x04,0x80,0x00,0xbe,0x1a,0x20,0x8d,
0x01,0x04,0x80,0x41,0xc2,0x88,0x20,0x8d,
+ 0x01,0x04,0x81,0x0d,0xbd,0xd4,0x20,0x8d,
0x01,0x04,0x81,0x7e,0xac,0x73,0x20,0x8d,
- 0x01,0x04,0x81,0xe2,0x7d,0x0a,0x20,0x8d,
+ 0x01,0x04,0x81,0x92,0x34,0xae,0x20,0x8d,
+ 0x01,0x04,0x82,0x2c,0xa8,0xca,0x20,0x8d,
+ 0x01,0x04,0x83,0xa1,0x50,0xa6,0x20,0x8d,
0x01,0x04,0x83,0xbc,0x28,0xbf,0x20,0x8d,
0x01,0x04,0x86,0xc3,0xb9,0x34,0x20,0x8d,
- 0x01,0x04,0x87,0xb4,0x2c,0x3d,0x20,0x8d,
- 0x01,0x04,0x88,0x34,0x72,0x7b,0x20,0x8d,
+ 0x01,0x04,0x87,0x86,0xee,0x2f,0x20,0x8d,
+ 0x01,0x04,0x87,0xb4,0xda,0x3a,0x20,0x8d,
+ 0x01,0x04,0x87,0xb5,0xd7,0xed,0x20,0x8d,
+ 0x01,0x04,0x88,0x1d,0x6d,0xb4,0x20,0x8d,
+ 0x01,0x04,0x88,0x20,0xee,0x06,0x20,0x8d,
0x01,0x04,0x88,0x38,0xaa,0x60,0x20,0x8d,
- 0x01,0x04,0x89,0x74,0xd5,0x8f,0x20,0x8d,
+ 0x01,0x04,0x89,0x19,0x26,0x6c,0x20,0x8d,
0x01,0x04,0x89,0xe2,0x22,0x2e,0x20,0x8d,
- 0x01,0x04,0x8a,0x2b,0xe9,0x39,0x20,0x8d,
+ 0x01,0x04,0x8a,0xcf,0xd3,0x6a,0x20,0x8d,
0x01,0x04,0x8b,0x82,0x29,0x52,0x20,0x8d,
+ 0x01,0x04,0x8b,0x99,0xff,0x6b,0x20,0x8d,
0x01,0x04,0x8c,0xbe,0x0c,0x81,0x20,0x8d,
- 0x01,0x04,0x8e,0x04,0x69,0x4d,0x20,0x8d,
0x01,0x04,0x8e,0x36,0xb5,0xda,0x20,0x8d,
- 0x01,0x04,0x8f,0xb1,0xe7,0xf7,0x20,0x8d,
+ 0x01,0x04,0x8f,0xb1,0xe5,0x95,0x20,0x8d,
0x01,0x04,0x8f,0xb2,0x40,0x0a,0x20,0x8d,
- 0x01,0x04,0x90,0x22,0xa1,0x41,0x47,0x9d,
- 0x01,0x04,0x92,0x04,0x7c,0x86,0x20,0x8d,
+ 0x01,0x04,0x90,0x18,0xf5,0xb7,0x20,0x8d,
+ 0x01,0x04,0x90,0x7e,0x82,0xb2,0x20,0x8d,
+ 0x01,0x04,0x92,0x04,0x7c,0x81,0x20,0x8d,
+ 0x01,0x04,0x92,0x47,0x45,0x67,0x20,0x8d,
0x01,0x04,0x92,0x53,0x38,0x45,0x20,0x8d,
- 0x01,0x04,0x92,0x5a,0xc1,0x44,0x20,0x8d,
- 0x01,0x04,0x92,0xc4,0x37,0x9c,0x70,0xa1,
- 0x01,0x04,0x94,0x42,0x32,0x32,0x20,0x8f,
- 0x01,0x04,0x94,0xfb,0x01,0x14,0x20,0x97,
- 0x01,0x04,0x97,0x30,0x5f,0xd4,0x20,0x8d,
+ 0x01,0x04,0x93,0xc2,0xb1,0xa5,0x20,0x8d,
+ 0x01,0x04,0x95,0x5a,0xd6,0x4e,0x20,0x8d,
+ 0x01,0x04,0x95,0x66,0x9d,0x9c,0x20,0x8d,
+ 0x01,0x04,0x97,0xf8,0x9c,0x37,0x20,0x8d,
0x01,0x04,0x97,0xfc,0xc1,0xf5,0x20,0x8d,
- 0x01,0x04,0x98,0x2c,0x89,0x53,0x20,0x8d,
- 0x01,0x04,0x98,0x73,0xbf,0xc4,0x20,0x8d,
- 0x01,0x04,0x9a,0xdd,0x1f,0x56,0x20,0x8d,
+ 0x01,0x04,0x99,0x5c,0x5d,0x72,0x20,0x8d,
+ 0x01,0x04,0x9a,0xd3,0x06,0x02,0x20,0x8d,
0x01,0x04,0x9c,0x11,0x67,0x02,0x1f,0x98,
- 0x01,0x04,0x9d,0x8a,0x14,0x16,0x20,0x8d,
+ 0x01,0x04,0x9c,0x92,0xb1,0xdd,0x20,0x8d,
+ 0x01,0x04,0x9d,0x83,0x8f,0xad,0x20,0x8d,
0x01,0x04,0x9e,0x3a,0xbc,0x25,0x20,0x8d,
- 0x01,0x04,0x9e,0x8c,0xd1,0x4f,0x20,0x8d,
+ 0x01,0x04,0x9e,0xf8,0x27,0xef,0x20,0x8d,
0x01,0x04,0x9f,0x59,0xe6,0x80,0x20,0x8d,
- 0x01,0x04,0x9f,0xf6,0x19,0x34,0x20,0x8d,
- 0x01,0x04,0xa0,0x14,0x3b,0xfa,0x20,0xf1,
- 0x01,0x04,0xa2,0x00,0xea,0xbe,0x20,0x8d,
- 0x01,0x04,0xa2,0x3e,0x1a,0xda,0x20,0x8d,
- 0x01,0x04,0xa2,0xfa,0xbc,0xc2,0x20,0x8d,
- 0x01,0x04,0xa2,0xfb,0x46,0x52,0x20,0x8d,
- 0x01,0x04,0xa3,0x9e,0xce,0xff,0x20,0x8d,
- 0x01,0x04,0xa4,0x44,0x69,0x69,0x20,0x8d,
+ 0x01,0x04,0x9f,0xc4,0x03,0xef,0x20,0x8d,
+ 0x01,0x04,0x9f,0xe0,0xbd,0xfa,0x20,0x8d,
+ 0x01,0x04,0xa0,0x48,0x33,0x9a,0x20,0x8d,
+ 0x01,0x04,0xa1,0x1d,0xec,0x37,0x20,0x8d,
+ 0x01,0x04,0xa1,0x61,0x77,0xa6,0x20,0x8d,
+ 0x01,0x04,0xa1,0xf6,0x0b,0xe6,0x20,0x8d,
+ 0x01,0x04,0xa2,0x3e,0x12,0xe2,0x20,0x8d,
+ 0x01,0x04,0xa2,0xfa,0x7b,0xb3,0x20,0x8d,
+ 0x01,0x04,0xa2,0xfa,0xbf,0xde,0x20,0x8d,
+ 0x01,0x04,0xa2,0xfe,0x76,0x14,0x20,0x8d,
+ 0x01,0x04,0xa3,0xac,0x51,0x46,0x20,0x8d,
+ 0x01,0x04,0xa4,0x5a,0x2f,0x08,0x20,0x8d,
0x01,0x04,0xa5,0xe4,0xae,0x75,0x20,0x8d,
- 0x01,0x04,0xa6,0x3e,0x52,0x67,0x80,0x03,
- 0x01,0x04,0xa6,0x46,0x31,0x1a,0x20,0x8d,
- 0x01,0x04,0xa6,0x4e,0xf1,0x09,0x20,0x8d,
- 0x01,0x04,0xa6,0x4e,0xf1,0x19,0x20,0x8d,
- 0x01,0x04,0xa7,0x47,0x49,0xf4,0x20,0x8d,
- 0x01,0x04,0xa7,0xb3,0x93,0x9b,0x20,0x8d,
+ 0x01,0x04,0xa6,0x46,0x91,0x97,0x20,0x8d,
0x01,0x04,0xa8,0x5b,0xee,0x08,0x20,0x8d,
+ 0x01,0x04,0xaa,0xfd,0x0b,0x19,0x20,0x8d,
+ 0x01,0x04,0xab,0x67,0xaa,0x73,0x20,0x8d,
+ 0x01,0x04,0xac,0x5d,0xa6,0x87,0x20,0x8d,
+ 0x01,0x04,0xac,0x67,0xd9,0xec,0x20,0x8d,
0x01,0x04,0xac,0x69,0x15,0xd8,0x20,0x8d,
- 0x01,0x04,0xac,0x75,0x69,0x5f,0x20,0x8d,
- 0x01,0x04,0xad,0x17,0x67,0x1e,0x1f,0x40,
- 0x01,0x04,0xad,0xcd,0x5c,0x97,0xd6,0x15,
- 0x01,0x04,0xad,0xcd,0x5c,0x9a,0xd6,0x15,
- 0x01,0x04,0xad,0xcd,0x5c,0x9d,0xd6,0x15,
+ 0x01,0x04,0xac,0x70,0x99,0x5f,0x20,0x8d,
+ 0x01,0x04,0xad,0x03,0xda,0x5b,0x20,0x8d,
+ 0x01,0x04,0xad,0x0c,0x77,0x85,0x20,0x8d,
+ 0x01,0x04,0xad,0x22,0x7f,0xb5,0x20,0x8d,
+ 0x01,0x04,0xad,0x4c,0x7b,0xad,0x20,0x8d,
+ 0x01,0x04,0xad,0xb0,0xc6,0x44,0x20,0x8d,
0x01,0x04,0xad,0xd0,0x98,0xda,0x20,0x8d,
0x01,0x04,0xad,0xf1,0xe3,0xf3,0x20,0x8d,
- 0x01,0x04,0xae,0x03,0x04,0xe8,0x20,0x8d,
- 0x01,0x04,0xae,0x11,0x0b,0x16,0x20,0x8d,
- 0x01,0x04,0xae,0x58,0xf1,0xa7,0x20,0x8d,
- 0x01,0x04,0xae,0x72,0x66,0x29,0x20,0x8d,
+ 0x01,0x04,0xad,0xf6,0x1b,0x07,0x20,0x8d,
+ 0x01,0x04,0xad,0xff,0xf0,0xcd,0x20,0x8d,
+ 0x01,0x04,0xae,0x1e,0x2f,0x0f,0x20,0x8d,
0x01,0x04,0xae,0x72,0xfa,0x56,0x20,0x8d,
+ 0x01,0x04,0xae,0x8a,0x23,0xe5,0x20,0x8d,
0x01,0x04,0xae,0x8e,0xbf,0x88,0x20,0x8d,
- 0x01,0x04,0xaf,0x27,0x48,0x57,0x20,0x8d,
- 0x01,0x04,0xb0,0x0c,0x10,0x87,0x20,0x8d,
- 0x01,0x04,0xb0,0x25,0x17,0x1e,0x20,0x8d,
- 0x01,0x04,0xb0,0x3e,0xb3,0xdd,0x20,0x8d,
+ 0x01,0x04,0xb0,0x0a,0x8f,0xbe,0x20,0x8d,
0x01,0x04,0xb0,0x4a,0x88,0xed,0x20,0x8d,
- 0x01,0x04,0xb0,0x63,0x06,0xe2,0x20,0x8d,
+ 0x01,0x04,0xb0,0x76,0xdc,0x1d,0x20,0x8d,
+ 0x01,0x04,0xb0,0x7e,0x74,0x07,0x20,0x8d,
+ 0x01,0x04,0xb0,0x7e,0xa7,0x0a,0x20,0x8d,
0x01,0x04,0xb0,0xd4,0xb9,0x99,0x20,0x8d,
+ 0x01,0x04,0xb0,0xeb,0xd1,0xba,0x20,0x8d,
0x01,0x04,0xb1,0x51,0xec,0x75,0x20,0x8d,
- 0x01,0x04,0xb2,0x13,0x6a,0x1a,0x20,0x8d,
- 0x01,0x04,0xb2,0x15,0x76,0xb2,0x20,0x8d,
- 0x01,0x04,0xb2,0x21,0xe8,0x45,0x20,0x8d,
- 0x01,0x04,0xb2,0x4f,0x54,0x8b,0x20,0x8d,
+ 0x01,0x04,0xb1,0x59,0xcd,0x46,0x20,0x8d,
+ 0x01,0x04,0xb2,0x30,0xa8,0x0c,0x20,0x8d,
0x01,0x04,0xb2,0x7c,0xa2,0xd1,0x20,0x8d,
- 0x01,0x04,0xb2,0x84,0x02,0xf6,0x20,0x8d,
- 0x01,0x04,0xb2,0x96,0x60,0x2e,0x20,0x8d,
- 0x01,0x04,0xb2,0xa2,0xd4,0x2c,0x20,0x8d,
- 0x01,0x04,0xb2,0xc1,0xe2,0x78,0x20,0x8d,
+ 0x01,0x04,0xb2,0x9f,0x62,0x85,0x20,0x8d,
+ 0x01,0x04,0xb2,0xc4,0x59,0xd1,0x20,0x8d,
0x01,0x04,0xb2,0xec,0x89,0x3f,0x20,0x8d,
+ 0x01,0x04,0xb2,0xfc,0x7b,0x18,0x20,0x8d,
+ 0x01,0x04,0xb3,0x2b,0xaa,0xba,0x20,0x8d,
0x01,0x04,0xb4,0x96,0x2e,0xbb,0x20,0x8d,
- 0x01,0x04,0xb5,0xa4,0xd2,0xe4,0x21,0x52,
- 0x01,0x04,0xb7,0x6e,0xdc,0xd2,0x76,0x5d,
- 0x01,0x04,0xb8,0x5f,0x3a,0xa6,0x20,0x90,
- 0x01,0x04,0xb8,0xa4,0x93,0x52,0xa1,0x75,
- 0x01,0x04,0xb8,0xab,0xd0,0x6d,0x20,0x8d,
- 0x01,0x04,0xb9,0x11,0x8f,0xdc,0x20,0x8d,
- 0x01,0x04,0xb9,0x15,0xd9,0x31,0x20,0x8d,
+ 0x01,0x04,0xb5,0x75,0x80,0x8c,0x20,0x8d,
+ 0x01,0x04,0xb8,0x13,0x13,0x10,0x20,0x8d,
+ 0x01,0x04,0xb9,0x15,0xd9,0x30,0x20,0x8d,
0x01,0x04,0xb9,0x19,0x30,0xb8,0x20,0x8d,
- 0x01,0x04,0xb9,0x1c,0x60,0x10,0x20,0x8d,
0x01,0x04,0xb9,0x1f,0x88,0xf6,0x20,0x8d,
+ 0x01,0x04,0xb9,0x34,0x5d,0x2d,0x20,0x8d,
0x01,0x04,0xb9,0x40,0x74,0x0f,0x20,0x8d,
0x01,0x04,0xb9,0x44,0xf9,0x5b,0x20,0x8d,
- 0x01,0x04,0xb9,0x6c,0xf7,0xbe,0x20,0x8d,
- 0x01,0x04,0xb9,0x8d,0x3c,0x24,0x20,0x8d,
- 0x01,0x04,0xb9,0x94,0x03,0xe3,0x20,0x8d,
+ 0x01,0x04,0xb9,0x62,0x36,0x14,0x20,0x8d,
+ 0x01,0x04,0xb9,0x6b,0x53,0x37,0x20,0x8d,
+ 0x01,0x04,0xb9,0x8c,0xfd,0xa9,0x20,0x8d,
0x01,0x04,0xb9,0x94,0x91,0x4a,0x20,0x8d,
- 0x01,0x04,0xb9,0x9f,0x14,0x8f,0x20,0x8d,
+ 0x01,0x04,0xb9,0xa5,0xaa,0x13,0x20,0x8d,
0x01,0x04,0xb9,0xa7,0x71,0x3b,0x20,0x8d,
0x01,0x04,0xb9,0xb9,0x1a,0x8d,0x1f,0xaf,
- 0x01,0x04,0xb9,0xbd,0x84,0xb2,0xe1,0xb4,
- 0x01,0x04,0xb9,0xcc,0xc5,0x70,0x20,0x8d,
+ 0x01,0x04,0xb9,0xc5,0xa3,0x88,0x20,0x8d,
+ 0x01,0x04,0xb9,0xd1,0x0c,0x4c,0x20,0x8d,
0x01,0x04,0xb9,0xd1,0x46,0x11,0x20,0x8d,
- 0x01,0x04,0xb9,0xdc,0x9c,0xc1,0x20,0x8d,
- 0x01,0x04,0xb9,0xee,0x81,0x71,0x20,0x8d,
+ 0x01,0x04,0xb9,0xe3,0x9c,0xe2,0x20,0x8d,
+ 0x01,0x04,0xb9,0xe9,0xbd,0xd2,0x20,0x8d,
0x01,0x04,0xb9,0xef,0xdd,0x05,0x20,0x8d,
- 0x01,0x04,0xb9,0xf4,0xd9,0x27,0x20,0x8d,
+ 0x01,0x04,0xb9,0xf4,0x64,0x6a,0x20,0x8d,
0x01,0x04,0xb9,0xfe,0x61,0xa4,0x20,0x8d,
0x01,0x04,0xba,0x21,0xa7,0x0b,0x20,0x8d,
+ 0x01,0x04,0xba,0xb0,0x62,0x25,0x20,0x8d,
+ 0x01,0x04,0xba,0xf9,0xd9,0x19,0x20,0x8d,
+ 0x01,0x04,0xba,0xfa,0x5f,0x84,0x20,0x8d,
0x01,0x04,0xbc,0x20,0x0e,0x1f,0x20,0x8e,
- 0x01,0x04,0xbc,0x2a,0x28,0xea,0x47,0x9d,
- 0x01,0x04,0xbc,0x86,0x08,0x24,0x20,0x8d,
+ 0x01,0x04,0xbc,0x23,0xa7,0x0e,0x20,0x8d,
+ 0x01,0x04,0xbc,0x44,0x2d,0x8f,0x20,0x8d,
+ 0x01,0x04,0xbc,0x75,0xc8,0xd4,0x20,0x8d,
0x01,0x04,0xbc,0x8a,0x58,0x0e,0x20,0x8d,
- 0x01,0x04,0xbc,0x9c,0x6e,0xef,0x20,0x8d,
- 0x01,0x04,0xbc,0xa5,0xf4,0x8f,0x20,0x8d,
- 0x01,0x04,0xbc,0xd5,0x44,0x26,0x20,0x8d,
- 0x01,0x04,0xbc,0xd6,0x81,0x41,0x4e,0x2c,
- 0x01,0x04,0xbc,0xf2,0x0f,0x4a,0x20,0x8d,
- 0x01,0x04,0xbc,0xf4,0x04,0x4e,0x20,0x8d,
- 0x01,0x04,0xbd,0x27,0x06,0x52,0x20,0x8d,
- 0x01,0x04,0xbd,0xcf,0x2e,0x20,0x20,0x8d,
- 0x01,0x04,0xbd,0xd4,0x79,0x4a,0x20,0x8d,
- 0x01,0x04,0xc0,0x03,0x0b,0x14,0x20,0x8d,
- 0x01,0x04,0xc0,0x41,0xaa,0x0f,0x20,0x8d,
+ 0x01,0x04,0xbc,0x97,0xed,0x9e,0x20,0x8d,
+ 0x01,0x04,0xbc,0x9a,0xec,0x31,0x20,0x8d,
+ 0x01,0x04,0xbd,0x7b,0xb1,0x80,0x20,0x8d,
+ 0x01,0x04,0xbe,0x7b,0x1b,0x0b,0x20,0x8d,
+ 0x01,0x04,0xbe,0x91,0x7f,0xfe,0x20,0x8d,
+ 0x01,0x04,0xc0,0x45,0x35,0x4d,0x20,0x8d,
0x01,0x04,0xc0,0x92,0x89,0x2c,0x20,0x8d,
- 0x01,0x04,0xc0,0xb6,0x9d,0x77,0x20,0x8d,
- 0x01,0x04,0xc0,0xbb,0x6d,0x8d,0x20,0x8d,
- 0x01,0x04,0xc0,0xe3,0x50,0x53,0x20,0x8d,
- 0x01,0x04,0xc1,0x0a,0xcb,0x17,0x20,0x8e,
- 0x01,0x04,0xc1,0x20,0x7f,0xa0,0xe4,0x6d,
- 0x01,0x04,0xc1,0x20,0x7f,0xa2,0xe4,0x6d,
- 0x01,0x04,0xc1,0x3a,0xc4,0xd4,0x20,0x8d,
- 0x01,0x04,0xc1,0x6a,0x1d,0x6a,0x20,0x8d,
- 0x01,0x04,0xc1,0x8a,0x9a,0x2b,0x20,0x8d,
- 0x01,0x04,0xc1,0xb2,0xaa,0xe8,0x20,0x8d,
+ 0x01,0x04,0xc0,0xde,0x18,0x36,0x20,0x8d,
+ 0x01,0x04,0xc0,0xde,0x93,0x8d,0x20,0x8d,
+ 0x01,0x04,0xc1,0x20,0x7f,0xa2,0xee,0x29,
+ 0x01,0x04,0xc1,0x6f,0xc6,0xbb,0x1f,0xaf,
0x01,0x04,0xc1,0xc4,0x25,0x3e,0x20,0x8d,
- 0x01,0x04,0xc1,0xde,0x82,0x0e,0x20,0x8d,
- 0x01,0x04,0xc1,0xea,0x32,0xe3,0x20,0x8d,
- 0x01,0x04,0xc2,0x0e,0xf6,0xcd,0x20,0x8d,
- 0x01,0x04,0xc2,0x87,0x87,0x45,0x20,0x8d,
+ 0x01,0x04,0xc2,0x0d,0x50,0xb9,0x3c,0x46,
0x01,0x04,0xc2,0x93,0x71,0xc9,0x20,0x8d,
0x01,0x04,0xc2,0xa5,0x1e,0x14,0x20,0x8d,
- 0x01,0x04,0xc2,0xdb,0x3e,0x17,0x20,0x8d,
+ 0x01,0x04,0xc2,0xbf,0xef,0x62,0x20,0x8d,
0x01,0x04,0xc3,0x38,0x3f,0x04,0x20,0x8d,
- 0x01,0x04,0xc3,0x86,0xb7,0xbc,0x20,0x8d,
- 0x01,0x04,0xc3,0xd0,0x67,0x1e,0x20,0xfc,
- 0x01,0x04,0xc3,0xd0,0x67,0x1f,0x20,0xfc,
+ 0x01,0x04,0xc3,0x38,0x3f,0x0a,0x20,0x8d,
+ 0x01,0x04,0xc3,0x7b,0xef,0xb9,0x20,0x8d,
+ 0x01,0x04,0xc3,0x8c,0xe2,0x9a,0x20,0x8d,
0x01,0x04,0xc6,0x01,0xe7,0x06,0x20,0x8d,
- 0x01,0x04,0xc6,0x0c,0x0e,0x88,0x20,0x8d,
- 0x01,0x04,0xc6,0x54,0xed,0x46,0x20,0x8d,
- 0x01,0x04,0xc6,0xb2,0x78,0x05,0x1f,0xb0,
- 0x01,0x04,0xc7,0x30,0x5c,0xb8,0x20,0x8d,
- 0x01,0x04,0xc7,0x44,0xc7,0x13,0x20,0x8d,
- 0x01,0x04,0xc7,0xb6,0xb8,0xcc,0x20,0x8d,
- 0x01,0x04,0xc7,0xbd,0xf2,0x8d,0x20,0x8d,
+ 0x01,0x04,0xc6,0x94,0x70,0x1b,0x20,0x8d,
+ 0x01,0x04,0xc7,0x7e,0xea,0xed,0x20,0x8d,
+ 0x01,0x04,0xc7,0xc1,0xae,0xad,0x20,0x8d,
0x01,0x04,0xc7,0xf7,0x07,0xd0,0x20,0x8d,
- 0x01,0x04,0xc8,0x7a,0xb5,0x25,0x20,0x8d,
+ 0x01,0x04,0xc8,0x7a,0xb5,0x2e,0x20,0x8d,
0x01,0x04,0xc9,0xbf,0x06,0x67,0x20,0x8d,
- 0x01,0x04,0xca,0x6b,0xdb,0x82,0x20,0x8d,
+ 0x01,0x04,0xc9,0xd4,0x24,0xd1,0x20,0x8d,
+ 0x01,0x04,0xc9,0xdd,0xea,0xc8,0x20,0x8d,
0x01,0x04,0xca,0x6c,0xd3,0x87,0x20,0x8d,
- 0x01,0x04,0xcb,0x5e,0x21,0x70,0x20,0x8d,
+ 0x01,0x04,0xca,0xa9,0x11,0xb2,0x20,0x8d,
+ 0x01,0x04,0xca,0xb1,0x18,0x8c,0x20,0x8d,
0x01,0x04,0xcb,0x82,0x30,0x75,0x22,0xb5,
0x01,0x04,0xcb,0x84,0x5e,0xc4,0x20,0x8d,
- 0x01,0x04,0xcb,0xa2,0x0d,0xb5,0x20,0x8c,
- 0x01,0x04,0xcc,0xbf,0xc9,0x2b,0x20,0x8d,
- 0x01,0x04,0xcc,0xe5,0x0a,0x5a,0x20,0x8d,
0x01,0x04,0xcd,0xb2,0x29,0x7c,0x20,0x8d,
- 0x01,0x04,0xce,0x37,0xb2,0x9d,0x20,0x8d,
- 0x01,0x04,0xce,0x7e,0xcb,0x08,0x20,0x8d,
- 0x01,0x04,0xce,0xae,0x73,0x60,0x20,0x8d,
+ 0x01,0x04,0xce,0x48,0xc9,0xe4,0x20,0x8d,
+ 0x01,0x04,0xce,0xc0,0xcb,0x00,0x20,0x8d,
0x01,0x04,0xce,0xdf,0x99,0x34,0x20,0x8d,
- 0x01,0x04,0xcf,0xbc,0x9f,0x19,0x20,0x8d,
+ 0x01,0x04,0xcf,0x86,0xd8,0x91,0x20,0x8e,
+ 0x01,0x04,0xcf,0xbc,0x9a,0x32,0x20,0x8d,
0x01,0x04,0xcf,0xe5,0x2e,0x50,0x20,0x8d,
+ 0x01,0x04,0xcf,0xff,0xc1,0x2f,0x20,0x8d,
+ 0x01,0x04,0xd0,0x68,0x5c,0x4a,0x20,0x8d,
0x01,0x04,0xd1,0x3a,0x91,0x9d,0x20,0x8d,
- 0x01,0x04,0xd1,0x7e,0x51,0x93,0x20,0x8d,
- 0x01,0x04,0xd1,0x91,0x3f,0x96,0x20,0x8d,
- 0x01,0x04,0xd1,0xd1,0x0a,0x1e,0x20,0x8d,
+ 0x01,0x04,0xd1,0x3a,0x9e,0xe8,0x20,0x8f,
+ 0x01,0x04,0xd1,0x8d,0x2b,0xf3,0x20,0x8d,
+ 0x01,0x04,0xd1,0xe2,0x8e,0x3e,0x20,0x8d,
0x01,0x04,0xd1,0xed,0x7f,0xe3,0x20,0x8d,
- 0x01,0x04,0xd4,0x63,0xe2,0x24,0x23,0x3c,
- 0x01,0x04,0xd4,0xb9,0x56,0x54,0x20,0x8d,
+ 0x01,0x04,0xd1,0xed,0x85,0x36,0x20,0x8d,
+ 0x01,0x04,0xd3,0xf8,0x5a,0x32,0x20,0x8d,
+ 0x01,0x04,0xd4,0x15,0x12,0x4e,0x20,0x8d,
+ 0x01,0x04,0xd4,0x22,0xe1,0x76,0x20,0x8d,
+ 0x01,0x04,0xd4,0x33,0x92,0x89,0x20,0x8d,
0x01,0x04,0xd4,0xe3,0xd3,0x57,0x20,0x8d,
+ 0x01,0x04,0xd5,0x00,0x45,0x4c,0x20,0x8d,
0x01,0x04,0xd5,0x05,0x24,0x3a,0x20,0x8d,
- 0x01,0x04,0xd5,0x59,0xec,0xdb,0x20,0x8d,
- 0x01,0x04,0xd5,0x5d,0x91,0xb7,0x20,0x8d,
+ 0x01,0x04,0xd5,0x2f,0x40,0x69,0x20,0x8d,
+ 0x01,0x04,0xd5,0x59,0x87,0x97,0x20,0x8d,
+ 0x01,0x04,0xd5,0x8d,0x9a,0xc9,0x20,0x8d,
+ 0x01,0x04,0xd5,0x9f,0xc6,0x2d,0x20,0x8d,
+ 0x01,0x04,0xd5,0xb8,0xf4,0x18,0x20,0x8d,
0x01,0x04,0xd5,0xd6,0x42,0xb6,0x20,0x8d,
- 0x01,0x04,0xd8,0x29,0xf9,0xb2,0x20,0x8d,
+ 0x01,0x04,0xd5,0xe2,0x7b,0x4c,0x20,0x8d,
0x01,0x04,0xd8,0x92,0xfb,0x08,0x20,0x8d,
- 0x01,0x04,0xd8,0xf9,0x46,0x16,0x20,0x8d,
- 0x01,0x04,0xd9,0x0b,0xf0,0x04,0x20,0x8d,
- 0x01,0x04,0xd9,0x0f,0xb2,0x07,0x20,0x8d,
- 0x01,0x04,0xd9,0x18,0xe9,0x74,0x20,0x8d,
- 0x01,0x04,0xd9,0x40,0x94,0x62,0xc8,0xc9,
+ 0x01,0x04,0xd8,0xba,0xee,0x0e,0x20,0x8d,
+ 0x01,0x04,0xd9,0x05,0x96,0x72,0x20,0x8d,
+ 0x01,0x04,0xd9,0x0f,0xb2,0x0b,0x20,0x8d,
+ 0x01,0x04,0xd9,0x18,0xef,0x6d,0x20,0x8d,
+ 0x01,0x04,0xd9,0x40,0x2f,0x8a,0x20,0x8d,
+ 0x01,0x04,0xd9,0x49,0x50,0x68,0x20,0x8d,
+ 0x01,0x04,0xd9,0x4f,0xb5,0x26,0x20,0x8d,
+ 0x01,0x04,0xd9,0x5c,0x37,0xf6,0x20,0x8d,
0x01,0x04,0xd9,0x71,0x79,0xa9,0x20,0x8d,
+ 0x01,0x04,0xd9,0x73,0x74,0xfa,0x20,0x8d,
+ 0x01,0x04,0xd9,0x9b,0xf4,0xaa,0x20,0x8d,
0x01,0x04,0xd9,0xaa,0x7c,0xaa,0x20,0x8d,
0x01,0x04,0xdc,0x84,0x87,0x36,0x20,0x8d,
- 0x01,0x04,0xdc,0xdd,0x3a,0x19,0x20,0x8d,
0x01,0x04,0xdc,0xe9,0xb2,0xc7,0x20,0x8d,
- 0x01,0x04,0xdd,0xdb,0x61,0x69,0x07,0xd1,
- 0x02,0x10,0x20,0x01,0x16,0x08,0x00,0x1b,0x00,0xf9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x67,0x7b,
+ 0x01,0x04,0xde,0x9a,0x6f,0x2e,0x20,0x8d,
0x02,0x10,0x20,0x01,0x16,0x20,0x05,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x19,0xf0,0x60,0x01,0x39,0xaa,0x54,0x00,0x03,0xff,0xfe,0xf0,0x09,0x16,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x19,0xf0,0x80,0x01,0x0f,0x71,0x54,0x00,0x04,0xff,0xfe,0x10,0x6a,0x63,0x20,0x8d,
0x02,0x10,0x20,0x01,0x1b,0xc0,0x00,0xc1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x20,0x8d,
- 0x02,0x10,0x20,0x01,0x04,0x70,0x1f,0x0a,0x08,0x9a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x1c,0x02,0x01,0x1e,0x35,0x00,0xdf,0x25,0x63,0x21,0x82,0x60,0xd9,0xbe,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x41,0xd0,0x10,0x04,0x1b,0x79,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x93,
+ 0x02,0x10,0x20,0x01,0x41,0xd0,0x02,0x03,0x37,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x41,0xd0,0x02,0x03,0xaa,0xcc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x41,0xd0,0x02,0x03,0xbb,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x41,0xd0,0x00,0x02,0xbf,0x8f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x41,0xd0,0x03,0x03,0x65,0x86,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x41,0xd0,0x06,0x02,0x44,0x93,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x41,0xd0,0x00,0x08,0xb9,0xd8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x41,0xd0,0x00,0x0a,0x69,0xa2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x41,0xf0,0x00,0x00,0x00,0x00,0x00,0x62,0x69,0x74,0x63,0x6f,0x69,0x6e,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x44,0xb8,0x02,0x56,0x5d,0x11,0x02,0x16,0x3e,0xff,0xfe,0x39,0xd5,0xd4,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x04,0x70,0x1b,0x62,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x04,0x70,0x1f,0x07,0x08,0x03,0x02,0x0c,0x29,0xff,0xfe,0x2d,0x58,0x79,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x04,0x70,0x1f,0x15,0x01,0x06,0xe2,0xd5,0x5e,0xff,0xfe,0x42,0x7a,0xe5,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x04,0x70,0x1f,0x15,0x0c,0x43,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x04,0x70,0x00,0x26,0x04,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x0b,0x7c,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x04,0x70,0x75,0xe9,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x20,0x8d,
0x02,0x10,0x20,0x01,0x04,0x70,0xde,0x5a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xec,0x24,0x75,
- 0x02,0x10,0x20,0x01,0x4b,0x98,0x0d,0xc0,0x00,0x45,0x02,0x16,0x3e,0xff,0xfe,0xa2,0x95,0xcd,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x4b,0xa0,0xba,0xbe,0x05,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x4b,0xa0,0xff,0xff,0x00,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x00,0x30,0xb7,0x1d,0x7b,0x6f,0xec,0x4c,0x5c,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x00,0x08,0x8e,0xb4,0xff,0x2a,0xd0,0x69,0x9b,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x00,0x9c,0x1c,0xcc,0x31,0x9f,0xe8,0x55,0x05,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x00,0xa0,0xc4,0xd4,0x1f,0x04,0xc4,0x1b,0xb0,0x20,0x8d,
0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x00,0xfd,0x76,0xc1,0xd3,0x18,0x54,0x5b,0xd9,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x01,0x00,0x00,0x00,0x00,0x76,0x76,0x80,0x90,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x01,0xb9,0x77,0xbd,0x71,0x46,0x12,0x8e,0x40,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x4d,0xd0,0xaf,0x0e,0x35,0x64,0x00,0x00,0x00,0x00,0x00,0x69,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x4d,0xd0,0xaf,0x0e,0x35,0x64,0x00,0x00,0x00,0x00,0x00,0x69,0x00,0x90,0x20,0x8d,
0x02,0x10,0x20,0x01,0x4d,0xe8,0xb1,0xb2,0x00,0x01,0x00,0x00,0xde,0xad,0xbe,0xef,0x00,0x07,0x20,0x8d,
0x02,0x10,0x20,0x01,0x06,0x38,0xa0,0x00,0x41,0x40,0x00,0x00,0x00,0x00,0xff,0xff,0x01,0x91,0x20,0x8d,
- 0x02,0x10,0x20,0x01,0x06,0x48,0x28,0x00,0x01,0x31,0x4b,0x1f,0xf6,0xfc,0x20,0xf7,0xf9,0x9f,0x20,0x8d,
- 0x02,0x10,0x20,0x01,0x06,0x78,0x0c,0xc8,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x10,0x00,0x88,0x4e,0x28,
+ 0x02,0x10,0x20,0x01,0x06,0x78,0x0a,0xcc,0x00,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d,
0x02,0x10,0x20,0x01,0x06,0x7c,0x26,0xb4,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x44,0x20,0x8d,
- 0x02,0x10,0x20,0x01,0x06,0x7c,0x2d,0xb8,0x00,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x92,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x06,0x7c,0x2d,0xb8,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x07,0xc0,0x23,0x10,0x00,0x00,0xf8,0x16,0x3e,0xff,0xfe,0x0d,0x4a,0xb6,0x20,0x8d,
0x02,0x10,0x20,0x01,0x07,0xc0,0x23,0x10,0x00,0x00,0xf8,0x16,0x3e,0xff,0xfe,0x6c,0x4f,0x58,0x20,0x8d,
- 0x02,0x10,0x20,0x01,0x08,0x18,0xea,0x1b,0x76,0x00,0xf0,0x53,0xaa,0xde,0xf4,0x7b,0xb7,0x01,0x20,0x8d,
- 0x02,0x10,0x20,0x01,0x08,0xf1,0x14,0x04,0x37,0x00,0x8e,0x49,0x71,0x5a,0x2e,0x09,0xb6,0x34,0x24,0xe4,
- 0x02,0x10,0x20,0x01,0x09,0x85,0x55,0xa0,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
- 0x02,0x10,0x20,0x01,0x09,0x99,0x02,0x70,0x2c,0x2c,0x0c,0x8b,0x3a,0x20,0x3f,0x2f,0x31,0x8f,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x08,0x61,0x32,0x46,0x0a,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x0b,0x07,0x02,0xe6,0x38,0xd7,0xba,0x27,0xeb,0xff,0xfe,0x60,0x3d,0xc1,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x0b,0x07,0x64,0x61,0x78,0x11,0x04,0x89,0xd2,0xda,0x0e,0x07,0x1a,0xf7,0x20,0x8d,
0x02,0x10,0x20,0x01,0x0b,0x07,0x0a,0xc9,0x44,0x2b,0x79,0xd6,0xbb,0xbe,0xb3,0x7c,0xa7,0x83,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x0b,0xc8,0x16,0x00,0x00,0x00,0x02,0x08,0xa2,0xff,0xfe,0x0c,0x8a,0x2e,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x0b,0xc8,0x32,0x3c,0x00,0xff,0xa6,0x34,0x38,0x4f,0x18,0x49,0xf4,0xbc,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x0b,0xc8,0x32,0x3c,0x00,0xff,0xd2,0x17,0xc2,0xff,0xfe,0x07,0x2c,0xd9,0x20,0x8d,
+ 0x02,0x10,0x20,0x01,0x0b,0xc8,0x3b,0xec,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
0x02,0x10,0x20,0x02,0x2f,0x5b,0xa5,0xf9,0x00,0x00,0x00,0x00,0x00,0x00,0x2f,0x5b,0xa5,0xf9,0x22,0xb5,
- 0x02,0x10,0x20,0x02,0xb6,0xff,0x3d,0xca,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0xff,0x3d,0xca,0x6e,0xcc,
+ 0x02,0x10,0x20,0x03,0x00,0xcb,0x87,0x13,0x61,0x02,0xaa,0xa1,0x59,0xff,0xfe,0x57,0x77,0x79,0x20,0x8d,
+ 0x02,0x10,0x20,0x03,0x00,0xe0,0x37,0x0e,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x20,0x8d,
+ 0x02,0x10,0x20,0x03,0x00,0xf6,0x3f,0x10,0x67,0x00,0x4c,0x9f,0x76,0x20,0x83,0x24,0xd4,0xa7,0x20,0x8d,
0x02,0x10,0x24,0x00,0x24,0x10,0xce,0xa2,0x0d,0x00,0x41,0xbc,0xc9,0xea,0x86,0x1b,0x51,0xee,0x20,0x8d,
+ 0x02,0x10,0x24,0x00,0x24,0x11,0xa3,0xe1,0x49,0x00,0x25,0x68,0x68,0x4b,0x0e,0x99,0x71,0x20,0x20,0x8d,
+ 0x02,0x10,0x24,0x00,0x24,0x11,0xa3,0xe1,0x49,0x00,0x29,0x87,0xb8,0x8f,0x61,0xe0,0x84,0xfa,0x20,0x8d,
0x02,0x10,0x24,0x00,0x3b,0x00,0x00,0x20,0x00,0x0c,0xba,0xcb,0x29,0xff,0xfe,0xab,0x88,0x86,0x20,0x8d,
+ 0x02,0x10,0x24,0x01,0xb1,0x40,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x02,0x10,0x20,0x8d,
+ 0x02,0x10,0x24,0x01,0xb1,0x40,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x02,0x20,0x20,0x8d,
+ 0x02,0x10,0x24,0x01,0xb1,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x42,0x01,0x00,0x20,0x8d,
+ 0x02,0x10,0x24,0x01,0xb1,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x44,0x01,0x30,0x20,0x8d,
0x02,0x10,0x24,0x01,0xd0,0x02,0x39,0x02,0x07,0x00,0xd7,0x2c,0x5e,0x22,0x4e,0x95,0x38,0x9d,0x20,0x8d,
- 0x02,0x10,0x24,0x03,0x62,0x00,0x88,0xa0,0xfb,0x17,0xf5,0xf2,0xd8,0xb5,0xb7,0xba,0xf4,0xd3,0x20,0x8d,
- 0x02,0x10,0x24,0x05,0x98,0x00,0xb9,0x10,0x5f,0x8e,0x18,0x30,0xf6,0x30,0x2c,0xc6,0x88,0xfb,0x20,0x8d,
- 0x02,0x10,0x24,0x05,0x98,0x00,0xb9,0x70,0xc6,0x4c,0x10,0x9f,0x74,0xe7,0xae,0x5f,0x87,0xc7,0x20,0x8d,
- 0x02,0x10,0x24,0x05,0xaa,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x20,0x8d,
- 0x02,0x10,0x24,0x07,0x88,0x00,0xbc,0x61,0x22,0x02,0xd6,0x3d,0x7e,0xff,0xfe,0x6c,0xdc,0x36,0x20,0x8d,
- 0x02,0x10,0x24,0x08,0x82,0x48,0x70,0x04,0xf8,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x3c,0x20,0x8d,
+ 0x02,0x10,0x24,0x04,0x44,0x08,0x67,0x52,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x19,0x99,0x20,0x8d,
+ 0x02,0x10,0x24,0x04,0x7a,0x85,0x41,0x61,0x2b,0x00,0x49,0xa1,0x42,0x7a,0x0f,0xac,0x34,0x09,0x20,0x8d,
+ 0x02,0x10,0x24,0x05,0x98,0x00,0xb9,0x72,0xab,0x58,0x0c,0x05,0xe9,0x38,0x26,0x7e,0x02,0x71,0x20,0x8d,
+ 0x02,0x10,0x24,0x06,0xda,0x11,0x01,0x69,0x0b,0x03,0x32,0xb5,0xf9,0x01,0x9f,0x7c,0x3e,0x4b,0x20,0x8d,
+ 0x02,0x10,0x24,0x06,0xda,0x14,0x03,0x35,0xb6,0x01,0xce,0xb7,0xb4,0xfc,0xa8,0x55,0xf3,0xa5,0x20,0x8d,
+ 0x02,0x10,0x24,0x06,0xda,0x1e,0x0a,0x4e,0x8a,0x03,0x2a,0xad,0x49,0x6b,0x76,0x8d,0xe4,0x97,0x20,0x8d,
+ 0x02,0x10,0x24,0x07,0x88,0x00,0xbc,0x61,0x22,0x02,0xa0,0xc6,0x01,0x07,0x50,0x2b,0x4e,0x3b,0x20,0x8d,
0x02,0x10,0x24,0x09,0x00,0x10,0xca,0x20,0x1d,0xf0,0x02,0x24,0xe8,0xff,0xfe,0x1f,0x60,0xd9,0x20,0x8d,
- 0x02,0x10,0x24,0x0b,0x00,0x11,0x43,0xa1,0xbd,0x00,0xe5,0x89,0xf8,0xa7,0x04,0x9b,0x3b,0x86,0x20,0x8d,
- 0x02,0x10,0x24,0x0d,0x00,0x1a,0x07,0x91,0x34,0x00,0xd6,0x5d,0x64,0xff,0xfe,0x28,0x92,0x7e,0x20,0x8d,
- 0x02,0x10,0x24,0x0d,0x00,0x1a,0x07,0x91,0x34,0x00,0xd6,0x81,0xd7,0xff,0xfe,0xf6,0xa2,0x1e,0x27,0x42,
- 0x02,0x10,0x26,0x00,0x17,0x00,0x5b,0x2b,0x00,0x5f,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x40,0x20,0x8d,
+ 0x02,0x10,0x26,0x00,0x17,0x00,0x22,0xf1,0x64,0x1f,0x00,0xe8,0x39,0xc8,0xeb,0x1d,0xa1,0xeb,0x20,0x8d,
+ 0x02,0x10,0x26,0x00,0x17,0x00,0x9c,0x5d,0x0e,0xd0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x20,0x8d,
+ 0x02,0x10,0x26,0x00,0x17,0x00,0x9c,0x5d,0x0e,0xd0,0xd0,0xd6,0x01,0xd9,0x5c,0xc2,0xab,0x47,0x20,0x8d,
+ 0x02,0x10,0x26,0x00,0x17,0x02,0x1c,0xe0,0x40,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x20,0x8d,
+ 0x02,0x10,0x26,0x00,0x1f,0x14,0x04,0x0e,0xe3,0x01,0xd1,0x55,0xaa,0x3a,0x77,0xbe,0x96,0x0e,0x20,0x8d,
+ 0x02,0x10,0x26,0x00,0x1f,0x16,0x0a,0x08,0xb9,0x01,0x1a,0xfa,0xef,0x4e,0x4c,0xe7,0x2b,0xa4,0x20,0x8d,
+ 0x02,0x10,0x26,0x00,0x1f,0x1c,0x02,0xd3,0x24,0x03,0x5b,0xac,0x3f,0xc6,0x65,0x13,0x7a,0x63,0x20,0x8d,
0x02,0x10,0x26,0x00,0x21,0x04,0x10,0x03,0xc5,0xab,0xdc,0x5e,0x90,0xff,0xfe,0x18,0x1d,0x08,0x20,0x8d,
+ 0x02,0x10,0x26,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0xf0,0x3c,0x92,0xff,0xfe,0x92,0x27,0x45,0x20,0x8d,
+ 0x02,0x10,0x26,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0xf0,0x3c,0x92,0xff,0xfe,0xcf,0x61,0xb6,0x20,0x8d,
+ 0x02,0x10,0x26,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0xf0,0x3c,0x93,0xff,0xfe,0xb3,0x01,0xb6,0x20,0x8d,
0x02,0x10,0x26,0x00,0x3c,0x00,0xe0,0x02,0x2e,0x32,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x14,0x20,0x8d,
+ 0x02,0x10,0x26,0x00,0x3c,0x02,0x00,0x00,0x00,0x00,0xf0,0x3c,0x92,0xff,0xfe,0x5d,0x09,0xfb,0x20,0x8d,
+ 0x02,0x10,0x26,0x00,0x40,0x40,0x28,0x54,0x5e,0x00,0xc6,0xe9,0x84,0xff,0xfe,0x46,0x0e,0xe8,0x21,0xda,
+ 0x02,0x10,0x26,0x00,0x6c,0x54,0x71,0x00,0x1a,0xd1,0xbd,0xdf,0x55,0x0e,0x91,0xbe,0xf9,0xe1,0x20,0x8d,
0x02,0x10,0x26,0x00,0x88,0x05,0x24,0x00,0x01,0x4e,0x12,0xdd,0xb1,0xff,0xfe,0xf2,0x30,0x13,0x20,0x8d,
+ 0x02,0x10,0x26,0x01,0x01,0x84,0x03,0x00,0x0b,0xde,0x3c,0x29,0x8e,0x94,0x1b,0xa8,0xfd,0xe3,0x20,0x8d,
+ 0x02,0x10,0x26,0x01,0x01,0x8c,0x80,0x80,0x30,0x0f,0x02,0x19,0xd1,0xff,0xfe,0x75,0xdc,0x2f,0x20,0x8d,
+ 0x02,0x10,0x26,0x01,0x01,0x8d,0x46,0x00,0x43,0xf1,0x20,0xe7,0xb3,0xff,0xfe,0xcf,0x0a,0x99,0x20,0x8d,
+ 0x02,0x10,0x26,0x01,0x01,0x8d,0x87,0x01,0xc2,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x20,0x8d,
+ 0x02,0x10,0x26,0x01,0x02,0x46,0x4d,0x7f,0x9e,0x28,0xf3,0x21,0x36,0xca,0x7a,0x71,0xc6,0x87,0x20,0x8d,
+ 0x02,0x10,0x26,0x01,0x06,0x40,0xc2,0x01,0x96,0x0d,0x86,0xeb,0xf2,0x7d,0x66,0xa2,0xf2,0xc1,0x20,0x8d,
+ 0x02,0x10,0x26,0x02,0x02,0x41,0x75,0xd1,0x2b,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x40,0x20,0x8d,
0x02,0x10,0x26,0x02,0xff,0xb8,0x00,0x00,0x00,0x00,0x02,0x08,0x00,0x72,0x00,0x57,0x02,0x00,0x20,0x8d,
- 0x02,0x10,0x26,0x03,0x30,0x1f,0x1e,0xbf,0xe0,0x00,0xe2,0x3f,0x49,0xff,0xfe,0xe7,0x74,0x31,0x20,0x8d,
- 0x02,0x10,0x26,0x03,0x60,0x81,0x18,0x00,0x66,0x00,0x16,0xdd,0xa9,0xff,0xfe,0xee,0xb2,0xf3,0x20,0x8d,
- 0x02,0x10,0x26,0x04,0x13,0x80,0x10,0x00,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
- 0x02,0x10,0x26,0x04,0x45,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2e,0x06,0x1f,0xb0,
- 0x02,0x10,0x26,0x04,0x55,0x00,0xc1,0x34,0x40,0x00,0x72,0x85,0xc2,0xff,0xfe,0x4a,0xe1,0x43,0x80,0x1d,
- 0x02,0x10,0x26,0x04,0x55,0x00,0xc1,0x34,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xfc,0x80,0x1d,
+ 0x02,0x10,0x26,0x03,0x30,0x04,0x06,0xa1,0x38,0x00,0x85,0x1f,0x58,0x4d,0x7a,0xba,0xaf,0xfb,0x20,0x8d,
+ 0x02,0x10,0x26,0x03,0x30,0x04,0x06,0xa1,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x44,0x02,0x20,0x8d,
+ 0x02,0x10,0x26,0x03,0x30,0x04,0x07,0x0d,0x14,0x00,0x85,0x32,0x29,0x00,0xce,0x6f,0xac,0xdf,0x20,0x8d,
+ 0x02,0x10,0x26,0x03,0x30,0x04,0x07,0x45,0x09,0x00,0xf0,0xd7,0x55,0x6a,0x0a,0x8c,0xce,0xd5,0x20,0x8d,
+ 0x02,0x10,0x26,0x03,0x60,0x80,0xc0,0x00,0x5d,0x8a,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x4f,0x20,0x8d,
+ 0x02,0x10,0x26,0x03,0x80,0x00,0xd1,0x00,0x89,0x91,0xcc,0x29,0xcc,0xff,0xfe,0x42,0x30,0x0c,0x20,0x8d,
+ 0x02,0x10,0x26,0x03,0x80,0x80,0x1f,0x07,0x6f,0xdd,0x7d,0xe2,0xd9,0x69,0x78,0xc9,0xb7,0xea,0x20,0x8d,
+ 0x02,0x10,0x26,0x03,0x80,0x80,0x73,0x00,0x05,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xea,0x20,0x8d,
+ 0x02,0x10,0x26,0x03,0x80,0xa0,0x07,0x03,0x40,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x20,0x8d,
+ 0x02,0x10,0x26,0x04,0x01,0x80,0x00,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x18,0x20,0x8d,
+ 0x02,0x10,0x26,0x04,0x3d,0x08,0x00,0x00,0x00,0x05,0xd9,0x41,0x4b,0x03,0xa0,0x93,0x13,0x1b,0x20,0x8d,
0x02,0x10,0x26,0x04,0x7c,0x00,0x01,0x20,0x00,0x4b,0x00,0x00,0x00,0x00,0x00,0x00,0xeb,0x24,0x20,0x8d,
+ 0x02,0x10,0x26,0x04,0x0a,0x00,0x00,0x21,0x30,0x43,0xbf,0x6a,0x53,0x5e,0xdf,0xeb,0x5b,0x7b,0x20,0x8d,
+ 0x02,0x10,0x26,0x04,0xa8,0x80,0x04,0x00,0x00,0xd0,0x00,0x00,0x00,0x00,0x1c,0xe7,0x40,0x01,0x20,0x8d,
+ 0x02,0x10,0x26,0x04,0xa8,0x80,0x04,0x00,0x00,0xd0,0x00,0x00,0x00,0x00,0x1d,0x44,0xe0,0x01,0x20,0x8d,
+ 0x02,0x10,0x26,0x04,0xa8,0x80,0x04,0x00,0x00,0xd0,0x00,0x00,0x00,0x00,0x26,0x1f,0x60,0x01,0x20,0x8d,
+ 0x02,0x10,0x26,0x04,0xa8,0x80,0x04,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x07,0xe2,0xe0,0x01,0x20,0x8d,
+ 0x02,0x10,0x26,0x04,0xa8,0x80,0x00,0x04,0x01,0xd0,0x00,0x00,0x00,0x00,0x00,0x14,0x30,0x00,0x20,0x8d,
+ 0x02,0x10,0x26,0x04,0xa8,0x80,0x00,0x04,0x01,0xd0,0x00,0x00,0x00,0x00,0x00,0xe5,0xb0,0x00,0x20,0x8d,
0x02,0x10,0x26,0x05,0x64,0x00,0x00,0x30,0xf2,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d,
0x02,0x10,0x26,0x05,0x6f,0x80,0x00,0x00,0x00,0x07,0xfc,0x1b,0xcc,0xff,0xfe,0x8a,0xd8,0x22,0x20,0x8d,
+ 0x02,0x10,0x26,0x05,0xa1,0x40,0x20,0x76,0x82,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x26,0x05,0xa1,0x40,0x30,0x07,0x12,0x87,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
0x02,0x10,0x26,0x05,0xae,0x00,0x02,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x03,0x20,0x8d,
0x02,0x10,0x26,0x05,0xc0,0x00,0x2a,0x0a,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x20,0x8d,
- 0x02,0x10,0x26,0x05,0xf7,0x00,0x00,0xc0,0x08,0x27,0x02,0x25,0x90,0xff,0xfe,0xe3,0x34,0xa6,0x20,0x8d,
+ 0x02,0x10,0x26,0x07,0x1a,0x00,0x00,0x01,0x00,0x0d,0x00,0x00,0x00,0x00,0x00,0x11,0x7c,0x4d,0x20,0x8d,
+ 0x02,0x10,0x26,0x07,0x53,0x00,0x02,0x03,0x12,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d,
0x02,0x10,0x26,0x07,0x92,0x80,0x00,0x0b,0x07,0x3b,0x02,0x50,0x56,0xff,0xfe,0x14,0x25,0xb5,0x20,0x8d,
- 0x02,0x10,0x26,0x07,0xf2,0xf8,0xad,0x40,0x0b,0xc1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
- 0x02,0x10,0x26,0x07,0xfa,0x18,0x3a,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x8d,
- 0x02,0x10,0x26,0x20,0x01,0x1c,0x50,0x01,0x11,0x18,0xd2,0x67,0xe5,0xff,0xfe,0xe9,0xe6,0x73,0x20,0x8d,
- 0x02,0x10,0x26,0x20,0x01,0x1c,0x50,0x01,0x21,0x99,0xd2,0x67,0xe5,0xff,0xfe,0xe9,0xe6,0x73,0x20,0x8d,
+ 0x02,0x10,0x26,0x07,0x92,0x80,0x00,0x0b,0x07,0x3b,0x02,0x50,0x56,0xff,0xfe,0x21,0x9c,0x2f,0x20,0x8d,
+ 0x02,0x10,0x26,0x07,0x92,0x80,0x00,0x0b,0x07,0x3b,0x02,0x50,0x56,0xff,0xfe,0x21,0xbf,0x32,0x20,0x8d,
+ 0x02,0x10,0x26,0x07,0x92,0x80,0x00,0x0b,0x07,0x3b,0x02,0x50,0x56,0xff,0xfe,0x33,0x4d,0x1b,0x20,0x8d,
+ 0x02,0x10,0x26,0x07,0x92,0x80,0x00,0x0b,0x07,0x3b,0x02,0x50,0x56,0xff,0xfe,0x3d,0x04,0x01,0x20,0x8d,
+ 0x02,0x10,0x26,0x07,0xf2,0xc0,0xe1,0xc2,0x00,0x69,0x12,0xc3,0x7b,0xff,0xfe,0x4d,0x94,0x31,0x20,0x8d,
+ 0x02,0x10,0x26,0x07,0xf2,0xc0,0xe1,0xc2,0x00,0x69,0xec,0xb2,0x6e,0x88,0x9f,0x33,0x50,0x57,0x20,0x8d,
0x02,0x10,0x26,0x20,0x00,0x06,0x20,0x03,0x01,0x05,0x02,0xd8,0x61,0xff,0xfe,0x0f,0x08,0x53,0x20,0x8d,
0x02,0x10,0x26,0x20,0x00,0x6e,0xa0,0x00,0x00,0x01,0x00,0x42,0x00,0x42,0x00,0x42,0x00,0x42,0x20,0x8d,
- 0x02,0x10,0x28,0x03,0xcf,0x00,0x0a,0xf8,0xf2,0x00,0xb8,0x9e,0xcf,0x34,0x92,0xc7,0x2d,0x26,0x20,0x8d,
- 0x02,0x10,0x28,0x04,0x01,0x4c,0x65,0xd1,0x40,0x2c,0xbc,0x53,0xbf,0x5d,0x06,0x8a,0x21,0x36,0x20,0x8d,
- 0x02,0x10,0x28,0x04,0x07,0xf1,0xe7,0x83,0xd4,0x01,0x66,0x1c,0x67,0xff,0xfe,0xba,0x55,0x47,0x20,0x8d,
- 0x02,0x10,0x28,0x04,0x0d,0x57,0x55,0x37,0x48,0x00,0x02,0x1e,0x67,0xff,0xfe,0xa8,0xd7,0x98,0x20,0x8d,
- 0x02,0x10,0x28,0x04,0x0d,0x57,0x55,0x37,0x48,0x00,0x36,0x15,0x9e,0xff,0xfe,0x23,0xd6,0x10,0x20,0x8d,
- 0x02,0x10,0x28,0x06,0x02,0xf0,0x20,0x80,0x06,0x2a,0x08,0x6f,0x1a,0x01,0xc4,0x4f,0x17,0x94,0x20,0x8d,
- 0x02,0x10,0x2a,0x00,0x10,0x28,0x83,0x82,0xbf,0x22,0x5f,0x7f,0xb7,0x8f,0x27,0x37,0x77,0x39,0x20,0x8d,
+ 0x02,0x10,0x26,0x20,0x00,0xa6,0x20,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x03,0xd5,0x70,0x20,0x8d,
+ 0x02,0x10,0x26,0x20,0x00,0xa6,0x20,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x05,0x16,0x2a,0x20,0x8d,
+ 0x02,0x10,0x26,0x20,0x00,0xa6,0x20,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x05,0x16,0x31,0x20,0x8d,
+ 0x02,0x10,0x26,0x20,0x00,0xa6,0x20,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x0c,0xe6,0x34,0x20,0x8d,
+ 0x02,0x10,0x28,0x00,0x00,0x40,0x00,0x33,0x08,0xab,0xa0,0xe7,0xb2,0x15,0xfc,0x83,0x5c,0x31,0x20,0x8d,
+ 0x02,0x10,0x28,0x00,0x0b,0xf0,0x01,0x49,0x0f,0x4b,0xf8,0xdf,0x8d,0x7d,0x80,0x1b,0xe2,0x5e,0x20,0x8d,
+ 0x02,0x10,0x28,0x04,0x01,0x4c,0x01,0x98,0x80,0xd5,0x76,0x03,0x41,0xd1,0xd3,0xfc,0xe7,0x97,0x20,0x8d,
+ 0x02,0x10,0x28,0x04,0x01,0x4d,0xae,0x81,0x82,0x7b,0x99,0xa8,0x1e,0x3f,0x6d,0xb2,0x29,0xdb,0x20,0x8d,
+ 0x02,0x10,0x28,0x04,0x0d,0x57,0x55,0x37,0x48,0x00,0x3e,0x7c,0x3f,0xff,0xfe,0x7b,0x80,0xaa,0x20,0x8d,
0x02,0x10,0x2a,0x00,0x12,0xe0,0x01,0x01,0x00,0x99,0x02,0x0c,0x29,0xff,0xfe,0x29,0xd0,0x3f,0x20,0x8d,
0x02,0x10,0x2a,0x00,0x13,0x28,0xe1,0x01,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x63,0x20,0x8d,
+ 0x02,0x10,0x2a,0x00,0x13,0x98,0x00,0x04,0x2a,0x03,0x02,0x15,0x5d,0xff,0xfe,0xd6,0x10,0x33,0x20,0x8d,
+ 0x02,0x10,0x2a,0x00,0x13,0x98,0x00,0x04,0x2a,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xbc,0x03,0x20,0x8d,
0x02,0x10,0x2a,0x00,0x16,0x30,0x00,0x10,0x10,0x03,0x00,0x00,0x0b,0x19,0xb0,0x0b,0xba,0xbe,0x20,0x8d,
0x02,0x10,0x2a,0x00,0x17,0x68,0x20,0x01,0x00,0x27,0x00,0x00,0x00,0x00,0x00,0x00,0xef,0x6a,0x20,0x8d,
0x02,0x10,0x2a,0x00,0x18,0x28,0xa0,0x04,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x66,0x20,0x8d,
- 0x02,0x10,0x2a,0x00,0x18,0x38,0x00,0x2a,0x14,0x00,0x92,0xe2,0xba,0xff,0xfe,0x4a,0xc4,0x16,0x20,0x8d,
0x02,0x10,0x2a,0x00,0x1c,0x10,0x00,0x02,0x07,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x17,0x56,0xcc,
0x02,0x10,0x2a,0x00,0x1f,0x40,0x50,0x01,0x01,0x08,0x5d,0x17,0x77,0x03,0xb0,0xf5,0x41,0x33,0x20,0x8d,
- 0x02,0x10,0x2a,0x00,0x60,0x20,0x15,0xdd,0xee,0x00,0xc8,0xc2,0x2c,0x77,0x17,0x49,0x35,0xdb,0x20,0x8d,
+ 0x02,0x10,0x2a,0x00,0x23,0xc5,0xfe,0x80,0x73,0x01,0xd6,0xae,0x52,0xff,0xfe,0xd5,0x56,0xa5,0x20,0x8d,
+ 0x02,0x10,0x2a,0x00,0x23,0xc6,0x5c,0x91,0x58,0x08,0xc0,0x5a,0x4d,0xff,0xfe,0x65,0x9d,0x69,0x20,0x8d,
+ 0x02,0x10,0x2a,0x00,0x60,0x20,0x1b,0xfa,0xd4,0x00,0x02,0x0c,0x29,0xff,0xfe,0x61,0x4a,0x4c,0x20,0x8d,
0x02,0x10,0x2a,0x00,0x60,0x20,0xb4,0x82,0x92,0x00,0x49,0x1a,0x35,0x8c,0xd8,0xf7,0x01,0xda,0x20,0x8d,
- 0x02,0x10,0x2a,0x00,0x71,0x45,0x00,0xc1,0x00,0x01,0xae,0x29,0x07,0x27,0x2b,0x87,0x0f,0x64,0x14,0x15,
+ 0x02,0x10,0x2a,0x00,0x60,0x20,0xb4,0x89,0x20,0x00,0x50,0x54,0x00,0xff,0xfe,0xfc,0x5e,0xd8,0x20,0x8d,
+ 0x02,0x10,0x2a,0x00,0x7c,0x80,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x00,0x00,0x00,0xe3,0x7a,0x20,0x8d,
+ 0x02,0x10,0x2a,0x00,0x7c,0x80,0x00,0x00,0x00,0x71,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x20,0x8d,
0x02,0x10,0x2a,0x00,0x8a,0x60,0xe0,0x12,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0x20,0x8d,
- 0x02,0x10,0x2a,0x00,0xa0,0x40,0x01,0x00,0x00,0xf3,0x45,0xa5,0x0a,0xc0,0xfe,0xa3,0x71,0xe1,0x20,0x8d,
- 0x02,0x10,0x2a,0x01,0x04,0x88,0x20,0x00,0x98,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0x20,0x8d,
+ 0x02,0x10,0x2a,0x00,0xae,0x40,0x24,0x0e,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x20,0x8d,
+ 0x02,0x10,0x2a,0x00,0xbb,0xe0,0x00,0xcc,0x00,0x00,0x62,0xa4,0x4c,0xff,0xfe,0x23,0x75,0x10,0x20,0x8d,
+ 0x02,0x10,0x2a,0x00,0x0c,0xa8,0x0a,0x1f,0x30,0x25,0xf9,0x49,0xe4,0x42,0xc9,0x40,0x13,0xe8,0x20,0x8d,
+ 0x02,0x10,0x2a,0x00,0xd4,0xe0,0x00,0x02,0xd0,0x02,0x44,0x67,0x31,0xe0,0x6f,0xa5,0xb3,0xef,0x20,0x8d,
+ 0x02,0x10,0x2a,0x00,0x0e,0xe2,0x12,0x00,0x19,0x00,0x08,0xd3,0xd2,0xff,0xfe,0xb1,0xbc,0x58,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x02,0x38,0x42,0x0f,0x92,0x00,0xfa,0x5a,0x1a,0x4b,0x1e,0x6a,0xfa,0xdf,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x02,0x38,0x43,0x89,0xc4,0x00,0x3b,0x26,0xd9,0x4e,0x38,0xd5,0x44,0xef,0x20,0x8d,
0x02,0x10,0x2a,0x01,0x04,0x90,0x00,0x16,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x4b,0x00,0x80,0x7c,0x31,0x00,0xcd,0xa1,0x0c,0x6a,0x2b,0xad,0x24,0x18,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x04,0xf8,0x01,0x41,0x22,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x04,0xf8,0x01,0x73,0x23,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x04,0xf8,0x01,0x90,0x91,0xc4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x04,0xf8,0x02,0x00,0x72,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x04,0xf8,0x02,0x02,0x03,0xe6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x04,0xf8,0x02,0x21,0x44,0xd7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x04,0xf8,0x02,0x31,0x09,0x15,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x04,0xf9,0x00,0x2a,0x1c,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x04,0xf9,0x00,0x2b,0x02,0x9a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x04,0xf9,0x00,0x4a,0x31,0xde,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
0x02,0x10,0x2a,0x01,0x52,0x00,0x00,0x6c,0x61,0x62,0x7a,0x61,0x74,0x6b,0x6f,0x2e,0x73,0x6b,0x20,0x8d,
- 0x02,0x10,0x2a,0x01,0x63,0x80,0xff,0xfe,0x00,0x73,0x04,0xe3,0xb3,0xcc,0xa8,0x71,0x36,0xd1,0x20,0x8d,
- 0x02,0x10,0x2a,0x01,0x07,0xa0,0x00,0x02,0x13,0x7c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x63,0x80,0xff,0xfe,0x00,0x73,0x10,0xfb,0xd0,0x12,0x85,0x81,0xb4,0xd7,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x07,0xa7,0x00,0x02,0x28,0x04,0xae,0x1f,0x6b,0xff,0xfe,0x9d,0x6c,0x94,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x07,0xc8,0xaa,0xac,0x00,0x89,0x50,0x54,0x00,0xff,0xfe,0xb7,0xf5,0xcb,0x20,0x8d,
0x02,0x10,0x2a,0x01,0x07,0xc8,0xaa,0xc9,0x00,0xc9,0x50,0x54,0x00,0xff,0xfe,0xdf,0xff,0x95,0x20,0x8d,
0x02,0x10,0x2a,0x01,0x07,0xc8,0xd0,0x01,0x01,0xc1,0x50,0x54,0x00,0xff,0xfe,0xee,0x3e,0x1a,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x07,0xc8,0xd0,0x09,0x02,0xaa,0x50,0x54,0x00,0xff,0xfe,0x1b,0xa1,0x96,0x2d,0x00,
+ 0x02,0x10,0x2a,0x01,0x07,0xc8,0xff,0xfa,0x05,0x0e,0xdd,0xfe,0xc9,0x24,0xca,0x0a,0xcb,0xab,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x7e,0x00,0x00,0x00,0x00,0x00,0xf0,0x3c,0x93,0xff,0xfe,0x59,0x66,0xdc,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x7e,0x01,0x00,0x00,0x00,0x00,0xf0,0x3c,0x93,0xff,0xfe,0x3b,0xbb,0x5b,0x20,0x8d,
0x02,0x10,0x2a,0x01,0x87,0x40,0x00,0x01,0xff,0xc5,0x00,0x00,0x00,0x00,0x00,0x00,0x8c,0x6a,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x9f,0x40,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x20,0x8d,
0x02,0x10,0x2a,0x01,0xcb,0x00,0x0d,0x3d,0x77,0x00,0x02,0x27,0x0e,0xff,0xfe,0x28,0xc5,0x65,0x20,0x8d,
- 0x02,0x10,0x2a,0x01,0x00,0xd0,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x53,0x20,0x8d,
- 0x02,0x10,0x2a,0x01,0x00,0xd0,0xbe,0xf2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0x20,0x8d,
- 0x02,0x10,0x2a,0x01,0x0e,0x0a,0x09,0xfb,0xb0,0xe0,0x54,0xf8,0x19,0x01,0x6e,0x83,0x62,0xc1,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x0e,0x0a,0x00,0x20,0x73,0x50,0x91,0x9c,0xb1,0xc3,0x8b,0x83,0xad,0xf9,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x0e,0x0a,0x03,0x01,0x70,0x10,0xb8,0x7d,0xe1,0x4b,0xce,0xa9,0xb9,0x98,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x0e,0x0a,0x04,0x8b,0x2d,0x10,0x94,0xf2,0x4d,0x5c,0xca,0x5f,0xbf,0x49,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x0e,0x0a,0x05,0x30,0xa0,0xa0,0xf4,0x65,0x0a,0xf5,0xbe,0x1b,0x90,0x75,0x20,0x8d,
0x02,0x10,0x2a,0x01,0x0e,0x0a,0x0a,0xa7,0xc8,0xc0,0x96,0x79,0xaf,0xfa,0xb6,0xe5,0xef,0xc7,0x20,0x8d,
- 0x02,0x10,0x2a,0x02,0x13,0xb8,0xf0,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0x20,0x8d,
- 0x02,0x10,0x2a,0x02,0x01,0x68,0x63,0x28,0x00,0x00,0x02,0xa8,0x2c,0xff,0xfe,0x68,0xe3,0x2c,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x0e,0x11,0x10,0x0c,0x00,0x70,0xcb,0xc8,0x9e,0x31,0x4b,0x77,0x16,0x26,0x20,0x8d,
+ 0x02,0x10,0x2a,0x01,0x0e,0x34,0xee,0x78,0x30,0x60,0x02,0x30,0x48,0xff,0xfe,0x81,0xf1,0xc6,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x12,0x10,0x14,0xa9,0x67,0x00,0x0a,0x00,0x27,0xff,0xfe,0x4e,0x82,0xb6,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x12,0x10,0x46,0x39,0x0f,0x00,0x10,0xa7,0xe9,0x65,0x50,0x9a,0x7a,0x4a,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x12,0x10,0x7c,0x92,0x51,0x00,0x02,0x11,0x32,0xff,0xfe,0xae,0x15,0x2d,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x12,0x10,0x86,0xbf,0xf1,0x00,0x31,0x78,0xd7,0x00,0xd4,0x4d,0x6b,0xb1,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x12,0x10,0x94,0x87,0xa2,0x00,0xed,0xc1,0x93,0xa4,0x09,0x45,0x9a,0x92,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x01,0x68,0x42,0x0b,0x00,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x01,0x68,0x63,0x28,0x00,0x00,0x4a,0x21,0x0b,0xff,0xfe,0x26,0x38,0xc3,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x01,0x68,0x67,0x6e,0x00,0x00,0xe6,0x5f,0x01,0xff,0xfe,0x09,0x35,0x91,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x17,0x48,0xf3,0x9f,0x58,0x72,0xde,0xad,0xbe,0xef,0xb1,0xac,0xc0,0xfe,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x01,0x80,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x05,0x17,0x10,0xb6,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x21,0x68,0xa3,0x79,0xd1,0x00,0x96,0xde,0x80,0xff,0xfe,0xa3,0xfd,0x00,0x20,0x8d,
0x02,0x10,0x2a,0x02,0x27,0x80,0x90,0x00,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x27,0x80,0x90,0x00,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x27,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x1a,0x20,0x8d,
0x02,0x10,0x2a,0x02,0x2e,0x02,0x39,0x00,0x54,0x00,0xa0,0x99,0xe1,0xff,0xfe,0xb6,0x0d,0x0e,0x20,0x8d,
- 0x02,0x10,0x2a,0x02,0x03,0x90,0x90,0x00,0x00,0x00,0xaa,0xa1,0x59,0xff,0xfe,0x43,0xb5,0x7b,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x2f,0x05,0x66,0x0e,0x8b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
0x02,0x10,0x2a,0x02,0x00,0x58,0x00,0x97,0x7d,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x20,0x8d,
- 0x02,0x10,0x2a,0x02,0x6d,0x40,0x30,0x5e,0x06,0x01,0xde,0xa6,0x32,0xff,0xfe,0x44,0x4b,0x25,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x6d,0x40,0x30,0x73,0x0c,0x01,0xde,0xa6,0x32,0xff,0xfe,0x44,0x4b,0x25,0x20,0x8d,
0x02,0x10,0x2a,0x02,0x7a,0x01,0x00,0x00,0x00,0x00,0x00,0x91,0x02,0x28,0x00,0x45,0x01,0x30,0x20,0x8d,
- 0x02,0x10,0x2a,0x02,0x7a,0xa0,0x16,0x19,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0xdc,0x8d,0xe0,0x20,0x8d,
- 0x02,0x10,0x2a,0x02,0x7b,0x40,0x3e,0x4d,0x99,0x8d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
- 0x02,0x10,0x2a,0x02,0x7b,0x40,0x59,0x2f,0xa1,0x87,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
- 0x02,0x10,0x2a,0x02,0x83,0x88,0xe5,0xc6,0xd3,0x80,0x02,0x01,0x2e,0xff,0xfe,0x82,0xb3,0xcc,0x20,0x8d,
- 0x02,0x10,0x2a,0x02,0x09,0xa0,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x20,0x8d,
- 0x02,0x10,0x2a,0x02,0xa3,0x11,0x81,0x43,0x8c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x20,0xa1,
- 0x02,0x10,0x2a,0x02,0x0a,0xf8,0xfa,0xb0,0x08,0x08,0x00,0x85,0x02,0x34,0x01,0x45,0x01,0x32,0x20,0x8d,
- 0x02,0x10,0x2a,0x02,0x0e,0x00,0xff,0xf0,0x05,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0xfc,
- 0x02,0x10,0x2a,0x02,0x0e,0x00,0xff,0xf0,0x05,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0x20,0xfc,
+ 0x02,0x10,0x2a,0x02,0x7b,0x40,0x59,0x28,0x00,0x89,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x7b,0x40,0xc3,0xb5,0xf5,0x83,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x83,0x08,0x80,0x87,0xaa,0x00,0x9e,0xa8,0x01,0xb2,0xef,0x98,0x56,0xbf,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0x84,0x2a,0x01,0xdf,0x8a,0x01,0x1e,0x1b,0x0d,0xff,0xfe,0x0b,0x23,0x6d,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0xa4,0x4d,0x14,0xd6,0x00,0x01,0x02,0xc0,0x08,0xff,0xfe,0x8f,0xb3,0xb2,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0xa4,0x5a,0x94,0xcd,0xf0,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0xa4,0x5f,0x3b,0x9d,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0xa4,0x67,0x78,0x33,0x00,0x01,0x72,0x85,0xc2,0xff,0xfe,0x2c,0x21,0xe9,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0xaa,0x14,0x23,0x80,0xb3,0x00,0x40,0x40,0xbe,0x88,0x8b,0x01,0x0d,0x38,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0xc2,0x06,0x20,0x44,0x98,0x26,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0xc2,0x06,0x20,0x82,0x12,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0xc2,0x06,0x30,0x08,0x23,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0xc2,0x07,0x00,0x00,0x49,0x71,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x14,0xd4,
+ 0x02,0x10,0x2a,0x02,0xc2,0x07,0x20,0x14,0x41,0x99,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0xc2,0x07,0x20,0x24,0x61,0x15,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0xc2,0x07,0x20,0x26,0x66,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x02,0xc2,0x07,0x30,0x02,0x74,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
0x02,0x10,0x2a,0x02,0x0e,0x98,0x00,0x20,0x15,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
- 0x02,0x10,0x2a,0x03,0x40,0x00,0x00,0x47,0x00,0xf1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x03,0x40,0x00,0x00,0x06,0x41,0x6c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x43,0x20,0x8d,
+ 0x02,0x10,0x2a,0x03,0x40,0x00,0x00,0x06,0xf8,0x14,0x54,0x8b,0x17,0xff,0xfe,0x31,0xb6,0x4a,0x20,0x8d,
0x02,0x10,0x2a,0x03,0x60,0x00,0x08,0x70,0x00,0x00,0x00,0x46,0x00,0x23,0x00,0x87,0x02,0x18,0x20,0x8d,
- 0x02,0x10,0x2a,0x03,0x73,0x80,0x30,0x15,0x05,0x24,0xaf,0xc5,0xd3,0xbc,0x7c,0x66,0x8f,0x94,0x20,0x8d,
- 0x02,0x10,0x2a,0x03,0x0e,0xc0,0x00,0x00,0x09,0x28,0x8c,0x00,0x93,0xff,0xfe,0x84,0xa0,0x07,0x20,0x8d,
- 0x02,0x10,0x2a,0x03,0x0e,0xc0,0x00,0x00,0x09,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x01,0x20,0x8d,
- 0x02,0x10,0x2a,0x04,0x21,0x80,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xaa,0x20,0x8d,
- 0x02,0x10,0x2a,0x04,0x52,0xc0,0x01,0x01,0x02,0x9e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d,
- 0x02,0x10,0x2a,0x04,0x52,0xc0,0x01,0x03,0xc4,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x03,0x94,0xe0,0xff,0xff,0x01,0x85,0x02,0x43,0x02,0x18,0x00,0x00,0x00,0x19,0x20,0x8d,
+ 0x02,0x10,0x2a,0x03,0xb0,0xc0,0x00,0x01,0x00,0xe0,0x00,0x00,0x00,0x00,0x03,0x97,0x60,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x03,0xb0,0xc0,0x00,0x02,0x00,0xf0,0x00,0x00,0x00,0x00,0x01,0x63,0x30,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x03,0xb0,0xc0,0x00,0x02,0x00,0xf0,0x00,0x00,0x00,0x00,0x01,0x8a,0xd0,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x03,0xb0,0xc0,0x00,0x03,0x00,0xd0,0x00,0x00,0x00,0x00,0x0f,0x3e,0x20,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x03,0xe2,0xc0,0x13,0x47,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x2a,0x03,0x0e,0xc0,0x00,0x00,0x09,0x28,0x00,0x00,0x00,0x00,0x07,0x01,0x07,0x01,0x20,0x8d,
+ 0x02,0x10,0x2a,0x04,0x52,0xc0,0x01,0x03,0xc4,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8e,
+ 0x02,0x10,0x2a,0x04,0x52,0xc0,0x30,0x07,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x20,0x8d,
0x02,0x10,0x2a,0x04,0xbc,0x40,0x1d,0xc3,0x00,0x8d,0x00,0x00,0x00,0x00,0x00,0x02,0x10,0x01,0x20,0x8d,
0x02,0x10,0x2a,0x05,0x15,0x00,0x07,0x02,0x00,0x00,0x1c,0x00,0x40,0xff,0xfe,0x00,0x00,0x0c,0x20,0x8d,
- 0x02,0x10,0x2a,0x06,0xdd,0x00,0x00,0x10,0x00,0x03,0x02,0x25,0x90,0xff,0xfe,0x32,0x64,0xcc,0x20,0x8d,
- 0x02,0x10,0x2a,0x06,0xdd,0x00,0x00,0x01,0x00,0x22,0x02,0x25,0x90,0xff,0xfe,0x0e,0xbd,0x48,0x20,0x8d,
- 0x02,0x10,0x2a,0x07,0x6b,0x47,0x01,0x00,0x04,0x64,0x00,0x00,0x00,0x00,0x93,0x57,0xff,0xda,0x20,0x8d,
- 0x02,0x10,0x2a,0x07,0xa8,0x80,0x46,0x01,0x10,0x62,0xb4,0xb4,0xbd,0x2a,0x39,0xd4,0x7a,0xcf,0xc8,0xc9,
+ 0x02,0x10,0x2a,0x05,0x35,0x80,0xd1,0x01,0x37,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d,
+ 0x02,0x10,0x2a,0x05,0x35,0x80,0xdb,0x0b,0x16,0x00,0xc4,0x89,0x76,0xed,0x31,0x3d,0x0b,0x33,0x20,0x8d,
+ 0x02,0x10,0x2a,0x05,0xd0,0x14,0x0a,0x55,0x40,0x01,0x81,0x27,0xaf,0xa7,0xda,0xf9,0xd9,0x1b,0x20,0x8d,
+ 0x02,0x10,0x2a,0x05,0xd0,0x14,0x0a,0x55,0x40,0x01,0xf6,0xab,0xdd,0x5e,0x40,0x39,0xb4,0x6c,0x20,0x8d,
+ 0x02,0x10,0x2a,0x05,0xd0,0x14,0x0a,0x55,0x40,0x03,0x65,0x23,0x50,0xa1,0x01,0x52,0xe8,0x8c,0x20,0x8d,
+ 0x02,0x10,0x2a,0x05,0xd0,0x1a,0x0b,0x7b,0x3c,0x01,0x8b,0xf7,0xae,0x14,0xaf,0xb3,0x33,0xae,0x20,0x8d,
+ 0x02,0x10,0x2a,0x05,0xf4,0x80,0x18,0x00,0x06,0x97,0x54,0x00,0x02,0xff,0xfe,0xb6,0xc3,0x6d,0x20,0x8d,
+ 0x02,0x10,0x2a,0x06,0xe0,0x40,0x76,0x03,0x29,0x18,0xc6,0xef,0x46,0x4e,0x9f,0xe5,0x73,0xec,0x20,0x8d,
0x02,0x10,0x2a,0x07,0xab,0xc4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x09,0x46,0x20,0x8d,
- 0x02,0x10,0x2a,0x07,0xab,0xc4,0x00,0x00,0x00,0x00,0x00,0x89,0x02,0x34,0x01,0x80,0x01,0x94,0x20,0x8d,
0x02,0x10,0x2a,0x09,0x26,0x81,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x10,0x20,0x8d,
0x02,0x10,0x2a,0x0a,0xc8,0x01,0x00,0x01,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x83,0x20,0x8d,
- 0x02,0x10,0x2a,0x0b,0xf3,0x00,0x00,0x02,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
- 0x02,0x10,0x2a,0x0c,0x59,0xc0,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xa2,0x0e,0xe1,0x3a,
+ 0x02,0x10,0x2a,0x0c,0x5a,0x80,0x12,0x10,0xa8,0x00,0x6a,0xf7,0x28,0xff,0xfe,0xe5,0x6b,0x3a,0x20,0x8d,
0x02,0x10,0x2a,0x0d,0x56,0x00,0x00,0x24,0x0a,0x8e,0x00,0x00,0x00,0x00,0x00,0x00,0xa9,0x1e,0xd8,0x4d,
- 0x02,0x10,0x2a,0x0d,0xeb,0x00,0x80,0x05,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0x20,0x8d,
- 0x02,0x10,0x2a,0x10,0x47,0x40,0x00,0x45,0x00,0x01,0xa0,0x13,0xd1,0xff,0xfe,0x85,0x36,0xe3,0x20,0x8d,
- 0x02,0x10,0x2a,0x10,0x8b,0x40,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x20,0x8f,
+ 0x02,0x10,0x2a,0x0d,0x7c,0x40,0x30,0x00,0x0b,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x2a,0x0d,0x83,0x40,0x00,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d,
+ 0x02,0x10,0x2a,0x0f,0xdf,0x00,0x00,0x00,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x62,0x20,0x8d,
+ 0x02,0x10,0x2a,0x10,0x37,0x81,0x16,0xb9,0x00,0x01,0xfe,0x3f,0xdb,0xff,0xfe,0x04,0x2d,0x4c,0x20,0x8d,
+ 0x02,0x10,0x2a,0x10,0x37,0x81,0x08,0x4b,0x00,0x01,0xb1,0x23,0x63,0x06,0x94,0x3a,0xf0,0x9b,0x20,0x8d,
+ 0x02,0x10,0x2a,0x10,0xd2,0x00,0x00,0x01,0x00,0x33,0xa6,0xbf,0x01,0xff,0xfe,0x6a,0x46,0xa9,0x20,0x8d,
+ 0x02,0x10,0x2c,0x0f,0xf4,0xc0,0x22,0x02,0x20,0xb0,0x26,0x1c,0x04,0xff,0xfe,0x14,0xda,0xa0,0x20,0x8d,
0x02,0x10,0x2c,0x0f,0xf8,0xf0,0xda,0x51,0x00,0x00,0x70,0xc3,0xee,0xa9,0x97,0x17,0x95,0x79,0x20,0x8d,
- 0x04,0x20,0xd1,0xbb,0x02,0x8d,0x4d,0xd5,0x6a,0x20,0xc0,0xf9,0x16,0x2b,0x84,0x22,0x66,0xe0,0x89,0x45,0x60,0x37,0x52,0xe2,0x0b,0xa5,0xb4,0xf8,0x26,0xb3,0x8f,0x5a,0x30,0xed,0x20,0x8d,
- 0x04,0x20,0xd2,0x59,0x3b,0xd7,0x14,0x7e,0xd0,0x98,0xfe,0x9e,0xa5,0x69,0xf4,0x26,0x6d,0x72,0x6f,0xc3,0x76,0xce,0x1d,0x40,0x41,0xa2,0xa1,0xaf,0xf9,0x6e,0x57,0x2d,0x9d,0xc3,0x20,0x8d,
- 0x04,0x20,0xdf,0xd9,0xed,0x59,0xbf,0x1e,0x77,0x48,0x3c,0x13,0x3b,0xc5,0xc8,0x15,0x86,0x88,0x68,0xf0,0x08,0xe9,0xee,0x9b,0x3d,0xa4,0x33,0x0a,0x68,0x67,0x86,0x9d,0xe2,0x83,0x20,0x8d,
0x04,0x20,0xe9,0xbf,0xa7,0xbd,0x9b,0x54,0x54,0xe8,0xc8,0xae,0x78,0x99,0xa0,0xa3,0xf6,0x5d,0x78,0xe3,0x9e,0x5c,0xa7,0x18,0xb9,0x13,0x0c,0x04,0x9b,0xf3,0x7f,0x27,0x18,0xb0,0x20,0x8d,
- 0x04,0x20,0xf8,0x8d,0x64,0xd2,0xc8,0xe9,0x0f,0x51,0x03,0x1c,0x98,0x33,0x8f,0xe0,0x1e,0xe7,0xb6,0x16,0x8d,0x2a,0xf5,0xf3,0x19,0xce,0xdd,0x9e,0xee,0x17,0xc3,0x8f,0xd6,0xa1,0x20,0x8d,
- 0x04,0x20,0xfb,0xf1,0x17,0xd6,0x03,0x3b,0x01,0x8b,0x98,0xcf,0x16,0x20,0xde,0xaf,0x6c,0xed,0x60,0xab,0x6e,0x14,0x0b,0x58,0x6b,0x2d,0xf8,0x06,0x98,0x37,0x7a,0xff,0x7a,0x0f,0x20,0x8d,
0x04,0x20,0x0f,0xb9,0x71,0x05,0x64,0x83,0x2c,0x68,0x6a,0x9c,0xf0,0x4f,0xc3,0x90,0xcd,0x5c,0x73,0x9a,0xdd,0xb3,0xc6,0x42,0xca,0x09,0xbb,0xcc,0xfe,0x29,0x49,0x9f,0xc7,0x28,0x20,0x8d,
- 0x04,0x20,0x22,0x6e,0x42,0xe4,0xbd,0x2b,0xe5,0x3e,0x30,0xda,0x8a,0x03,0xf3,0x45,0x52,0xac,0x84,0xbf,0xbf,0xc5,0xaa,0x5f,0xe0,0x1b,0x26,0x28,0xb5,0x83,0x2e,0xed,0x4c,0xee,0x20,0x8d,
0x04,0x20,0x2a,0x47,0x8b,0xa0,0x4f,0x67,0x1d,0xcd,0x5d,0x84,0x1a,0xec,0xbd,0xd2,0xaa,0xe9,0x99,0x01,0x96,0x5d,0x4e,0xff,0x64,0x47,0xba,0xde,0xbf,0x56,0x89,0x39,0xac,0xde,0x20,0x8d,
0x04,0x20,0x2b,0xf3,0xe8,0xf5,0xef,0x90,0x14,0xab,0x61,0xe9,0x11,0x97,0x9f,0x18,0x4d,0xb4,0xff,0x89,0x94,0xf7,0x92,0x94,0x53,0xe6,0x9e,0xd4,0xdb,0x85,0x89,0x4d,0x3e,0xc9,0x20,0x8d,
- 0x04,0x20,0x2e,0x4e,0xde,0x51,0xd7,0x28,0x4b,0x29,0x7c,0xff,0x1f,0x8a,0x50,0xb7,0x5e,0xf0,0x81,0xcd,0xe8,0x8a,0x08,0x73,0x58,0x4e,0x43,0x1f,0x7b,0x85,0x9a,0xed,0xe2,0x68,0x20,0x8d,
0x04,0x20,0x35,0xdd,0xd0,0x36,0xa5,0x69,0x4a,0xd2,0xcc,0xb8,0xe9,0x62,0xa3,0x55,0xeb,0x86,0xe2,0xf3,0x03,0x48,0x26,0xe6,0x20,0xad,0xda,0xaa,0xff,0xde,0x16,0xad,0x39,0x9d,0x20,0x8d,
0x04,0x20,0x41,0x47,0x4e,0xc2,0xa1,0x71,0x63,0x3e,0x11,0x54,0x46,0x91,0x80,0xed,0x41,0x16,0x32,0x29,0x19,0x60,0xc9,0xef,0xa3,0xb7,0x96,0x2c,0x94,0xa8,0xdf,0x55,0xd7,0x21,0x20,0x8d,
0x04,0x20,0x44,0xf3,0xb7,0x5e,0x48,0x3c,0xbd,0xa6,0x52,0xaa,0x68,0xb5,0xbf,0xdc,0x01,0x5f,0x4b,0xeb,0x7a,0x25,0xcb,0x4a,0x70,0xbc,0x18,0x8c,0x97,0x5d,0x27,0x54,0x09,0x17,0x20,0x8d,
- 0x04,0x20,0x5c,0x52,0x7f,0x17,0x16,0x4c,0x27,0x36,0x2d,0x05,0xa1,0x19,0x0d,0xbe,0x87,0xab,0x24,0x7b,0xe7,0x38,0x3b,0xa1,0x7f,0xd1,0xd4,0x28,0x16,0x8e,0xfc,0x98,0x7d,0x08,0x20,0x8d,
+ 0x04,0x20,0x53,0xcd,0x56,0x48,0x48,0x8c,0x47,0x07,0x91,0x41,0x82,0x65,0x5b,0x76,0x64,0x03,0x4e,0x09,0xe6,0x6f,0x7e,0x8c,0xbf,0x10,0x84,0xe6,0x54,0xeb,0x56,0xc5,0xbd,0x88,0x20,0x8d,
0x04,0x20,0x67,0xc4,0x17,0xa5,0xcb,0x77,0xbd,0xaa,0x11,0x7f,0x8b,0xc0,0x81,0xf3,0xc0,0x96,0x9d,0x31,0x27,0x9c,0xad,0x6c,0x6d,0x98,0x42,0x70,0xdb,0x50,0x12,0x96,0x0b,0x36,0x20,0x8d,
- 0x04,0x20,0x73,0xdb,0x82,0xe0,0x88,0x40,0x49,0xd8,0x3b,0xa0,0xdd,0x83,0x7c,0x84,0x3c,0xb8,0xd0,0x03,0x0b,0x7a,0x08,0x44,0x4e,0x79,0xd6,0x61,0x23,0x31,0xa9,0xb3,0x07,0x58,0x20,0x8d,
- 0x04,0x20,0x75,0x93,0x21,0xdd,0x99,0x58,0x3c,0x3f,0xae,0x36,0x50,0x58,0x49,0xe2,0xd0,0xc3,0x3a,0x2c,0x4a,0xcf,0x41,0xc4,0x82,0x48,0xab,0xec,0x07,0x5d,0x56,0x2c,0xb4,0x8d,0x20,0x8d,
- 0x04,0x20,0x87,0xd4,0x66,0x0f,0xed,0xf9,0xf5,0xf1,0xcb,0x85,0x37,0xec,0xe1,0x19,0xa8,0xa4,0x03,0xb7,0x13,0x59,0xbb,0xf8,0xd2,0x93,0x92,0x50,0xfa,0x30,0x7a,0xd8,0x43,0xd0,0x20,0x8d,
+ 0x04,0x20,0x65,0x98,0x55,0xd0,0x8a,0xe0,0x29,0xe6,0x5e,0xef,0xb7,0x8b,0x8f,0xc9,0x27,0x53,0x3d,0xd0,0x8c,0xa2,0xfa,0x32,0x2f,0xad,0xf9,0xdc,0xe2,0x4b,0x14,0x66,0x3e,0x23,0x20,0x8d,
0x04,0x20,0x8b,0xfe,0xad,0x19,0xdb,0x97,0x57,0x84,0xec,0xad,0x4f,0xb2,0xdf,0x69,0x53,0x04,0x57,0x19,0x16,0x7a,0x71,0xd7,0x2b,0xab,0x03,0xfd,0x76,0x4d,0xa0,0x70,0xc3,0xe7,0x20,0x8d,
- 0x04,0x20,0x96,0x25,0xde,0x4a,0xbc,0xbd,0x76,0x76,0xee,0x43,0x45,0x76,0xe0,0x0d,0x99,0x83,0xcd,0x83,0x8f,0x94,0xe5,0xde,0x7a,0xf2,0xf0,0x57,0xb8,0x25,0x54,0x17,0xcb,0x3b,0x20,0x8d,
- 0x04,0x20,0x98,0xc6,0x44,0x27,0x90,0x41,0xa6,0x98,0xf9,0x25,0x6c,0x59,0x0f,0x06,0x6d,0x44,0x59,0x0e,0xb2,0x46,0xb0,0xa4,0x37,0x88,0x69,0x8f,0xc1,0x32,0xcd,0x9f,0x15,0xd7,0x20,0x8d,
- 0x04,0x20,0xaa,0x3a,0x16,0x86,0xea,0x59,0x09,0x04,0x78,0xe5,0x10,0x92,0xe1,0x1d,0xad,0xf7,0x56,0x2b,0xac,0xb0,0x97,0x29,0x63,0x30,0xf4,0x1b,0xcf,0xde,0xf3,0x28,0x0a,0x29,0x20,0x8d,
- 0x04,0x20,0xbc,0x27,0xae,0x89,0xc1,0x67,0x73,0x0a,0x08,0x02,0xdf,0xb7,0xcc,0x94,0xc7,0x9f,0xf4,0x72,0x7a,0x9b,0x20,0x0c,0x5c,0x11,0x3d,0x22,0xd6,0x13,0x88,0x66,0x74,0xbf,0x20,0x8d,
+ 0x05,0x20,0xd7,0x7a,0x53,0x89,0xfe,0x02,0x6a,0x59,0xb7,0x0e,0xf8,0x6d,0x9d,0x81,0xbb,0x9f,0x01,0xc0,0xc4,0xee,0x7b,0x36,0x10,0x33,0x07,0xd2,0x29,0xd8,0xec,0xcd,0x8e,0xa3,0x00,0x00,
+ 0x05,0x20,0xd7,0xf1,0x19,0x9e,0x7d,0x0f,0x43,0x97,0x33,0x56,0xe8,0x12,0x1d,0x7d,0xa0,0x4d,0x21,0x5a,0x60,0x73,0xc8,0x7e,0x10,0x55,0x60,0x56,0xbb,0x65,0x50,0xa4,0x17,0x59,0x00,0x00,
+ 0x05,0x20,0xd1,0x17,0xe2,0x34,0x4a,0x61,0x70,0x8b,0x04,0xa2,0xb4,0xb0,0x65,0x59,0x52,0x75,0x67,0x86,0xe6,0x48,0x33,0x31,0x5d,0x87,0x38,0x57,0xd3,0xf8,0x40,0x07,0x73,0xa8,0x00,0x00,
+ 0x05,0x20,0xd9,0x9c,0x20,0xfe,0xc2,0xe6,0x6a,0x16,0x30,0x81,0x54,0xc9,0x3f,0x9a,0x89,0x10,0xa9,0x4b,0xf1,0x05,0x56,0xd5,0x04,0x2d,0xb7,0x6a,0x7b,0x67,0x8d,0xf0,0xbe,0x8f,0x00,0x00,
+ 0x05,0x20,0xdc,0xdb,0x2d,0x39,0xd5,0xe4,0xda,0xb5,0xb6,0x6e,0x98,0x33,0x8f,0x51,0x99,0x54,0x38,0x53,0xa8,0x4e,0xda,0x29,0xd8,0xb5,0x57,0x9d,0x36,0xf0,0x97,0x37,0x18,0x3f,0x00,0x00,
+ 0x05,0x20,0xe1,0x44,0x2d,0x6e,0xd3,0xd9,0xf0,0x95,0x6c,0x52,0x2e,0x44,0x3c,0x27,0x3d,0x78,0xac,0x6e,0x8f,0x27,0x1c,0x0c,0xc0,0x78,0x22,0x3e,0xa1,0x84,0x01,0x42,0x08,0x5c,0x00,0x00,
+ 0x05,0x20,0xe3,0xa5,0x88,0x11,0x4d,0x3d,0xfb,0x02,0xec,0x1f,0xda,0x48,0x86,0x12,0xf6,0x12,0xd9,0x3e,0x68,0x49,0xa7,0xae,0x37,0xfd,0x02,0x48,0x38,0x8b,0xdc,0xd4,0xa6,0x8f,0x00,0x00,
+ 0x05,0x20,0xe5,0x19,0x24,0x71,0xab,0x61,0xb0,0xfe,0x44,0x4a,0x74,0x8d,0xca,0x90,0xc3,0xd6,0x24,0xb4,0xd5,0x03,0xe7,0xf3,0x4f,0xbe,0x12,0x72,0xd6,0xa0,0x4b,0x22,0x0b,0xe1,0x00,0x00,
+ 0x05,0x20,0xee,0xab,0xea,0x3b,0xc2,0x8a,0xe2,0xb3,0xe5,0xe7,0x92,0xf0,0x88,0x05,0x68,0x8d,0x2f,0xe7,0x2c,0xd2,0x00,0x39,0xd4,0x5d,0x07,0xfc,0xa1,0xd6,0xbf,0x89,0xa2,0x40,0x00,0x00,
+ 0x05,0x20,0xf2,0x74,0x4c,0x90,0xc3,0xd9,0x34,0x4d,0x5f,0x6e,0xdb,0xdd,0x7d,0xef,0xa5,0xed,0x6e,0x59,0x9e,0x31,0x41,0x94,0x38,0x84,0xc5,0x08,0xd2,0x23,0xb3,0xa7,0xe0,0x2c,0x00,0x00,
+ 0x05,0x20,0xf3,0x77,0xe5,0xa7,0x11,0xef,0x65,0x91,0x23,0xb8,0x32,0x06,0xcb,0xc0,0x91,0xf7,0x21,0x1d,0x70,0xbc,0x83,0x1b,0x86,0x34,0x35,0x31,0x0f,0x9f,0xc1,0x0d,0xbb,0x56,0x00,0x00,
+ 0x05,0x20,0xfe,0xb0,0x99,0x79,0x95,0x58,0x71,0xb5,0x63,0xcc,0x33,0xeb,0x55,0x91,0x8c,0xb4,0x3a,0xf2,0x8b,0x2d,0x8e,0x47,0xbe,0x25,0x47,0x12,0xcd,0x14,0x48,0xf0,0x1d,0xea,0x00,0x00,
+ 0x05,0x20,0xfc,0x79,0x14,0x77,0x6b,0x0e,0x34,0x8d,0xde,0x01,0x33,0xed,0xb4,0x0e,0xa7,0xc9,0x15,0x4f,0xd0,0x27,0x2c,0xd6,0x5f,0xe9,0x63,0x82,0x8d,0xd5,0x0c,0x9e,0x18,0x29,0x00,0x00,
0x05,0x20,0x07,0x61,0x26,0xd7,0x6c,0x05,0xbf,0xf6,0x2d,0x8c,0xca,0xc4,0x65,0xd3,0xd3,0xb2,0x49,0xe9,0xcc,0x53,0x1e,0xca,0x77,0x84,0xb6,0x10,0x5e,0xc2,0x5a,0xfe,0x28,0xb3,0x00,0x00,
- 0x05,0x20,0x0a,0x26,0x27,0x45,0xb1,0x1e,0xfc,0x27,0x03,0x32,0x0e,0x65,0x9e,0x3c,0x64,0x0e,0x33,0x50,0x3d,0x6c,0x90,0x17,0x0e,0x29,0xee,0x5a,0x58,0xdf,0x08,0xde,0xbf,0x73,0x00,0x00,
+ 0x05,0x20,0x03,0xaa,0x47,0xe9,0xe2,0x77,0xeb,0xa5,0x72,0x27,0x23,0x8b,0x13,0x62,0x61,0x32,0xb5,0xb2,0x1b,0x5a,0x18,0xb2,0xf9,0x26,0x06,0x84,0xee,0x28,0x42,0xac,0xba,0xbc,0x00,0x00,
+ 0x05,0x20,0x08,0xc6,0x19,0x31,0x40,0x96,0xf3,0xe2,0x81,0x4e,0x88,0x54,0x54,0x9e,0xbf,0xfa,0x38,0x7a,0xfa,0x38,0x96,0x13,0x2a,0xc4,0x69,0xa2,0xae,0xe5,0x94,0xc7,0x16,0xb7,0x00,0x00,
+ 0x05,0x20,0x0a,0x26,0x27,0x23,0xdd,0xf3,0x56,0xbe,0x9e,0x9e,0xa7,0xc6,0x3c,0xc5,0x99,0xc4,0x87,0x3b,0x4d,0xb9,0x13,0x62,0x91,0xf2,0x25,0x1c,0x02,0x42,0x63,0xe3,0x63,0x7a,0x00,0x00,
+ 0x05,0x20,0x0c,0x50,0x55,0x46,0x87,0x5a,0x8d,0x14,0xfb,0xa7,0x29,0x70,0x18,0xa6,0x29,0x80,0x8c,0x33,0x42,0x5a,0x8f,0xe4,0x84,0x64,0x3d,0x0e,0xb5,0xbd,0x36,0x34,0x42,0xb6,0x00,0x00,
0x05,0x20,0x17,0x0c,0x56,0xce,0x72,0xa5,0xa0,0xe6,0x23,0x06,0xa3,0xc7,0x08,0x43,0x18,0xee,0x3a,0x46,0x35,0x5d,0x17,0xf6,0x78,0x96,0xa0,0x9c,0x51,0xef,0xbe,0x23,0xfd,0x71,0x00,0x00,
- 0x05,0x20,0x19,0xe7,0x0d,0x3f,0xfe,0x9e,0x0e,0x8e,0x73,0x40,0x40,0xc3,0xba,0x8f,0x41,0xaf,0xf1,0x7b,0xa6,0x83,0x1b,0xc3,0xa4,0xe0,0x6d,0x6c,0x57,0xa7,0x36,0x5d,0x09,0xce,0x00,0x00,
+ 0x05,0x20,0x18,0x31,0xb3,0x9a,0xf8,0x8c,0xec,0x99,0x2e,0x7d,0xe4,0x90,0xa2,0x54,0x27,0xbd,0xe5,0xc8,0x65,0xdf,0x1f,0xaa,0x8f,0xe9,0x0f,0x64,0x85,0x09,0xc3,0x70,0x62,0x13,0x00,0x00,
+ 0x05,0x20,0x1a,0x35,0x98,0x78,0xb1,0xd9,0x48,0x62,0xe9,0x23,0x10,0xfe,0x71,0xdb,0x10,0x5f,0x28,0xc8,0xf3,0xda,0x33,0x9b,0x26,0xcb,0x4d,0xbe,0x7b,0x03,0x76,0xb9,0xe0,0x54,0x00,0x00,
+ 0x05,0x20,0x27,0x7a,0xaf,0x5a,0x9c,0xf4,0x72,0xfe,0x3c,0xdd,0x7a,0xba,0xd7,0x98,0x31,0xde,0x73,0xce,0x84,0x5b,0x41,0xe7,0x9a,0x6a,0xe2,0xc1,0x3b,0x5b,0x37,0x23,0xc7,0xdf,0x00,0x00,
+ 0x05,0x20,0x20,0x90,0xe3,0xd3,0xad,0x87,0xeb,0x2a,0xd9,0x29,0x17,0x74,0x47,0xc9,0x54,0x57,0xfa,0x3d,0x71,0x02,0x11,0xb2,0xc3,0x87,0x31,0xb3,0x9b,0x6f,0x2e,0xfc,0x30,0xea,0x00,0x00,
+ 0x05,0x20,0x22,0x56,0xd6,0x98,0x11,0x61,0xe1,0x5a,0x34,0x9f,0xe2,0x9d,0xf5,0x2b,0xbd,0xbc,0xcc,0x1c,0xf5,0x1d,0x68,0xa5,0xca,0xb1,0xb5,0x4b,0xf1,0xb5,0xff,0x1e,0xdd,0xc0,0x00,0x00,
+ 0x05,0x20,0x37,0x3e,0x28,0x39,0xef,0xa6,0xbc,0xf8,0xf1,0xba,0x11,0x40,0x87,0x42,0xff,0x58,0x46,0x8f,0xb0,0x80,0xfe,0x20,0x75,0xc5,0x43,0xce,0xec,0x9b,0x5d,0x37,0x19,0xf4,0x00,0x00,
0x05,0x20,0x3e,0xe3,0xe0,0xa9,0xbc,0xf4,0x2e,0x59,0xd9,0x20,0xee,0xdf,0x74,0x61,0x4d,0x99,0x0c,0x5c,0x15,0x30,0x9b,0x72,0x16,0x79,0x15,0xf4,0x7a,0xca,0x34,0xcc,0x81,0x99,0x00,0x00,
- 0x05,0x20,0x3b,0x42,0x1c,0x25,0xf7,0xbf,0x79,0xed,0x6d,0x7d,0xef,0x65,0x30,0x7d,0xee,0x16,0x37,0x22,0x72,0x43,0x33,0x28,0x40,0xa3,0xaa,0xf4,0x48,0x49,0x67,0xb1,0x4b,0xfd,0x00,0x00,
+ 0x05,0x20,0x39,0xca,0x8e,0x62,0x0a,0x36,0xa7,0x68,0x22,0xc4,0xcc,0x4a,0xa9,0x16,0x69,0x4b,0x8a,0x1c,0x5f,0x6e,0x4a,0x98,0xb6,0x95,0x82,0xb3,0x66,0x66,0xc5,0x29,0x3a,0xb0,0x00,0x00,
+ 0x05,0x20,0x3b,0xd0,0x80,0xc4,0xab,0x82,0x83,0x11,0x48,0xe5,0x3b,0x23,0x3b,0x17,0x04,0x0f,0xd8,0x39,0x7c,0x1b,0x70,0x73,0x68,0x98,0x11,0x22,0x49,0x51,0x8f,0x20,0x65,0x65,0x00,0x00,
+ 0x05,0x20,0x47,0x1f,0x83,0xc8,0xa3,0x87,0x35,0xcf,0x8e,0x8d,0x22,0xfe,0xf0,0x02,0x63,0x04,0x3a,0x6a,0x49,0x1a,0x3b,0x70,0x17,0xeb,0x05,0xed,0xaf,0x61,0xb6,0x26,0xb8,0x21,0x00,0x00,
+ 0x05,0x20,0x45,0xbd,0x33,0x3d,0x81,0x1e,0x14,0x52,0x88,0x8a,0xa7,0x46,0x0f,0x37,0x25,0xd0,0x59,0x9f,0x78,0xb5,0x7f,0x4a,0xf1,0x54,0x8c,0x2d,0x36,0x32,0xf8,0x10,0xf3,0xcc,0x00,0x00,
+ 0x05,0x20,0x4a,0x8b,0x40,0x25,0xdc,0x06,0x2a,0xed,0x44,0x35,0xec,0x06,0x9e,0x73,0x70,0xf0,0x07,0x06,0x35,0xd1,0x60,0x4f,0x22,0xe8,0xbf,0x8a,0xdf,0xd9,0xeb,0x97,0x73,0x06,0x00,0x00,
0x05,0x20,0x4e,0x77,0x2e,0x12,0x91,0x67,0x6b,0x94,0xc4,0x92,0x2f,0x19,0x67,0x7d,0xcd,0x47,0x02,0xad,0xf8,0x60,0x72,0xed,0x73,0xf1,0x10,0x99,0x2c,0x05,0x61,0x66,0x55,0xd9,0x00,0x00,
- 0x05,0x20,0x53,0x94,0xa6,0x3e,0x14,0x82,0xd4,0xf9,0xd3,0xa7,0x53,0x33,0x05,0xce,0x72,0x64,0xed,0x74,0x09,0x63,0x8f,0x24,0xef,0xda,0x12,0xa1,0x55,0xe0,0xd8,0xbb,0xd3,0x58,0x00,0x00,
+ 0x05,0x20,0x5a,0x29,0xfe,0x8a,0xaa,0x9d,0x78,0x81,0x04,0x53,0x37,0xf5,0x6f,0xb6,0xe1,0x57,0x08,0x80,0xcf,0xf6,0x03,0x11,0x92,0x8d,0x08,0xe3,0x99,0x9f,0x98,0x4a,0x27,0x6b,0x00,0x00,
+ 0x05,0x20,0x5a,0x65,0x0a,0x52,0x9b,0xc7,0x2e,0x92,0x79,0x7b,0xd3,0xf7,0xa4,0x35,0xbe,0x8e,0xb1,0xea,0x2d,0x7e,0x73,0x84,0x6a,0x3f,0xc9,0x9a,0x62,0xe3,0xc6,0x17,0x0e,0x7c,0x00,0x00,
+ 0x05,0x20,0x5c,0x40,0x7f,0x80,0x43,0x91,0x9c,0xfc,0x04,0xdc,0xdc,0x8e,0x01,0xda,0xc8,0xaf,0x90,0x62,0x64,0x16,0xf7,0x11,0xe4,0x87,0xac,0xa4,0x06,0x6f,0x8d,0x87,0x4e,0xd6,0x00,0x00,
+ 0x05,0x20,0x5e,0x69,0x4f,0x31,0x33,0xa7,0xea,0x3e,0xf4,0x7a,0x0a,0x1e,0x74,0x09,0x07,0xa1,0x50,0xf7,0x03,0xf5,0xc6,0x19,0xeb,0x95,0xaa,0x63,0x17,0x10,0x6e,0x68,0xca,0x10,0x00,0x00,
+ 0x05,0x20,0x67,0x82,0xfc,0x36,0xea,0xae,0x95,0x3b,0x5d,0x46,0xf3,0xf4,0x6c,0x50,0x69,0x29,0xc7,0x47,0x87,0xca,0xa6,0x40,0x12,0x40,0x6d,0x12,0x94,0x35,0x17,0x8a,0xba,0x56,0x00,0x00,
+ 0x05,0x20,0x67,0xab,0xce,0xf2,0xe3,0xf3,0xf6,0x19,0xf6,0x56,0xa2,0x4c,0xca,0x91,0x4b,0x93,0xfe,0xbc,0x85,0x50,0x5c,0x20,0x51,0x69,0xfb,0xf0,0x92,0xbb,0x57,0xa3,0x0d,0x05,0x00,0x00,
+ 0x05,0x20,0x62,0xcc,0x44,0x66,0x31,0x76,0x1b,0x43,0xbe,0xe1,0xc9,0x1d,0x20,0x26,0x7d,0xa7,0x06,0x31,0x57,0x0d,0xb9,0x58,0x20,0xb3,0xca,0xb2,0x5e,0x0a,0x15,0x0b,0xba,0x0d,0x00,0x00,
+ 0x05,0x20,0x71,0x68,0x1b,0xc7,0x48,0x8f,0xe9,0xa3,0x53,0x29,0xb6,0x23,0x5a,0x25,0x02,0x45,0x72,0xca,0xa1,0xb6,0x06,0xfc,0xda,0x65,0x71,0x37,0xe6,0xd9,0x30,0x81,0x02,0xfe,0x00,0x00,
+ 0x05,0x20,0x72,0x8b,0x72,0x38,0xfe,0x44,0xe9,0xc2,0xf4,0xbc,0xd8,0xce,0x1c,0xd5,0x50,0xb1,0x63,0x56,0x74,0x5e,0xf2,0xd3,0xe5,0xd5,0x29,0xe4,0x34,0x1d,0xf5,0x1c,0x7a,0xb9,0x00,0x00,
+ 0x05,0x20,0x76,0x74,0x80,0x6c,0xab,0x7b,0x36,0x3a,0x6a,0x78,0xa4,0xa8,0xb8,0xb7,0xe7,0xf9,0x34,0x47,0x6d,0x34,0xca,0xa2,0xc6,0xef,0x81,0xab,0x62,0xb1,0x46,0x86,0xaf,0xd0,0x00,0x00,
+ 0x05,0x20,0x80,0xfc,0x95,0xc8,0x95,0x91,0x2f,0x6b,0x6e,0xc4,0x2b,0xe1,0x2f,0xa0,0xcb,0xb8,0x76,0x5f,0x6f,0x1c,0x27,0xb0,0x3a,0x2c,0xb9,0x8d,0x6c,0x55,0xbd,0x02,0x60,0xe0,0x00,0x00,
+ 0x05,0x20,0x80,0xc6,0x6f,0xb3,0x18,0x5a,0x1a,0xde,0x4e,0xde,0x50,0xd2,0xc6,0x3f,0xc5,0x96,0x09,0x35,0x3a,0x4d,0x88,0x5f,0xa3,0x49,0x37,0xff,0xe6,0xc5,0x43,0x10,0xaf,0xa8,0x00,0x00,
+ 0x05,0x20,0x8a,0x32,0x54,0x37,0x12,0x24,0xb5,0x1d,0xba,0x3c,0x45,0x03,0x2e,0xda,0xfe,0xf1,0x87,0x8f,0x31,0xe5,0xfe,0xed,0xfa,0x38,0x25,0x00,0x99,0xa5,0xf4,0x51,0x02,0xf7,0x00,0x00,
+ 0x05,0x20,0x97,0x4e,0x74,0xcd,0x8b,0x37,0x76,0x15,0x3b,0x6d,0xb5,0xa4,0xa0,0x4b,0xf7,0x34,0x98,0x48,0x67,0x1e,0x99,0xe7,0x24,0xf4,0x00,0xf7,0x06,0x9c,0x45,0x69,0x34,0x01,0x00,0x00,
0x05,0x20,0x91,0x06,0xd1,0x9e,0xbd,0xab,0xc4,0x61,0xb3,0x0a,0xc2,0x3b,0x29,0xf3,0x10,0x38,0xee,0xbd,0x9d,0xe3,0x99,0x97,0x30,0x70,0x6e,0xe6,0xfb,0x6a,0x3c,0x07,0x3d,0xfd,0x00,0x00,
+ 0x05,0x20,0x9c,0x97,0xc1,0xad,0xf4,0xd2,0x07,0xae,0xe8,0x3e,0x14,0x42,0x36,0x85,0x71,0xa0,0xa7,0xef,0x72,0x44,0xf1,0x74,0x8b,0x6f,0xa5,0xa4,0x2c,0x0d,0xcc,0x9f,0xb0,0x9d,0x00,0x00,
+ 0x05,0x20,0x9d,0x0d,0x0f,0x58,0x1a,0x5c,0xb4,0x1a,0xeb,0xef,0x8e,0x91,0x8d,0x8c,0x1b,0x57,0x5d,0x6d,0x97,0x24,0x28,0x45,0x54,0x8a,0x3a,0xd5,0x05,0xfb,0x76,0xac,0x25,0x52,0x00,0x00,
+ 0x05,0x20,0xa4,0xb3,0x30,0x54,0x28,0x0f,0xfb,0xe5,0x76,0xb0,0x31,0xb2,0x65,0x62,0x56,0x72,0x7c,0xc9,0xcd,0x07,0x4d,0x5f,0xb1,0x69,0xe0,0xf7,0x35,0x3d,0x30,0x3c,0x7d,0x64,0x00,0x00,
+ 0x05,0x20,0xa9,0xa9,0xe5,0xae,0x01,0xc2,0x5e,0x76,0x2f,0x5d,0xa3,0x07,0xdc,0xce,0xb8,0xbc,0x6f,0x47,0xaf,0x3a,0x37,0xf8,0x5c,0x86,0xff,0xe9,0xb6,0xa5,0x00,0x93,0x76,0x11,0x00,0x00,
+ 0x05,0x20,0xb2,0x63,0x45,0xf5,0x36,0xb0,0x79,0x58,0x0d,0x8a,0x54,0x52,0x16,0x2f,0x1f,0x74,0x93,0xe0,0x30,0x82,0x1b,0xe4,0x01,0x76,0xf5,0x03,0xa1,0x19,0xa3,0x8d,0x0e,0xce,0x00,0x00,
+ 0x05,0x20,0xb5,0x55,0x31,0x3f,0xe7,0xc7,0x17,0xe4,0x31,0x87,0x47,0x45,0x7c,0x67,0x43,0x5c,0x82,0x73,0xd6,0x62,0x64,0x94,0x92,0x32,0x2d,0x81,0x0e,0x01,0x35,0xc0,0x7e,0xb7,0x00,0x00,
0x05,0x20,0xb5,0x83,0x6f,0xb6,0x11,0xd8,0x0e,0xa8,0x57,0xda,0x15,0x20,0x5b,0x1a,0x6d,0x21,0x15,0x5a,0xbd,0xb4,0x17,0x11,0xc2,0xfb,0x0e,0xfc,0xde,0xe8,0x26,0x56,0xa8,0xac,0x00,0x00,
+ 0x05,0x20,0xba,0xe0,0xd1,0xe5,0x2e,0x27,0x5b,0x1d,0x36,0x57,0x77,0xaf,0x64,0x04,0xfc,0xe1,0x8f,0x8c,0xf1,0x25,0x81,0x2b,0x4f,0x6a,0xf8,0x55,0x48,0xc2,0x5e,0x6f,0x62,0x43,0x00,0x00,
+ 0x05,0x20,0xc0,0xb9,0x7b,0x21,0xbd,0xa2,0x48,0xda,0x8a,0x3e,0xc3,0x6c,0xac,0xfd,0x6d,0x63,0x21,0xb6,0xb3,0x37,0xa9,0x4d,0x42,0x2c,0x9e,0x75,0x61,0x07,0xdc,0xc9,0xab,0x9b,0x00,0x00,
+ 0x05,0x20,0xc8,0xdc,0x00,0xc8,0xdf,0xa1,0xb2,0xe9,0x9f,0x00,0xb3,0x86,0x93,0xc3,0xbc,0x6d,0x56,0xe2,0x83,0xfc,0xf4,0x6e,0x55,0x5d,0xed,0x4e,0x53,0xe6,0xd1,0x4c,0x38,0x3c,0x00,0x00,
0x05,0x20,0xcc,0xaf,0x6c,0x3b,0xd0,0x13,0x76,0x23,0xc3,0x36,0xbb,0x64,0x4a,0x4a,0x06,0x93,0x69,0x6d,0xb0,0x10,0x6e,0x66,0xa4,0x61,0xf8,0x2d,0xe7,0x80,0x72,0x4d,0x53,0x94,0x00,0x00,
+ 0x05,0x20,0xce,0x25,0x15,0xbd,0x08,0xe9,0x67,0x1c,0xa2,0xa5,0x16,0x0e,0x38,0xd9,0xe4,0xc6,0x20,0x31,0x86,0x23,0x21,0x5a,0x5a,0x76,0x1e,0x74,0xd5,0xd3,0x4e,0x86,0x61,0xf4,0x00,0x00,
0x06,0x10,0xfc,0x32,0x17,0xea,0xe4,0x15,0xc3,0xbf,0x98,0x08,0x14,0x9d,0xb5,0xa2,0xc9,0xaa,0x20,0x8d,
0x06,0x10,0xfc,0xc7,0xbe,0x49,0xcc,0xd1,0xdc,0x91,0x31,0x25,0xf0,0xda,0x45,0x7d,0x08,0xce,0x20,0x8d,
};
static const uint8_t chainparams_seed_test[] = {
- 0x04,0x20,0xdf,0x55,0xaa,0x83,0xd5,0xc5,0xb8,0xe7,0x75,0x78,0xd4,0x29,0x51,0x4b,0x26,0x1c,0x23,0xdf,0x28,0x4d,0x29,0x85,0x07,0xb5,0xe2,0x29,0x69,0x3e,0x25,0xbb,0x61,0xcf,0x47,0x9d,
- 0x04,0x20,0x0a,0xdd,0xa2,0x48,0xb5,0x56,0xa3,0x1f,0xca,0x3c,0x4c,0x9e,0xca,0x6e,0xb3,0xd5,0x5e,0x68,0xf6,0x28,0x31,0x57,0x24,0xfb,0x9d,0x2b,0x55,0x4f,0xd7,0x90,0x62,0xd3,0x47,0x9d,
- 0x04,0x20,0x2d,0x04,0xa1,0x4a,0xd4,0x7c,0x7b,0x16,0x2e,0xb7,0xd2,0xa1,0x08,0xc5,0xd2,0xbd,0x53,0x87,0x34,0xdc,0x38,0x26,0xca,0x56,0xf2,0xac,0xc5,0x62,0x70,0x72,0x3f,0x63,0x47,0x9d,
- 0x04,0x20,0x30,0x57,0x85,0xe0,0x02,0x4a,0xd1,0x31,0xeb,0x16,0x1b,0x1d,0xa8,0x43,0x0b,0xb4,0xc6,0xac,0x7d,0x46,0x24,0x0b,0x55,0x9d,0x16,0xe6,0x46,0x03,0x72,0xfe,0xd4,0xef,0x47,0x9d,
- 0x04,0x20,0x36,0x6c,0xf1,0xd2,0xbb,0xda,0xff,0x8c,0x93,0x61,0x10,0xf2,0x9d,0xa1,0xa4,0x0a,0x30,0x9b,0x0c,0x69,0x6d,0xaa,0xd4,0x9c,0xfd,0xb5,0x5b,0x5e,0x30,0x9f,0xf3,0x13,0x47,0x9d,
- 0x04,0x20,0x3e,0xe2,0xf3,0xe5,0xc5,0xbe,0x61,0xdd,0x4c,0x3e,0xdb,0x0d,0xd2,0xf9,0x42,0xe3,0x31,0xb2,0xa8,0x51,0x31,0xf6,0xce,0xc2,0x38,0x20,0x27,0x39,0x73,0x68,0x5a,0x42,0x47,0x9d,
- 0x04,0x20,0x51,0x79,0x05,0x9c,0x8a,0xdf,0x03,0xb5,0x1b,0x17,0xc3,0x86,0xb6,0x54,0xcc,0xe0,0x6e,0x58,0xa6,0x41,0x4c,0xcc,0x0c,0x60,0x08,0xa6,0x0f,0x1d,0x11,0xd8,0x29,0xa6,0x47,0x9d,
+ 0x04,0x20,0xd7,0x13,0xfe,0x00,0xf0,0xf1,0x07,0xcb,0x30,0xb1,0x31,0xc7,0x68,0xbb,0x05,0xca,0x18,0xb5,0x1d,0xb1,0x0f,0xf2,0x7e,0x66,0xdb,0xaa,0xf4,0x05,0xaf,0x37,0x0a,0x62,0x47,0x9d,
+ 0x04,0x20,0xd1,0x71,0xfe,0x4c,0x9e,0xe9,0x99,0xb1,0x6c,0xd5,0xdd,0x3a,0xc3,0xd8,0x74,0x1d,0x42,0x32,0x9f,0xca,0xe9,0x47,0x9e,0x18,0x74,0x43,0x22,0xb9,0xa5,0x31,0xb9,0x66,0x47,0x9d,
+ 0x04,0x20,0xd2,0xe4,0xd1,0x40,0x65,0x5d,0x95,0xaf,0xe8,0x67,0xc0,0xe3,0x72,0xfd,0x0a,0x2e,0x35,0xb7,0xbd,0xac,0x67,0x78,0x36,0xe1,0xb8,0xba,0x30,0xcf,0x2e,0x05,0xdd,0x9d,0x47,0x9d,
+ 0x04,0x20,0xde,0xdd,0xe8,0x01,0x03,0x98,0x3f,0x2d,0x3d,0x8b,0x99,0x27,0xea,0xe1,0xa3,0xde,0xff,0x50,0x30,0xc2,0x41,0x4e,0x7e,0x72,0x24,0x3d,0xad,0x78,0x3c,0xab,0x12,0x67,0x47,0x9d,
+ 0x04,0x20,0xdb,0x9d,0xe0,0xc8,0x5d,0x65,0x4b,0xe0,0x7b,0x0f,0x63,0x80,0x85,0x21,0x73,0x07,0x94,0x0f,0xb0,0x23,0x38,0xec,0x78,0xb6,0x07,0xa1,0xd2,0xb0,0x82,0xf8,0x20,0xea,0x47,0x9d,
+ 0x04,0x20,0xdb,0xc9,0xb2,0xe1,0x4b,0x43,0xf1,0xc9,0x3d,0xea,0x51,0x9a,0x71,0x6b,0x4a,0x15,0xe7,0x3e,0xfe,0x8b,0x24,0x7a,0xaa,0xff,0x53,0xf7,0xfc,0x10,0xc6,0xcc,0x14,0x88,0x47,0x9d,
+ 0x04,0x20,0xe5,0x38,0xc1,0x67,0x52,0x2d,0x6f,0x2f,0xbe,0xc2,0xb3,0x45,0x2b,0x19,0x8c,0x10,0xf3,0x09,0xe5,0x37,0xfe,0x66,0x57,0x6f,0x7a,0x4d,0x78,0xf8,0x50,0x41,0xd6,0x90,0x47,0x9d,
+ 0x04,0x20,0xed,0x76,0x8d,0x28,0xb0,0x44,0x2c,0xff,0xf4,0xd9,0x6a,0x5c,0xf9,0xca,0x5f,0x81,0xa2,0x23,0x48,0x82,0x24,0x2e,0xfc,0xbe,0x0a,0x1e,0xf9,0x02,0x43,0x31,0x37,0xf5,0x47,0x9d,
+ 0x04,0x20,0xee,0x57,0x1b,0xab,0xd0,0xd7,0x27,0xca,0xe7,0xaa,0xe2,0x09,0xf3,0x4e,0x80,0x90,0xd2,0x0a,0x9d,0xc4,0x59,0xd0,0xdc,0xa1,0x97,0x09,0x4e,0x2f,0xfe,0x9f,0xa8,0x0a,0x47,0x9d,
+ 0x04,0x20,0xf7,0xe5,0xb0,0x17,0xdc,0x21,0xed,0xfb,0xb6,0xf5,0xe6,0xb4,0x0b,0xb3,0xf3,0x6c,0xb8,0x3f,0x2a,0x93,0x89,0x66,0x2f,0x05,0x30,0xdf,0xfa,0xdf,0xf2,0x1b,0x52,0x78,0x47,0x9d,
+ 0x04,0x20,0xf0,0x38,0xe6,0xe3,0xbb,0x4d,0x17,0x56,0xd0,0xae,0xf6,0xf4,0xa9,0x99,0x96,0xa9,0xe8,0xfc,0xfb,0x76,0x08,0xed,0x7f,0xb7,0xf0,0xa0,0xd3,0x8d,0xea,0x18,0x87,0x8c,0x47,0x9d,
+ 0x04,0x20,0xf1,0x67,0x83,0x6d,0xa7,0x17,0xa6,0xdd,0x1f,0x19,0x5c,0xbd,0x92,0x8e,0x45,0x0c,0x5c,0x2c,0x9d,0x57,0x98,0xe1,0x46,0x40,0x3d,0x87,0x30,0x02,0x9f,0x1d,0x88,0xde,0x47,0x9d,
+ 0x04,0x20,0xff,0x7b,0xca,0x5b,0xa3,0x4c,0x68,0x7d,0xf1,0x3d,0x18,0xef,0xb4,0x3e,0x39,0x34,0x0d,0x9c,0x3c,0x49,0x9b,0xe7,0x67,0x2c,0x78,0x61,0x42,0x67,0x62,0xd0,0x15,0xb5,0x47,0x9d,
+ 0x04,0x20,0xff,0xbc,0xb7,0x65,0x60,0x0d,0xc1,0x04,0xbc,0x30,0x95,0x67,0xe9,0x37,0x62,0xf7,0x5b,0xe3,0x2a,0xd6,0x3b,0xaf,0x8a,0x98,0xc9,0xfb,0x4b,0x66,0xb2,0xdc,0x13,0xc0,0x47,0x9d,
+ 0x04,0x20,0xf8,0x57,0x5e,0x80,0x2d,0xc4,0xc6,0x5e,0x7f,0x15,0x09,0xee,0xcc,0x29,0xf1,0x65,0x53,0x1c,0x41,0xc1,0xf7,0x51,0xa4,0xb0,0x76,0x86,0xe3,0x10,0x24,0x4d,0xd2,0x11,0x47,0x9d,
+ 0x04,0x20,0xfd,0x74,0x90,0x71,0x06,0xbf,0x2b,0xdc,0x69,0x31,0xf2,0x63,0x1f,0x18,0x17,0x71,0xd2,0x75,0xe4,0xf5,0x35,0x89,0xc0,0x55,0x71,0x05,0xc4,0x77,0x64,0xbf,0x20,0x36,0x47,0x9d,
+ 0x04,0x20,0xfe,0x4c,0x16,0x4f,0x37,0x75,0xb4,0xe2,0x54,0x7d,0x00,0x21,0x39,0x3a,0x2c,0xb7,0xe8,0x9a,0xf3,0x2f,0x8a,0xf6,0xe4,0x6b,0x7d,0xea,0x18,0xc3,0xc3,0x86,0x57,0xc5,0x47,0x9d,
+ 0x04,0x20,0x00,0xe5,0x30,0x05,0x39,0xf9,0x05,0xac,0x6d,0x33,0xe5,0xb4,0x3a,0xd1,0x8c,0x75,0xb3,0x5f,0x9b,0xcc,0xd1,0x76,0xa0,0x24,0x84,0x49,0x4b,0x40,0x67,0xee,0x4d,0x93,0x47,0x9d,
+ 0x04,0x20,0x01,0x25,0x8f,0x4c,0xb4,0x28,0x06,0xaa,0x4f,0xc5,0x5d,0x34,0x19,0x40,0xcd,0xb6,0xb9,0xad,0x52,0x3a,0xc3,0x52,0x05,0x9a,0x97,0x5e,0x69,0x9a,0x2a,0x66,0xde,0x48,0x47,0x9d,
+ 0x04,0x20,0x02,0xee,0xed,0xe8,0x3d,0x20,0xd1,0xb0,0xb7,0x44,0xd6,0xb9,0x08,0x9b,0x13,0x35,0xee,0xf4,0x0b,0x8a,0x6e,0x10,0xfa,0xbc,0x75,0x14,0xcb,0x28,0xd4,0x40,0x44,0x3a,0x47,0x9d,
+ 0x04,0x20,0x03,0x88,0x08,0xcf,0x7c,0xa3,0x1c,0xe6,0xd4,0x7a,0x50,0x61,0x1d,0x84,0x5c,0x95,0x89,0x4f,0x03,0x8c,0x91,0xf4,0x67,0xf2,0x4a,0xc4,0x2a,0x44,0x6e,0x47,0xc8,0xae,0x47,0x9d,
+ 0x04,0x20,0x03,0xe3,0x9a,0xa7,0xe7,0x30,0xa7,0x21,0x93,0x8c,0x52,0x76,0x4b,0x43,0x7d,0x35,0x70,0xa9,0x3d,0x35,0x54,0x11,0x83,0x1b,0xe1,0x7b,0x6d,0xd9,0xaa,0xb2,0x26,0x9b,0x47,0x9d,
+ 0x04,0x20,0x05,0x9e,0xaf,0x67,0x77,0x31,0xef,0xe7,0x65,0xd4,0x3b,0x86,0x2c,0x0c,0x10,0x9c,0x7d,0x4c,0xe4,0x2d,0xb7,0x05,0x12,0x51,0x17,0xfb,0x15,0x47,0xdd,0xd7,0x5b,0x0e,0x47,0x9d,
+ 0x04,0x20,0x06,0x30,0xd8,0x03,0x34,0x16,0x0f,0xa4,0x8f,0xb1,0x21,0xc4,0x53,0x19,0x87,0xa3,0x60,0xf1,0xdb,0x2b,0x09,0xf2,0x18,0x1b,0x1e,0x5a,0x78,0x29,0xe7,0x4e,0x12,0x7e,0x47,0x9d,
+ 0x04,0x20,0x0e,0x90,0xa0,0x77,0x60,0x80,0x95,0x5e,0x1a,0x5f,0xb3,0x1c,0x6d,0x87,0x11,0x20,0x35,0x52,0xe5,0x9d,0x7f,0xf0,0x4d,0xec,0x68,0x30,0xa4,0xca,0xd5,0xaf,0x0e,0x20,0x47,0x9d,
+ 0x04,0x20,0x09,0x3f,0x9b,0xec,0xe7,0xf0,0xd6,0x03,0xba,0x2b,0xac,0xa3,0x13,0x41,0x9c,0x70,0x0e,0x58,0x9a,0x0c,0xd4,0xc3,0x66,0x84,0x7c,0xa7,0x76,0xd9,0xd3,0xa5,0xff,0xba,0x47,0x9d,
+ 0x04,0x20,0x0a,0xe8,0xaf,0x6a,0xc9,0xd7,0x03,0x2a,0x8e,0xc7,0xe7,0xd9,0x47,0x77,0x87,0x3d,0xa4,0x09,0xa8,0xb3,0x44,0x2e,0x4a,0xcf,0xaa,0x08,0x3e,0x79,0x4d,0x2e,0x25,0xfd,0x47,0x9d,
+ 0x04,0x20,0x0d,0x02,0xcf,0x15,0x0e,0x79,0x72,0xab,0xc2,0x25,0xbf,0xab,0x07,0xa1,0xc4,0xe8,0x0e,0xb3,0xe2,0x81,0xcf,0x7e,0xe3,0x4a,0x10,0xc7,0x0e,0x0e,0xbd,0xad,0x32,0x20,0x47,0x9d,
+ 0x04,0x20,0x1e,0xc5,0x90,0x07,0x29,0x4f,0x7e,0xb3,0x46,0x51,0xe9,0x81,0x65,0x24,0x7f,0xae,0xcd,0x96,0xc6,0x00,0xfe,0x70,0x8c,0xc1,0x8a,0xe2,0xe3,0x80,0x48,0x40,0xab,0x10,0x47,0x9d,
+ 0x04,0x20,0x18,0xd3,0xc1,0x52,0xa1,0xde,0xaa,0x4e,0x88,0xf3,0x7f,0x92,0x09,0xcc,0x43,0x6b,0x59,0x76,0x3d,0xce,0x2a,0x66,0x7c,0xa3,0xf3,0x39,0xaf,0x73,0xcf,0xf8,0x83,0x89,0x47,0x9d,
+ 0x04,0x20,0x1c,0x0e,0x75,0xca,0x45,0xb3,0x0f,0xb3,0x7b,0x27,0xb5,0xde,0x22,0x86,0xde,0xe5,0xf3,0xd8,0x95,0x0a,0x11,0x96,0x86,0x84,0xee,0xbd,0x7a,0x04,0xd7,0x90,0xca,0x04,0x47,0x9d,
+ 0x04,0x20,0x1c,0x66,0x71,0x60,0x3c,0xbf,0x22,0x32,0x91,0x56,0xe9,0xbf,0x74,0xb8,0xd7,0x47,0xc1,0x07,0x2e,0x88,0x59,0xa8,0xb0,0x9a,0xd5,0x93,0x09,0xb6,0xdb,0x6e,0x40,0x6a,0x47,0x9d,
+ 0x04,0x20,0x1d,0x83,0xcf,0x89,0x90,0x06,0xa6,0x97,0xb2,0xa9,0x01,0x01,0x1f,0x98,0x62,0x04,0x65,0xa5,0x93,0x3e,0x6a,0x08,0x53,0xa3,0x90,0x2e,0xb5,0x02,0x1e,0x78,0x98,0x3d,0x47,0x9d,
+ 0x04,0x20,0x27,0xe6,0xa8,0x97,0xbc,0x69,0xb7,0x0e,0xd4,0x4d,0xe9,0x9b,0xff,0xe6,0xc9,0xb3,0x3f,0xc5,0xa8,0xa0,0xaf,0x19,0x61,0xd2,0xfb,0x7d,0x5c,0xdf,0x62,0xb0,0x36,0xe6,0x47,0x9d,
+ 0x04,0x20,0x24,0xe9,0x86,0x63,0x9f,0x96,0xe1,0x42,0x3e,0xa3,0x03,0x9d,0xfd,0x23,0xa4,0xeb,0x05,0xfa,0x3a,0xb1,0xcd,0x3a,0xce,0x24,0xbd,0x87,0x99,0x65,0xd9,0x19,0x75,0xf1,0x47,0x9d,
+ 0x04,0x20,0x28,0x50,0xc2,0x49,0xb8,0x3e,0x68,0x10,0xe6,0x02,0xdd,0x01,0x42,0xe5,0x41,0xc1,0x58,0xd5,0x5e,0xb8,0x7b,0xae,0x0a,0x90,0x14,0x0f,0xe3,0x97,0xe4,0xfa,0x2b,0xaf,0x47,0x9d,
+ 0x04,0x20,0x2e,0x43,0x1b,0x30,0xd6,0x62,0x9d,0xf8,0x50,0x8b,0x89,0xcb,0x49,0x2a,0x7b,0x42,0x40,0x10,0x0b,0xfe,0xcd,0xfe,0x6e,0xe8,0x65,0x89,0x2a,0xc3,0x8d,0x49,0xf2,0x2a,0x47,0x9d,
+ 0x04,0x20,0x36,0x3c,0xd4,0x1f,0x8f,0x63,0xfa,0x49,0x62,0xb5,0x69,0xd6,0x9f,0x42,0xaa,0xfe,0x54,0x14,0xd1,0xd2,0xb2,0xae,0x52,0xe1,0x08,0x7e,0xc6,0x15,0x56,0x45,0xbd,0xb3,0x47,0x9d,
+ 0x04,0x20,0x38,0x3d,0xac,0xa2,0x19,0x80,0xdc,0x61,0xff,0xb9,0x37,0xd6,0x53,0xf0,0xb0,0x89,0xb6,0x14,0x33,0x62,0x7c,0x33,0x8a,0x3d,0xdc,0xdd,0xba,0xfb,0x70,0xa4,0x6f,0x9b,0x47,0x9d,
+ 0x04,0x20,0x38,0x04,0x94,0x99,0x3a,0x60,0x61,0x08,0xcc,0xf9,0x43,0x18,0x8f,0x01,0xb5,0x43,0x7e,0x35,0xa5,0x27,0x2a,0xf6,0x85,0x78,0x81,0x36,0xed,0xb6,0xb0,0xd2,0xac,0x77,0x47,0x9d,
+ 0x04,0x20,0x38,0x54,0xfe,0xde,0xcf,0x83,0xb9,0x06,0x7f,0xaa,0x79,0x96,0x58,0x89,0x78,0x45,0x29,0x51,0x9a,0xbf,0x64,0xd1,0x02,0xe2,0x5f,0x74,0x15,0x2e,0x0d,0x8f,0x81,0x9e,0x47,0x9d,
+ 0x04,0x20,0x3c,0x0c,0xec,0x7b,0x47,0x74,0x12,0xcc,0xef,0xe1,0x88,0xcb,0x57,0x11,0xd5,0xd8,0x91,0x7e,0x95,0x17,0x0c,0x12,0xe2,0x46,0x7c,0xc0,0xe3,0xe3,0x92,0x26,0xeb,0x85,0x47,0x9d,
+ 0x04,0x20,0x3d,0x42,0xc6,0x66,0xb0,0x8b,0xcc,0xf9,0x6a,0xfd,0xa7,0x10,0xfe,0x2a,0x45,0xd9,0x3a,0xcd,0x15,0xa4,0x00,0xbf,0xde,0x1a,0x6d,0x3a,0x5b,0xa8,0xc8,0x95,0x6a,0x3c,0x47,0x9d,
+ 0x04,0x20,0x3d,0xb7,0x5c,0xbf,0x7a,0xb7,0x0f,0xe3,0x21,0xeb,0xa1,0x3c,0x8b,0xf3,0x9d,0x72,0x3d,0x69,0xfc,0xcc,0x22,0xa5,0x1c,0xb4,0x3f,0x98,0xcb,0x63,0xa8,0xc8,0x9f,0xcc,0x47,0x9d,
+ 0x04,0x20,0x40,0xc7,0x1f,0x78,0x96,0x51,0xc8,0xd4,0x54,0x0f,0x32,0x00,0xda,0x0d,0x2a,0xb4,0x04,0xff,0x0e,0xf3,0x94,0x8d,0xf4,0x5e,0x23,0x74,0xfa,0x0c,0xfa,0x87,0xfb,0x3b,0x47,0x9d,
+ 0x04,0x20,0x43,0x30,0x0d,0xf9,0x04,0xba,0x10,0x88,0x45,0x56,0xa5,0xee,0x22,0x1d,0xb5,0xe2,0x8d,0xd9,0x1e,0x68,0x8d,0x87,0xaa,0x9b,0x82,0x6a,0x71,0xe5,0x95,0x2b,0x79,0xf6,0x47,0x9d,
+ 0x04,0x20,0x45,0x0e,0x7a,0x38,0x4d,0x97,0xc8,0xd9,0xc7,0x2b,0xbc,0xae,0xa9,0xe3,0x3a,0x40,0x65,0xf3,0xc0,0xc8,0x31,0xab,0xe9,0x02,0x99,0x82,0xb2,0xa0,0x79,0x60,0x92,0xed,0x47,0x9d,
+ 0x04,0x20,0x4a,0x3b,0x66,0x48,0xee,0x55,0x05,0xed,0x91,0x14,0x5e,0x57,0x64,0xeb,0x23,0xab,0x56,0x83,0x56,0x36,0x2d,0x6a,0x2f,0xf8,0x28,0x11,0x37,0x3a,0x73,0x76,0x2e,0x6a,0x47,0x9d,
+ 0x04,0x20,0x4a,0x4b,0x4c,0x27,0xea,0x89,0xb2,0xa6,0x3e,0xf5,0x6e,0xc3,0xa4,0xe8,0xf6,0x5e,0x54,0xcc,0x93,0x64,0xce,0x36,0x82,0xd9,0x5f,0x1b,0x65,0x6b,0x02,0xc6,0x66,0x60,0x47,0x9d,
+ 0x04,0x20,0x4b,0x74,0xfe,0x48,0xc5,0x79,0xd5,0x77,0xba,0xdf,0x52,0x63,0x40,0xc4,0x30,0x04,0xff,0xf5,0x29,0x01,0xcc,0x15,0x5a,0x58,0xf7,0xc0,0x16,0x28,0x83,0x36,0x60,0xa0,0x47,0x9d,
+ 0x04,0x20,0x4c,0x6a,0x4c,0x67,0x94,0x97,0xa4,0xe3,0x78,0x9e,0x16,0x65,0x58,0xf5,0x95,0xa9,0xcd,0x94,0x9a,0xbc,0x40,0xc2,0x4e,0x20,0x71,0x72,0xd9,0x02,0x50,0x75,0x9f,0x41,0x47,0x9d,
+ 0x04,0x20,0x4c,0x85,0xc2,0xc6,0xb5,0x6a,0xf4,0x3d,0x84,0xf9,0xc3,0x8b,0x21,0x7f,0x57,0x7e,0x66,0x66,0x6b,0x6d,0x5f,0x0a,0xd0,0xf0,0x76,0x56,0x65,0x78,0xf8,0xa7,0x42,0x75,0x47,0x9d,
+ 0x04,0x20,0x52,0xf0,0xcb,0x8e,0xa9,0x27,0xd4,0x21,0x57,0x73,0xcb,0x40,0x17,0x6e,0x83,0x73,0xe4,0x59,0xa9,0x89,0x23,0x14,0x14,0x6a,0x91,0x85,0xd2,0xce,0x1c,0x85,0x05,0x06,0x47,0x9d,
+ 0x04,0x20,0x55,0x93,0x75,0x82,0x96,0x4c,0xc8,0x24,0x96,0x48,0xb3,0x9e,0x15,0xf5,0xf5,0x6f,0x5b,0xec,0xf6,0x39,0xff,0xa8,0xaf,0x3a,0x4c,0x5d,0x22,0x3d,0xd3,0x14,0xc3,0x05,0x47,0x9d,
+ 0x04,0x20,0x58,0xbf,0x47,0xe7,0x52,0x8d,0xb5,0x83,0xbc,0x55,0xb1,0xb8,0x02,0xe3,0xdc,0xc2,0xc4,0xe7,0x79,0x85,0xb2,0xe7,0x38,0x27,0xae,0xad,0x0c,0xb8,0x34,0xed,0x71,0xde,0x47,0x9d,
+ 0x04,0x20,0x59,0x76,0xc7,0xdc,0xb8,0x4d,0x1d,0x51,0x40,0x75,0x43,0x57,0xdd,0x3e,0xa3,0x63,0x06,0x0f,0x5e,0x18,0x29,0x10,0x08,0xac,0xa0,0x60,0xd2,0xf3,0x63,0x6e,0xa6,0x58,0x47,0x9d,
0x04,0x20,0x60,0xbe,0xae,0x7d,0xa3,0x4d,0x6a,0x71,0x1a,0x5d,0xe5,0x98,0x9c,0xde,0xa0,0x99,0x39,0x19,0xd3,0x01,0x0a,0x5d,0x1c,0x21,0x43,0x94,0x92,0x71,0x5d,0x77,0xd7,0xdf,0x47,0x9d,
- 0x04,0x20,0x64,0x4e,0x86,0xa1,0x02,0xa1,0x8a,0xef,0xb0,0xd1,0xb5,0x77,0x69,0xb9,0x6a,0xdc,0xdf,0x35,0x8a,0xda,0xa4,0x3e,0x83,0xfa,0x50,0xe6,0xca,0x0e,0x2b,0x99,0x0a,0x17,0x47,0x9d,
- 0x04,0x20,0xa2,0x28,0x3c,0x5a,0x5b,0x82,0x32,0x66,0x11,0xe5,0x71,0xff,0x6b,0x25,0x92,0x75,0xdd,0x7a,0x4f,0x90,0x8b,0x1d,0x34,0xa4,0xf1,0x6e,0xb9,0xfb,0xb5,0x2e,0x7c,0x7f,0x47,0x9d,
- 0x04,0x20,0xc8,0xb5,0x6a,0xba,0x02,0x26,0x45,0x12,0xfb,0x93,0x8a,0x51,0xe4,0xb0,0xf3,0x94,0xb7,0xc0,0x74,0x72,0xeb,0x67,0x91,0x9e,0x04,0x36,0x6a,0x4b,0xef,0x0d,0x88,0xfe,0x47,0x9d,
- 0x04,0x20,0xc8,0xfa,0xcd,0x8c,0xc3,0x6f,0x3c,0xd0,0x27,0x7e,0x7d,0xeb,0x51,0x01,0x65,0xb6,0x9e,0x02,0x09,0x64,0xf4,0x87,0x78,0x7b,0x8f,0x9d,0xaf,0x3b,0xa5,0xcc,0x56,0x2c,0x47,0x9d,
+ 0x04,0x20,0x62,0x42,0x6d,0x98,0xc4,0xa1,0x16,0xc1,0x7d,0x17,0x9e,0x37,0x94,0x88,0x58,0x76,0xa9,0x13,0x6a,0x98,0x39,0x22,0x58,0x13,0xf8,0x9a,0x99,0xa2,0x25,0x56,0x59,0x05,0x47,0x9d,
+ 0x04,0x20,0x6a,0xb1,0x07,0x66,0xbe,0x50,0xec,0x8e,0x6b,0x72,0xfc,0xb4,0xc2,0x67,0x26,0xa1,0x7a,0x10,0x15,0xd3,0xd0,0x36,0x7a,0xf0,0x83,0xd0,0xc3,0x59,0x85,0x68,0x90,0xd8,0x47,0x9d,
+ 0x04,0x20,0x6d,0x6b,0x07,0x72,0xf7,0x45,0x8c,0x1d,0xe3,0x5c,0xf2,0x58,0x20,0xd2,0x95,0x25,0x22,0x51,0xc3,0x59,0x96,0xc1,0xdc,0x7e,0xa4,0x82,0x89,0x0a,0xf5,0x25,0x38,0x1a,0x47,0x9d,
+ 0x04,0x20,0x77,0xaa,0x57,0x56,0x17,0xce,0xdd,0x79,0x64,0xaf,0x78,0xb2,0xf4,0x91,0x36,0x75,0x1c,0x1c,0xcd,0x31,0x35,0x4a,0xe4,0x8b,0x64,0x65,0x4e,0x06,0xec,0x2a,0xc4,0x95,0x47,0x9d,
+ 0x04,0x20,0x70,0x11,0x6d,0x2d,0xa7,0xc9,0x9f,0xd8,0xe3,0xe8,0xae,0x5a,0x4a,0x50,0xac,0x3f,0xd6,0x57,0xad,0xd0,0xa5,0x35,0x14,0xd7,0xb1,0x76,0x58,0xb0,0x90,0xa4,0x1c,0x9b,0x47,0x9d,
+ 0x04,0x20,0x72,0xdb,0xfc,0x04,0x48,0xee,0xec,0xae,0x1b,0xad,0xe6,0x9a,0x87,0xe1,0x17,0xae,0x13,0x70,0xae,0x83,0x90,0x62,0x59,0x30,0xed,0x44,0x43,0xe9,0xab,0xee,0xaa,0xd0,0x47,0x9d,
+ 0x04,0x20,0x74,0xd8,0x56,0x9c,0x5e,0xb4,0x62,0x12,0xea,0x6f,0x1e,0x3d,0x95,0x22,0x50,0x16,0xe3,0xba,0x45,0xb2,0xa3,0xad,0x26,0xb0,0x13,0x74,0xdd,0xec,0x22,0xe0,0x70,0x9a,0x47,0x9d,
+ 0x04,0x20,0x75,0x45,0xe9,0x01,0x21,0x81,0x44,0x1b,0x57,0x76,0x8c,0x3f,0x85,0xa9,0x74,0xf4,0x3c,0xf8,0xd5,0x12,0xcf,0x13,0x1e,0x2f,0x18,0x13,0x78,0xa3,0x51,0x59,0x49,0x49,0x47,0x9d,
+ 0x04,0x20,0x7b,0x3b,0xf1,0xa0,0x78,0xc9,0xba,0xb4,0xd2,0xad,0xed,0x0b,0x1e,0xd2,0x8d,0xd2,0x08,0xd1,0x51,0x5b,0x01,0x74,0x6c,0xc0,0x5d,0xcd,0x4c,0x53,0xf3,0xba,0x16,0xe3,0x47,0x9d,
+ 0x04,0x20,0x7b,0x11,0x26,0x1d,0xf6,0xa1,0xb7,0xc3,0x36,0x19,0x0d,0x27,0x10,0x36,0xb7,0xd5,0x93,0x60,0xc8,0xf8,0x82,0x79,0x46,0xe8,0x78,0xce,0x29,0x50,0x2b,0x44,0x3f,0x7a,0x47,0x9d,
+ 0x04,0x20,0x7b,0x96,0x08,0xa2,0x54,0x5c,0xd0,0x94,0x9c,0x0a,0xa7,0xb3,0xea,0x7d,0x38,0x0b,0x8e,0xee,0x91,0xe3,0x91,0xb9,0xc9,0xcc,0x81,0x28,0x2e,0xc3,0x02,0x9a,0xcf,0x65,0x47,0x9d,
+ 0x04,0x20,0x81,0x34,0x90,0x93,0x9c,0xf9,0xc9,0xe7,0x81,0xa5,0xab,0xf1,0x03,0xe1,0xb3,0x2f,0xf7,0xcf,0x19,0x94,0x2f,0x7a,0x1b,0xc5,0x83,0xc9,0x8c,0xc3,0x5b,0x09,0x96,0x14,0x47,0x9d,
+ 0x04,0x20,0x86,0x7d,0x81,0xf5,0x72,0x30,0xc0,0x91,0x4c,0x8e,0x04,0x48,0x47,0xce,0xcd,0x43,0xef,0x22,0x67,0x17,0xea,0x95,0x2f,0x51,0x4f,0x57,0x67,0x58,0x21,0xa5,0x2e,0x68,0x47,0x9d,
+ 0x04,0x20,0x8b,0xfc,0xf6,0xec,0x3b,0x71,0x25,0x88,0xe8,0xc8,0x72,0xc3,0x89,0x05,0x27,0x81,0x28,0x6b,0xb6,0x72,0xa7,0xc5,0x16,0xaa,0x67,0xe6,0x37,0xb7,0x2b,0x99,0xa8,0x97,0x47,0x9d,
+ 0x04,0x20,0xa2,0x46,0x23,0xd2,0xdb,0x3e,0x7a,0x65,0x05,0xd4,0x45,0x78,0xff,0x3e,0xc1,0x28,0xdb,0xfa,0xf1,0x5e,0x32,0xe9,0x6a,0x8f,0x96,0x45,0x15,0x01,0x7f,0x1f,0x75,0xbf,0x47,0x9d,
+ 0x04,0x20,0xa8,0xa6,0xbb,0x02,0x06,0x65,0x3b,0xb2,0x52,0x79,0xd1,0xc9,0x6a,0x01,0x42,0x43,0xca,0x69,0xe1,0x69,0x90,0xfe,0x2c,0xd2,0x7c,0x18,0xfd,0xab,0xc7,0x71,0x7f,0x8b,0x47,0x9d,
+ 0x04,0x20,0xa9,0x7b,0x48,0xf7,0x73,0xcb,0x07,0x37,0x4e,0xaa,0x68,0xc4,0x67,0x08,0xea,0x5b,0x68,0x0a,0xfe,0x14,0x82,0x6a,0xa1,0x79,0x82,0x9c,0x0b,0x52,0x17,0xd6,0x6b,0x1b,0x47,0x9d,
+ 0x04,0x20,0xb3,0x6e,0x64,0x99,0x26,0x7a,0xeb,0xd4,0x53,0x8c,0x94,0xb1,0x4a,0x37,0x02,0xaa,0xa9,0xc8,0x36,0x11,0xec,0xf9,0x0f,0x08,0xa3,0x9f,0x3e,0xaf,0xd2,0xd1,0x2a,0x0b,0x47,0x9d,
+ 0x04,0x20,0xb3,0xd4,0x15,0x0e,0x3f,0x12,0xed,0xfc,0xab,0x69,0x55,0x91,0x45,0xcf,0xc1,0x2f,0x35,0xa4,0xf7,0x94,0xff,0xd8,0xbe,0x44,0xc8,0x36,0xaf,0x0f,0xa0,0x4e,0x36,0x22,0x47,0x9d,
+ 0x04,0x20,0xb6,0x5e,0x39,0xeb,0x93,0x18,0x22,0xad,0x78,0x94,0x34,0x48,0x31,0x8d,0xd1,0x42,0xb7,0x38,0x58,0x49,0xea,0x00,0xb9,0xcf,0x58,0x43,0x38,0x22,0x22,0x88,0x87,0xde,0x47,0x9d,
+ 0x04,0x20,0xb9,0x80,0xf6,0xa1,0x4a,0x0a,0xc6,0xe3,0x19,0x27,0xe9,0x2d,0x75,0x60,0x14,0x3d,0x2c,0xda,0x97,0x77,0x54,0x9f,0x78,0xb8,0x9b,0x72,0x6a,0x58,0xab,0x8b,0x95,0x95,0x47,0x9d,
+ 0x04,0x20,0xba,0xab,0x91,0x9c,0x48,0x8b,0x9b,0x34,0x5c,0x30,0xf3,0xd2,0x78,0xd3,0xbf,0x09,0x79,0x23,0x4e,0x18,0x5d,0x5e,0x75,0x6b,0xa2,0x91,0x63,0x2c,0x75,0xfd,0x06,0x36,0x47,0x9d,
+ 0x04,0x20,0xbb,0x50,0xf0,0x50,0x8d,0xb7,0x6c,0xd4,0x87,0x59,0x0d,0xfd,0x5f,0x51,0x86,0xa8,0x43,0xb3,0x7a,0xe7,0x2f,0x54,0x97,0x7d,0xed,0xc3,0x7b,0x31,0xe6,0xb9,0x05,0xe7,0x47,0x9d,
+ 0x04,0x20,0xc0,0xc1,0xf5,0x59,0xe8,0x46,0xf6,0x9d,0x41,0xf0,0xde,0x8b,0x32,0xf9,0x6f,0xd6,0xb2,0xd5,0x36,0x56,0x43,0xd3,0x60,0x08,0x55,0x94,0x13,0xf5,0xef,0x7f,0x4d,0x4a,0x47,0x9d,
+ 0x04,0x20,0xce,0xd3,0xd2,0xba,0x56,0xa1,0xde,0xc9,0xc4,0xdb,0x50,0x7b,0xe9,0x4d,0x59,0x65,0x1c,0x02,0x4a,0xa3,0xea,0x73,0xb2,0x66,0x70,0xe7,0x85,0xc1,0x20,0x60,0x79,0x96,0x47,0x9d,
+ 0x04,0x20,0xc8,0x88,0xfe,0x71,0x5f,0xa3,0x6c,0x96,0x6a,0xd7,0x9e,0x38,0x84,0x9f,0x44,0xe1,0x6b,0xdc,0x98,0x31,0xad,0x96,0x29,0xe7,0x00,0x83,0x63,0x03,0xae,0x69,0x2e,0x63,0x47,0x9d,
+ 0x04,0x20,0xcb,0x2a,0x8c,0xe7,0xe5,0x1f,0x4e,0x3a,0x13,0xd6,0x9e,0xd7,0x68,0x51,0x83,0xf4,0x2d,0x3e,0x21,0x38,0x63,0x18,0xe9,0x97,0x27,0xff,0x45,0x48,0xc3,0x6c,0xca,0x59,0x47,0x9d,
+ 0x04,0x20,0xcb,0x80,0x0d,0xdf,0xf0,0xa6,0x28,0x84,0x98,0xc8,0x47,0x72,0xbe,0x53,0x04,0x43,0x9c,0x3a,0x0e,0x46,0x9c,0x75,0x4c,0x57,0x50,0x98,0xe5,0x25,0x80,0x5d,0xa2,0x91,0x47,0x9d,
+ 0x04,0x20,0xcb,0xaa,0x0a,0x5d,0x6e,0x8a,0xfa,0x49,0x5a,0x8c,0x0c,0x9d,0x7a,0xe9,0x9d,0xc6,0xe4,0xcd,0xc1,0x6a,0xff,0xbb,0xf1,0x89,0xf1,0xd5,0x16,0x3f,0xdc,0xbb,0xe9,0x1b,0x47,0x9d,
};
#endif // BITCOIN_CHAINPARAMSSEEDS_H
diff --git a/src/checkqueue.h b/src/checkqueue.h
index bead6f0c6f..c4a64444e9 100644
--- a/src/checkqueue.h
+++ b/src/checkqueue.h
@@ -199,11 +199,12 @@ public:
WITH_LOCK(m_mutex, m_request_stop = false);
}
+ bool HasThreads() const { return !m_worker_threads.empty(); }
+
~CCheckQueue()
{
assert(m_worker_threads.empty());
}
-
};
/**
diff --git a/src/coins.cpp b/src/coins.cpp
index 1753d33b71..5983a8a39f 100644
--- a/src/coins.cpp
+++ b/src/coins.cpp
@@ -296,7 +296,7 @@ bool CCoinsViewErrorCatcher::GetCoin(const COutPoint &outpoint, Coin &coin) cons
try {
return CCoinsViewBacked::GetCoin(outpoint, coin);
} catch(const std::runtime_error& e) {
- for (auto f : m_err_callbacks) {
+ for (const auto& f : m_err_callbacks) {
f();
}
LogPrintf("Error reading from database: %s\n", e.what());
diff --git a/src/common/run_command.cpp b/src/common/run_command.cpp
new file mode 100644
index 0000000000..6ad9f75b5d
--- /dev/null
+++ b/src/common/run_command.cpp
@@ -0,0 +1,64 @@
+// Copyright (c) 2022 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 <common/run_command.h>
+
+#include <tinyformat.h>
+#include <univalue.h>
+
+#ifdef ENABLE_EXTERNAL_SIGNER
+#if defined(__GNUC__)
+// Boost 1.78 requires the following workaround.
+// See: https://github.com/boostorg/process/issues/235
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnarrowing"
+#endif
+#include <boost/process.hpp>
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+#endif // ENABLE_EXTERNAL_SIGNER
+
+UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in)
+{
+#ifdef ENABLE_EXTERNAL_SIGNER
+ namespace bp = boost::process;
+
+ UniValue result_json;
+ bp::opstream stdin_stream;
+ bp::ipstream stdout_stream;
+ bp::ipstream stderr_stream;
+
+ if (str_command.empty()) return UniValue::VNULL;
+
+ bp::child c(
+ str_command,
+ bp::std_out > stdout_stream,
+ bp::std_err > stderr_stream,
+ bp::std_in < stdin_stream
+ );
+ if (!str_std_in.empty()) {
+ stdin_stream << str_std_in << std::endl;
+ }
+ stdin_stream.pipe().close();
+
+ std::string result;
+ std::string error;
+ std::getline(stdout_stream, result);
+ std::getline(stderr_stream, error);
+
+ c.wait();
+ const int n_error = c.exit_code();
+ if (n_error) throw std::runtime_error(strprintf("RunCommandParseJSON error: process(%s) returned %d: %s\n", str_command, n_error, error));
+ if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result);
+
+ return result_json;
+#else
+ throw std::runtime_error("Compiled without external signing support (required for external signing).");
+#endif // ENABLE_EXTERNAL_SIGNER
+}
diff --git a/src/common/run_command.h b/src/common/run_command.h
new file mode 100644
index 0000000000..2fbdc071ee
--- /dev/null
+++ b/src/common/run_command.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2022 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_COMMON_RUN_COMMAND_H
+#define BITCOIN_COMMON_RUN_COMMAND_H
+
+#include <string>
+
+class UniValue;
+
+/**
+ * Execute a command which returns JSON, and parse the result.
+ *
+ * @param str_command The command to execute, including any arguments
+ * @param str_std_in string to pass to stdin
+ * @return parsed JSON
+ */
+UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in="");
+
+#endif // BITCOIN_COMMON_RUN_COMMAND_H
diff --git a/src/util/url.cpp b/src/common/url.cpp
index ea9323e666..5200d55096 100644
--- a/src/util/url.cpp
+++ b/src/common/url.cpp
@@ -2,7 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include <util/url.h>
+#include <common/url.h>
#include <event2/http.h>
diff --git a/src/util/url.h b/src/common/url.h
index 5a7b11fa04..7bbd8b60de 100644
--- a/src/util/url.h
+++ b/src/common/url.h
@@ -2,8 +2,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#ifndef BITCOIN_UTIL_URL_H
-#define BITCOIN_UTIL_URL_H
+#ifndef BITCOIN_COMMON_URL_H
+#define BITCOIN_COMMON_URL_H
#include <string>
@@ -11,4 +11,4 @@ using UrlDecodeFn = std::string(const std::string& url_encoded);
UrlDecodeFn urlDecode;
extern UrlDecodeFn* const URL_DECODE;
-#endif // BITCOIN_UTIL_URL_H
+#endif // BITCOIN_COMMON_URL_H
diff --git a/src/compat/compat.h b/src/compat/compat.h
index a8e5552c0a..cc37797577 100644
--- a/src/compat/compat.h
+++ b/src/compat/compat.h
@@ -109,14 +109,6 @@ typedef char* sockopt_arg_type;
#define USE_POLL
#endif
-bool static inline IsSelectableSocket(const SOCKET& s) {
-#if defined(USE_POLL) || defined(WIN32)
- return true;
-#else
- return (s < FD_SETSIZE);
-#endif
-}
-
// MSG_NOSIGNAL is not available on some platforms, if it doesn't exist define it as 0
#if !defined(MSG_NOSIGNAL)
#define MSG_NOSIGNAL 0
diff --git a/src/consensus/consensus.h b/src/consensus/consensus.h
index b2a31e3ba4..10bdbf31a8 100644
--- a/src/consensus/consensus.h
+++ b/src/consensus/consensus.h
@@ -6,7 +6,7 @@
#ifndef BITCOIN_CONSENSUS_CONSENSUS_H
#define BITCOIN_CONSENSUS_CONSENSUS_H
-#include <stdlib.h>
+#include <cstdlib>
#include <stdint.h>
/** The maximum allowed size for a serialized block, in bytes (only for buffer size limits) */
diff --git a/src/consensus/params.h b/src/consensus/params.h
index 794e0f5383..7c35222713 100644
--- a/src/consensus/params.h
+++ b/src/consensus/params.h
@@ -8,6 +8,7 @@
#include <uint256.h>
+#include <chrono>
#include <limits>
#include <map>
@@ -109,6 +110,10 @@ struct Params {
bool fPowNoRetargeting;
int64_t nPowTargetSpacing;
int64_t nPowTargetTimespan;
+ std::chrono::seconds PowTargetSpacing() const
+ {
+ return std::chrono::seconds{nPowTargetSpacing};
+ }
int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; }
/** The best chain should have at least this much work */
uint256 nMinimumChainWork;
diff --git a/src/consensus/validation.h b/src/consensus/validation.h
index 6027bb9aeb..9c0aa09356 100644
--- a/src/consensus/validation.h
+++ b/src/consensus/validation.h
@@ -79,6 +79,7 @@ enum class BlockValidationResult {
BLOCK_INVALID_PREV, //!< A block this one builds on is invalid
BLOCK_TIME_FUTURE, //!< block timestamp was > 2 hours in the future (or our clock is bad)
BLOCK_CHECKPOINT, //!< the block failed to meet one of our checkpoints
+ BLOCK_HEADER_LOW_WORK //!< the block header may be on a too-little-work chain
};
diff --git a/src/core_read.cpp b/src/core_read.cpp
index 77c516427a..ec21a2f7f4 100644
--- a/src/core_read.cpp
+++ b/src/core_read.cpp
@@ -265,7 +265,7 @@ int ParseSighashString(const UniValue& sighash)
{std::string("SINGLE"), int(SIGHASH_SINGLE)},
{std::string("SINGLE|ANYONECANPAY"), int(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY)},
};
- std::string strHashType = sighash.get_str();
+ const std::string& strHashType = sighash.get_str();
const auto& it = map_sighash_values.find(strHashType);
if (it != map_sighash_values.end()) {
hash_type = it->second;
diff --git a/src/crc32c/CMakeLists.txt b/src/crc32c/CMakeLists.txt
index 71692d5796..2f52db104c 100644
--- a/src/crc32c/CMakeLists.txt
+++ b/src/crc32c/CMakeLists.txt
@@ -296,9 +296,10 @@ target_include_directories(crc32c
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
-target_compile_definitions(crc32c
-PRIVATE
- CRC32C_HAVE_CONFIG_H=1
+set_property(
+ TARGET crc32c_arm64 crc32c_sse42 crc32c
+ APPEND
+ PROPERTY COMPILE_DEFINITIONS CRC32C_HAVE_CONFIG_H
)
set_target_properties(crc32c
diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h
index 69fbbe9fa5..de16a77878 100644
--- a/src/crypto/chacha20.h
+++ b/src/crypto/chacha20.h
@@ -5,8 +5,8 @@
#ifndef BITCOIN_CRYPTO_CHACHA20_H
#define BITCOIN_CRYPTO_CHACHA20_H
+#include <cstdlib>
#include <stdint.h>
-#include <stdlib.h>
/** A class for ChaCha20 256-bit stream cipher developed by Daniel J. Bernstein
https://cr.yp.to/chacha/chacha-20080128.pdf */
diff --git a/src/crypto/hkdf_sha256_32.h b/src/crypto/hkdf_sha256_32.h
index fa1e42aec1..878b03a37f 100644
--- a/src/crypto/hkdf_sha256_32.h
+++ b/src/crypto/hkdf_sha256_32.h
@@ -7,8 +7,8 @@
#include <crypto/hmac_sha256.h>
+#include <cstdlib>
#include <stdint.h>
-#include <stdlib.h>
/** A rfc5869 HKDF implementation with HMAC_SHA256 and fixed key output length of 32 bytes (L=32) */
class CHKDF_HMAC_SHA256_L32
diff --git a/src/crypto/hmac_sha256.h b/src/crypto/hmac_sha256.h
index d31fda1dd1..9c25edd7c1 100644
--- a/src/crypto/hmac_sha256.h
+++ b/src/crypto/hmac_sha256.h
@@ -7,8 +7,8 @@
#include <crypto/sha256.h>
+#include <cstdlib>
#include <stdint.h>
-#include <stdlib.h>
/** A hasher class for HMAC-SHA-256. */
class CHMAC_SHA256
diff --git a/src/crypto/hmac_sha512.h b/src/crypto/hmac_sha512.h
index 1ea9a3671e..6acce8992e 100644
--- a/src/crypto/hmac_sha512.h
+++ b/src/crypto/hmac_sha512.h
@@ -7,8 +7,8 @@
#include <crypto/sha512.h>
+#include <cstdlib>
#include <stdint.h>
-#include <stdlib.h>
/** A hasher class for HMAC-SHA-512. */
class CHMAC_SHA512
diff --git a/src/crypto/poly1305.h b/src/crypto/poly1305.h
index 1598b013b9..c80faada7e 100644
--- a/src/crypto/poly1305.h
+++ b/src/crypto/poly1305.h
@@ -5,8 +5,8 @@
#ifndef BITCOIN_CRYPTO_POLY1305_H
#define BITCOIN_CRYPTO_POLY1305_H
+#include <cstdlib>
#include <stdint.h>
-#include <stdlib.h>
#define POLY1305_KEYLEN 32
#define POLY1305_TAGLEN 16
diff --git a/src/crypto/ripemd160.h b/src/crypto/ripemd160.h
index 38ea375c1f..f1d89b8407 100644
--- a/src/crypto/ripemd160.h
+++ b/src/crypto/ripemd160.h
@@ -5,8 +5,8 @@
#ifndef BITCOIN_CRYPTO_RIPEMD160_H
#define BITCOIN_CRYPTO_RIPEMD160_H
+#include <cstdlib>
#include <stdint.h>
-#include <stdlib.h>
/** A hasher class for RIPEMD-160. */
class CRIPEMD160
diff --git a/src/crypto/sha1.h b/src/crypto/sha1.h
index 8b4568ee12..6ef0187efd 100644
--- a/src/crypto/sha1.h
+++ b/src/crypto/sha1.h
@@ -5,8 +5,8 @@
#ifndef BITCOIN_CRYPTO_SHA1_H
#define BITCOIN_CRYPTO_SHA1_H
+#include <cstdlib>
#include <stdint.h>
-#include <stdlib.h>
/** A hasher class for SHA1. */
class CSHA1
diff --git a/src/crypto/sha256.h b/src/crypto/sha256.h
index 028ee14345..24bd1f2e7e 100644
--- a/src/crypto/sha256.h
+++ b/src/crypto/sha256.h
@@ -5,8 +5,8 @@
#ifndef BITCOIN_CRYPTO_SHA256_H
#define BITCOIN_CRYPTO_SHA256_H
+#include <cstdlib>
#include <stdint.h>
-#include <stdlib.h>
#include <string>
/** A hasher class for SHA-256. */
diff --git a/src/crypto/sha256_sse4.cpp b/src/crypto/sha256_sse4.cpp
index f1a7fefea3..bc69703607 100644
--- a/src/crypto/sha256_sse4.cpp
+++ b/src/crypto/sha256_sse4.cpp
@@ -5,8 +5,8 @@
// This is a translation to GCC extended asm syntax from YASM code by Intel
// (available at the bottom of this file).
+#include <cstdlib>
#include <stdint.h>
-#include <stdlib.h>
#if defined(__x86_64__) || defined(__amd64__)
diff --git a/src/crypto/sha3.h b/src/crypto/sha3.h
index 88d8c1204d..78608eae76 100644
--- a/src/crypto/sha3.h
+++ b/src/crypto/sha3.h
@@ -7,8 +7,8 @@
#include <span.h>
+#include <cstdlib>
#include <stdint.h>
-#include <stdlib.h>
//! The Keccak-f[1600] transform.
void KeccakF(uint64_t (&st)[25]);
diff --git a/src/crypto/sha512.cpp b/src/crypto/sha512.cpp
index 85a7bbcb53..59b79609dd 100644
--- a/src/crypto/sha512.cpp
+++ b/src/crypto/sha512.cpp
@@ -30,7 +30,7 @@ void inline Round(uint64_t a, uint64_t b, uint64_t c, uint64_t& d, uint64_t e, u
h = t1 + t2;
}
-/** Initialize SHA-256 state. */
+/** Initialize SHA-512 state. */
void inline Initialize(uint64_t* s)
{
s[0] = 0x6a09e667f3bcc908ull;
diff --git a/src/crypto/sha512.h b/src/crypto/sha512.h
index 21ca930c75..7356dff6d9 100644
--- a/src/crypto/sha512.h
+++ b/src/crypto/sha512.h
@@ -5,8 +5,8 @@
#ifndef BITCOIN_CRYPTO_SHA512_H
#define BITCOIN_CRYPTO_SHA512_H
+#include <cstdlib>
#include <stdint.h>
-#include <stdlib.h>
/** A hasher class for SHA-512. */
class CSHA512
diff --git a/src/cuckoocache.h b/src/cuckoocache.h
index d0dc61c7e6..61f553806e 100644
--- a/src/cuckoocache.h
+++ b/src/cuckoocache.h
@@ -12,7 +12,9 @@
#include <atomic>
#include <cmath>
#include <cstring>
+#include <limits>
#include <memory>
+#include <optional>
#include <utility>
#include <vector>
@@ -326,7 +328,7 @@ public:
}
/** setup initializes the container to store no more than new_size
- * elements.
+ * elements and no less than 2 elements.
*
* setup should only be called once.
*
@@ -336,8 +338,8 @@ public:
uint32_t setup(uint32_t new_size)
{
// depth_limit must be at least one otherwise errors can occur.
- depth_limit = static_cast<uint8_t>(std::log2(static_cast<float>(std::max((uint32_t)2, new_size))));
size = std::max<uint32_t>(2, new_size);
+ depth_limit = static_cast<uint8_t>(std::log2(static_cast<float>(size)));
table.resize(size);
collection_flags.setup(size);
epoch_flags.resize(size);
@@ -357,12 +359,21 @@ public:
*
* @param bytes the approximate number of bytes to use for this data
* structure
- * @returns the maximum number of elements storable (see setup()
- * documentation for more detail)
+ * @returns A pair of the maximum number of elements storable (see setup()
+ * documentation for more detail) and the approxmiate total size of these
+ * elements in bytes or std::nullopt if the size requested is too large.
*/
- uint32_t setup_bytes(size_t bytes)
+ std::optional<std::pair<uint32_t, size_t>> setup_bytes(size_t bytes)
{
- return setup(bytes/sizeof(Element));
+ size_t requested_num_elems = bytes / sizeof(Element);
+ if (std::numeric_limits<uint32_t>::max() < requested_num_elems) {
+ return std::nullopt;
+ }
+
+ auto num_elems = setup(bytes/sizeof(Element));
+
+ size_t approx_size_bytes = num_elems * sizeof(Element);
+ return std::make_pair(num_elems, approx_size_bytes);
}
/** insert loops at most depth_limit times trying to insert a hash
diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp
index 4dbc839941..7f45e35aef 100644
--- a/src/dbwrapper.cpp
+++ b/src/dbwrapper.cpp
@@ -128,7 +128,7 @@ static leveldb::Options GetOptions(size_t nCacheSize)
}
CDBWrapper::CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory, bool fWipe, bool obfuscate)
- : m_name{fs::PathToString(path.stem())}
+ : m_name{fs::PathToString(path.stem())}, m_path{path}, m_is_memory{fMemory}
{
penv = nullptr;
readoptions.verify_checksums = true;
diff --git a/src/dbwrapper.h b/src/dbwrapper.h
index 665eaa0e98..1052da01d5 100644
--- a/src/dbwrapper.h
+++ b/src/dbwrapper.h
@@ -39,6 +39,10 @@ public:
class CDBWrapper;
+namespace dbwrapper {
+ using leveldb::DestroyDB;
+}
+
/** These should be considered an implementation detail of the specific database.
*/
namespace dbwrapper_private {
@@ -219,6 +223,12 @@ private:
std::vector<unsigned char> CreateObfuscateKey() const;
+ //! path to filesystem storage
+ const fs::path m_path;
+
+ //! whether or not the database resides in memory
+ bool m_is_memory;
+
public:
/**
* @param[in] path Location in the filesystem where leveldb data will be stored.
@@ -268,6 +278,14 @@ public:
return WriteBatch(batch, fSync);
}
+ //! @returns filesystem path to the on-disk data.
+ std::optional<fs::path> StoragePath() {
+ if (m_is_memory) {
+ return {};
+ }
+ return m_path;
+ }
+
template <typename K>
bool Exists(const K& key) const
{
diff --git a/src/external_signer.cpp b/src/external_signer.cpp
index 6bab0a856c..f255834830 100644
--- a/src/external_signer.cpp
+++ b/src/external_signer.cpp
@@ -3,10 +3,10 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chainparams.h>
+#include <common/run_command.h>
#include <core_io.h>
#include <psbt.h>
#include <util/strencodings.h>
-#include <util/system.h>
#include <external_signer.h>
#include <algorithm>
@@ -28,7 +28,7 @@ bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalS
if (!result.isArray()) {
throw std::runtime_error(strprintf("'%s' received invalid response, expected array of signers", command));
}
- for (UniValue signer : result.getValues()) {
+ for (const UniValue& signer : result.getValues()) {
// Check for error
const UniValue& error = find_value(signer, "error");
if (!error.isNull()) {
@@ -81,6 +81,9 @@ bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::str
for (const auto& entry : input.hd_keypaths) {
if (parsed_m_fingerprint == MakeUCharSpan(entry.second.fingerprint)) return true;
}
+ for (const auto& entry : input.m_tap_bip32_paths) {
+ if (parsed_m_fingerprint == MakeUCharSpan(entry.second.second.fingerprint)) return true;
+ }
return false;
};
diff --git a/src/fs.cpp b/src/fs.cpp
index 74b167e313..07cce269ed 100644
--- a/src/fs.cpp
+++ b/src/fs.cpp
@@ -126,7 +126,7 @@ bool FileLock::TryLock()
if (hFile == INVALID_HANDLE_VALUE) {
return false;
}
- _OVERLAPPED overlapped = {0};
+ _OVERLAPPED overlapped = {};
if (!LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, std::numeric_limits<DWORD>::max(), std::numeric_limits<DWORD>::max(), &overlapped)) {
reason = GetErrorReason();
return false;
diff --git a/src/fs.h b/src/fs.h
index e8b34319bb..ac58c4a2ba 100644
--- a/src/fs.h
+++ b/src/fs.h
@@ -69,7 +69,11 @@ public:
static inline path u8path(const std::string& utf8_str)
{
+#if __cplusplus < 202002L
return std::filesystem::u8path(utf8_str);
+#else
+ return std::filesystem::path(std::u8string{utf8_str.begin(), utf8_str.end()});
+#endif
}
// Disallow implicit std::string conversion for absolute to avoid
diff --git a/src/hash.cpp b/src/hash.cpp
index 111b707964..06caaac6ee 100644
--- a/src/hash.cpp
+++ b/src/hash.cpp
@@ -16,7 +16,7 @@ inline uint32_t ROTL32(uint32_t x, int8_t r)
unsigned int MurmurHash3(unsigned int nHashSeed, Span<const unsigned char> vDataToHash)
{
- // The following is MurmurHash3 (x86_32), see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp
+ // The following is MurmurHash3 (x86_32), see https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp
uint32_t h1 = nHashSeed;
const uint32_t c1 = 0xcc9e2d51;
const uint32_t c2 = 0x1b873593;
diff --git a/src/headerssync.cpp b/src/headerssync.cpp
new file mode 100644
index 0000000000..757b942cd9
--- /dev/null
+++ b/src/headerssync.cpp
@@ -0,0 +1,317 @@
+// Copyright (c) 2022 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 <headerssync.h>
+#include <logging.h>
+#include <pow.h>
+#include <timedata.h>
+#include <util/check.h>
+
+// The two constants below are computed using the simulation script on
+// https://gist.github.com/sipa/016ae445c132cdf65a2791534dfb7ae1
+
+//! Store a commitment to a header every HEADER_COMMITMENT_PERIOD blocks.
+constexpr size_t HEADER_COMMITMENT_PERIOD{584};
+
+//! Only feed headers to validation once this many headers on top have been
+//! received and validated against commitments.
+constexpr size_t REDOWNLOAD_BUFFER_SIZE{13959}; // 13959/584 = ~23.9 commitments
+
+// Our memory analysis assumes 48 bytes for a CompressedHeader (so we should
+// re-calculate parameters if we compress further)
+static_assert(sizeof(CompressedHeader) == 48);
+
+HeadersSyncState::HeadersSyncState(NodeId id, const Consensus::Params& consensus_params,
+ const CBlockIndex* chain_start, const arith_uint256& minimum_required_work) :
+ m_id(id), m_consensus_params(consensus_params),
+ m_chain_start(chain_start),
+ m_minimum_required_work(minimum_required_work),
+ m_current_chain_work(chain_start->nChainWork),
+ m_commit_offset(GetRand<unsigned>(HEADER_COMMITMENT_PERIOD)),
+ m_last_header_received(m_chain_start->GetBlockHeader()),
+ m_current_height(chain_start->nHeight)
+{
+ // Estimate the number of blocks that could possibly exist on the peer's
+ // chain *right now* using 6 blocks/second (fastest blockrate given the MTP
+ // rule) times the number of seconds from the last allowed block until
+ // today. This serves as a memory bound on how many commitments we might
+ // store from this peer, and we can safely give up syncing if the peer
+ // exceeds this bound, because it's not possible for a consensus-valid
+ // chain to be longer than this (at the current time -- in the future we
+ // could try again, if necessary, to sync a longer chain).
+ m_max_commitments = 6*(Ticks<std::chrono::seconds>(GetAdjustedTime() - NodeSeconds{std::chrono::seconds{chain_start->GetMedianTimePast()}}) + MAX_FUTURE_BLOCK_TIME) / HEADER_COMMITMENT_PERIOD;
+
+ LogPrint(BCLog::NET, "Initial headers sync started with peer=%d: height=%i, max_commitments=%i, min_work=%s\n", m_id, m_current_height, m_max_commitments, m_minimum_required_work.ToString());
+}
+
+/** Free any memory in use, and mark this object as no longer usable. This is
+ * required to guarantee that we won't reuse this object with the same
+ * SaltedTxidHasher for another sync. */
+void HeadersSyncState::Finalize()
+{
+ Assume(m_download_state != State::FINAL);
+ m_header_commitments = {};
+ m_last_header_received.SetNull();
+ m_redownloaded_headers = {};
+ m_redownload_buffer_last_hash.SetNull();
+ m_redownload_buffer_first_prev_hash.SetNull();
+ m_process_all_remaining_headers = false;
+ m_current_height = 0;
+
+ m_download_state = State::FINAL;
+}
+
+/** Process the next batch of headers received from our peer.
+ * Validate and store commitments, and compare total chainwork to our target to
+ * see if we can switch to REDOWNLOAD mode. */
+HeadersSyncState::ProcessingResult HeadersSyncState::ProcessNextHeaders(const
+ std::vector<CBlockHeader>& received_headers, const bool full_headers_message)
+{
+ ProcessingResult ret;
+
+ Assume(!received_headers.empty());
+ if (received_headers.empty()) return ret;
+
+ Assume(m_download_state != State::FINAL);
+ if (m_download_state == State::FINAL) return ret;
+
+ if (m_download_state == State::PRESYNC) {
+ // During PRESYNC, we minimally validate block headers and
+ // occasionally add commitments to them, until we reach our work
+ // threshold (at which point m_download_state is updated to REDOWNLOAD).
+ ret.success = ValidateAndStoreHeadersCommitments(received_headers);
+ if (ret.success) {
+ if (full_headers_message || m_download_state == State::REDOWNLOAD) {
+ // A full headers message means the peer may have more to give us;
+ // also if we just switched to REDOWNLOAD then we need to re-request
+ // headers from the beginning.
+ ret.request_more = true;
+ } else {
+ Assume(m_download_state == State::PRESYNC);
+ // If we're in PRESYNC and we get a non-full headers
+ // message, then the peer's chain has ended and definitely doesn't
+ // have enough work, so we can stop our sync.
+ LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: incomplete headers message at height=%i (presync phase)\n", m_id, m_current_height);
+ }
+ }
+ } else if (m_download_state == State::REDOWNLOAD) {
+ // During REDOWNLOAD, we compare our stored commitments to what we
+ // receive, and add headers to our redownload buffer. When the buffer
+ // gets big enough (meaning that we've checked enough commitments),
+ // we'll return a batch of headers to the caller for processing.
+ ret.success = true;
+ for (const auto& hdr : received_headers) {
+ if (!ValidateAndStoreRedownloadedHeader(hdr)) {
+ // Something went wrong -- the peer gave us an unexpected chain.
+ // We could consider looking at the reason for failure and
+ // punishing the peer, but for now just give up on sync.
+ ret.success = false;
+ break;
+ }
+ }
+
+ if (ret.success) {
+ // Return any headers that are ready for acceptance.
+ ret.pow_validated_headers = PopHeadersReadyForAcceptance();
+
+ // If we hit our target blockhash, then all remaining headers will be
+ // returned and we can clear any leftover internal state.
+ if (m_redownloaded_headers.empty() && m_process_all_remaining_headers) {
+ LogPrint(BCLog::NET, "Initial headers sync complete with peer=%d: releasing all at height=%i (redownload phase)\n", m_id, m_redownload_buffer_last_height);
+ } else if (full_headers_message) {
+ // If the headers message is full, we need to request more.
+ ret.request_more = true;
+ } else {
+ // For some reason our peer gave us a high-work chain, but is now
+ // declining to serve us that full chain again. Give up.
+ // Note that there's no more processing to be done with these
+ // headers, so we can still return success.
+ LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: incomplete headers message at height=%i (redownload phase)\n", m_id, m_redownload_buffer_last_height);
+ }
+ }
+ }
+
+ if (!(ret.success && ret.request_more)) Finalize();
+ return ret;
+}
+
+bool HeadersSyncState::ValidateAndStoreHeadersCommitments(const std::vector<CBlockHeader>& headers)
+{
+ // The caller should not give us an empty set of headers.
+ Assume(headers.size() > 0);
+ if (headers.size() == 0) return true;
+
+ Assume(m_download_state == State::PRESYNC);
+ if (m_download_state != State::PRESYNC) return false;
+
+ if (headers[0].hashPrevBlock != m_last_header_received.GetHash()) {
+ // Somehow our peer gave us a header that doesn't connect.
+ // This might be benign -- perhaps our peer reorged away from the chain
+ // they were on. Give up on this sync for now (likely we will start a
+ // new sync with a new starting point).
+ LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: non-continuous headers at height=%i (presync phase)\n", m_id, m_current_height);
+ return false;
+ }
+
+ // If it does connect, (minimally) validate and occasionally store
+ // commitments.
+ for (const auto& hdr : headers) {
+ if (!ValidateAndProcessSingleHeader(hdr)) {
+ return false;
+ }
+ }
+
+ if (m_current_chain_work >= m_minimum_required_work) {
+ m_redownloaded_headers.clear();
+ m_redownload_buffer_last_height = m_chain_start->nHeight;
+ m_redownload_buffer_first_prev_hash = m_chain_start->GetBlockHash();
+ m_redownload_buffer_last_hash = m_chain_start->GetBlockHash();
+ m_redownload_chain_work = m_chain_start->nChainWork;
+ m_download_state = State::REDOWNLOAD;
+ LogPrint(BCLog::NET, "Initial headers sync transition with peer=%d: reached sufficient work at height=%i, redownloading from height=%i\n", m_id, m_current_height, m_redownload_buffer_last_height);
+ }
+ return true;
+}
+
+bool HeadersSyncState::ValidateAndProcessSingleHeader(const CBlockHeader& current)
+{
+ Assume(m_download_state == State::PRESYNC);
+ if (m_download_state != State::PRESYNC) return false;
+
+ int next_height = m_current_height + 1;
+
+ // Verify that the difficulty isn't growing too fast; an adversary with
+ // limited hashing capability has a greater chance of producing a high
+ // work chain if they compress the work into as few blocks as possible,
+ // so don't let anyone give a chain that would violate the difficulty
+ // adjustment maximum.
+ if (!PermittedDifficultyTransition(m_consensus_params, next_height,
+ m_last_header_received.nBits, current.nBits)) {
+ LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: invalid difficulty transition at height=%i (presync phase)\n", m_id, next_height);
+ return false;
+ }
+
+ if (next_height % HEADER_COMMITMENT_PERIOD == m_commit_offset) {
+ // Add a commitment.
+ m_header_commitments.push_back(m_hasher(current.GetHash()) & 1);
+ if (m_header_commitments.size() > m_max_commitments) {
+ // The peer's chain is too long; give up.
+ // It's possible the chain grew since we started the sync; so
+ // potentially we could succeed in syncing the peer's chain if we
+ // try again later.
+ LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: exceeded max commitments at height=%i (presync phase)\n", m_id, next_height);
+ return false;
+ }
+ }
+
+ m_current_chain_work += GetBlockProof(CBlockIndex(current));
+ m_last_header_received = current;
+ m_current_height = next_height;
+
+ return true;
+}
+
+bool HeadersSyncState::ValidateAndStoreRedownloadedHeader(const CBlockHeader& header)
+{
+ Assume(m_download_state == State::REDOWNLOAD);
+ if (m_download_state != State::REDOWNLOAD) return false;
+
+ int64_t next_height = m_redownload_buffer_last_height + 1;
+
+ // Ensure that we're working on a header that connects to the chain we're
+ // downloading.
+ if (header.hashPrevBlock != m_redownload_buffer_last_hash) {
+ LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: non-continuous headers at height=%i (redownload phase)\n", m_id, next_height);
+ return false;
+ }
+
+ // Check that the difficulty adjustments are within our tolerance:
+ uint32_t previous_nBits{0};
+ if (!m_redownloaded_headers.empty()) {
+ previous_nBits = m_redownloaded_headers.back().nBits;
+ } else {
+ previous_nBits = m_chain_start->nBits;
+ }
+
+ if (!PermittedDifficultyTransition(m_consensus_params, next_height,
+ previous_nBits, header.nBits)) {
+ LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: invalid difficulty transition at height=%i (redownload phase)\n", m_id, next_height);
+ return false;
+ }
+
+ // Track work on the redownloaded chain
+ m_redownload_chain_work += GetBlockProof(CBlockIndex(header));
+
+ if (m_redownload_chain_work >= m_minimum_required_work) {
+ m_process_all_remaining_headers = true;
+ }
+
+ // If we're at a header for which we previously stored a commitment, verify
+ // it is correct. Failure will result in aborting download.
+ // Also, don't check commitments once we've gotten to our target blockhash;
+ // it's possible our peer has extended its chain between our first sync and
+ // our second, and we don't want to return failure after we've seen our
+ // target blockhash just because we ran out of commitments.
+ if (!m_process_all_remaining_headers && next_height % HEADER_COMMITMENT_PERIOD == m_commit_offset) {
+ if (m_header_commitments.size() == 0) {
+ LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: commitment overrun at height=%i (redownload phase)\n", m_id, next_height);
+ // Somehow our peer managed to feed us a different chain and
+ // we've run out of commitments.
+ return false;
+ }
+ bool commitment = m_hasher(header.GetHash()) & 1;
+ bool expected_commitment = m_header_commitments.front();
+ m_header_commitments.pop_front();
+ if (commitment != expected_commitment) {
+ LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: commitment mismatch at height=%i (redownload phase)\n", m_id, next_height);
+ return false;
+ }
+ }
+
+ // Store this header for later processing.
+ m_redownloaded_headers.push_back(header);
+ m_redownload_buffer_last_height = next_height;
+ m_redownload_buffer_last_hash = header.GetHash();
+
+ return true;
+}
+
+std::vector<CBlockHeader> HeadersSyncState::PopHeadersReadyForAcceptance()
+{
+ std::vector<CBlockHeader> ret;
+
+ Assume(m_download_state == State::REDOWNLOAD);
+ if (m_download_state != State::REDOWNLOAD) return ret;
+
+ while (m_redownloaded_headers.size() > REDOWNLOAD_BUFFER_SIZE ||
+ (m_redownloaded_headers.size() > 0 && m_process_all_remaining_headers)) {
+ ret.emplace_back(m_redownloaded_headers.front().GetFullHeader(m_redownload_buffer_first_prev_hash));
+ m_redownloaded_headers.pop_front();
+ m_redownload_buffer_first_prev_hash = ret.back().GetHash();
+ }
+ return ret;
+}
+
+CBlockLocator HeadersSyncState::NextHeadersRequestLocator() const
+{
+ Assume(m_download_state != State::FINAL);
+ if (m_download_state == State::FINAL) return {};
+
+ auto chain_start_locator = LocatorEntries(m_chain_start);
+ std::vector<uint256> locator;
+
+ if (m_download_state == State::PRESYNC) {
+ // During pre-synchronization, we continue from the last header received.
+ locator.push_back(m_last_header_received.GetHash());
+ }
+
+ if (m_download_state == State::REDOWNLOAD) {
+ // During redownload, we will download from the last received header that we stored.
+ locator.push_back(m_redownload_buffer_last_hash);
+ }
+
+ locator.insert(locator.end(), chain_start_locator.begin(), chain_start_locator.end());
+
+ return CBlockLocator{std::move(locator)};
+}
diff --git a/src/headerssync.h b/src/headerssync.h
new file mode 100644
index 0000000000..16da964246
--- /dev/null
+++ b/src/headerssync.h
@@ -0,0 +1,277 @@
+// Copyright (c) 2022 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_HEADERSSYNC_H
+#define BITCOIN_HEADERSSYNC_H
+
+#include <arith_uint256.h>
+#include <chain.h>
+#include <consensus/params.h>
+#include <net.h> // For NodeId
+#include <primitives/block.h>
+#include <uint256.h>
+#include <util/bitdeque.h>
+#include <util/hasher.h>
+
+#include <deque>
+#include <vector>
+
+// A compressed CBlockHeader, which leaves out the prevhash
+struct CompressedHeader {
+ // header
+ int32_t nVersion{0};
+ uint256 hashMerkleRoot;
+ uint32_t nTime{0};
+ uint32_t nBits{0};
+ uint32_t nNonce{0};
+
+ CompressedHeader()
+ {
+ hashMerkleRoot.SetNull();
+ }
+
+ CompressedHeader(const CBlockHeader& header)
+ {
+ nVersion = header.nVersion;
+ hashMerkleRoot = header.hashMerkleRoot;
+ nTime = header.nTime;
+ nBits = header.nBits;
+ nNonce = header.nNonce;
+ }
+
+ CBlockHeader GetFullHeader(const uint256& hash_prev_block) {
+ CBlockHeader ret;
+ ret.nVersion = nVersion;
+ ret.hashPrevBlock = hash_prev_block;
+ ret.hashMerkleRoot = hashMerkleRoot;
+ ret.nTime = nTime;
+ ret.nBits = nBits;
+ ret.nNonce = nNonce;
+ return ret;
+ };
+};
+
+/** HeadersSyncState:
+ *
+ * We wish to download a peer's headers chain in a DoS-resistant way.
+ *
+ * The Bitcoin protocol does not offer an easy way to determine the work on a
+ * peer's chain. Currently, we can query a peer's headers by using a GETHEADERS
+ * message, and our peer can return a set of up to 2000 headers that connect to
+ * something we know. If a peer's chain has more than 2000 blocks, then we need
+ * a way to verify that the chain actually has enough work on it to be useful to
+ * us -- by being above our anti-DoS minimum-chain-work threshold -- before we
+ * commit to storing those headers in memory. Otherwise, it would be cheap for
+ * an attacker to waste all our memory by serving us low-work headers
+ * (particularly for a new node coming online for the first time).
+ *
+ * To prevent memory-DoS with low-work headers, while still always being
+ * able to reorg to whatever the most-work chain is, we require that a chain
+ * meet a work threshold before committing it to memory. We can do this by
+ * downloading a peer's headers twice, whenever we are not sure that the chain
+ * has sufficient work:
+ *
+ * - In the first download phase, called pre-synchronization, we can calculate
+ * the work on the chain as we go (just by checking the nBits value on each
+ * header, and validating the proof-of-work).
+ *
+ * - Once we have reached a header where the cumulative chain work is
+ * sufficient, we switch to downloading the headers a second time, this time
+ * processing them fully, and possibly storing them in memory.
+ *
+ * To prevent an attacker from using (eg) the honest chain to convince us that
+ * they have a high-work chain, but then feeding us an alternate set of
+ * low-difficulty headers in the second phase, we store commitments to the
+ * chain we see in the first download phase that we check in the second phase,
+ * as follows:
+ *
+ * - In phase 1 (presync), store 1 bit (using a salted hash function) for every
+ * N headers that we see. With a reasonable choice of N, this uses relatively
+ * little memory even for a very long chain.
+ *
+ * - In phase 2 (redownload), keep a lookahead buffer and only accept headers
+ * from that buffer into the block index (permanent memory usage) once they
+ * have some target number of verified commitments on top of them. With this
+ * parametrization, we can achieve a given security target for potential
+ * permanent memory usage, while choosing N to minimize memory use during the
+ * sync (temporary, per-peer storage).
+ */
+
+class HeadersSyncState {
+public:
+ ~HeadersSyncState() {}
+
+ enum class State {
+ /** PRESYNC means the peer has not yet demonstrated their chain has
+ * sufficient work and we're only building commitments to the chain they
+ * serve us. */
+ PRESYNC,
+ /** REDOWNLOAD means the peer has given us a high-enough-work chain,
+ * and now we're redownloading the headers we saw before and trying to
+ * accept them */
+ REDOWNLOAD,
+ /** We're done syncing with this peer and can discard any remaining state */
+ FINAL
+ };
+
+ /** Return the current state of our download */
+ State GetState() const { return m_download_state; }
+
+ /** Return the height reached during the PRESYNC phase */
+ int64_t GetPresyncHeight() const { return m_current_height; }
+
+ /** Return the block timestamp of the last header received during the PRESYNC phase. */
+ uint32_t GetPresyncTime() const { return m_last_header_received.nTime; }
+
+ /** Return the amount of work in the chain received during the PRESYNC phase. */
+ arith_uint256 GetPresyncWork() const { return m_current_chain_work; }
+
+ /** Construct a HeadersSyncState object representing a headers sync via this
+ * download-twice mechanism).
+ *
+ * id: node id (for logging)
+ * consensus_params: parameters needed for difficulty adjustment validation
+ * chain_start: best known fork point that the peer's headers branch from
+ * minimum_required_work: amount of chain work required to accept the chain
+ */
+ HeadersSyncState(NodeId id, const Consensus::Params& consensus_params,
+ const CBlockIndex* chain_start, const arith_uint256& minimum_required_work);
+
+ /** Result data structure for ProcessNextHeaders. */
+ struct ProcessingResult {
+ std::vector<CBlockHeader> pow_validated_headers;
+ bool success{false};
+ bool request_more{false};
+ };
+
+ /** Process a batch of headers, once a sync via this mechanism has started
+ *
+ * received_headers: headers that were received over the network for processing.
+ * Assumes the caller has already verified the headers
+ * are continuous, and has checked that each header
+ * satisfies the proof-of-work target included in the
+ * header (but not necessarily verified that the
+ * proof-of-work target is correct and passes consensus
+ * rules).
+ * full_headers_message: true if the message was at max capacity,
+ * indicating more headers may be available
+ * ProcessingResult.pow_validated_headers: will be filled in with any
+ * headers that the caller can fully process and
+ * validate now (because these returned headers are
+ * on a chain with sufficient work)
+ * ProcessingResult.success: set to false if an error is detected and the sync is
+ * aborted; true otherwise.
+ * ProcessingResult.request_more: if true, the caller is suggested to call
+ * NextHeadersRequestLocator and send a getheaders message using it.
+ */
+ ProcessingResult ProcessNextHeaders(const std::vector<CBlockHeader>&
+ received_headers, bool full_headers_message);
+
+ /** Issue the next GETHEADERS message to our peer.
+ *
+ * This will return a locator appropriate for the current sync object, to continue the
+ * synchronization phase it is in.
+ */
+ CBlockLocator NextHeadersRequestLocator() const;
+
+private:
+ /** Clear out all download state that might be in progress (freeing any used
+ * memory), and mark this object as no longer usable.
+ */
+ void Finalize();
+
+ /**
+ * Only called in PRESYNC.
+ * Validate the work on the headers we received from the network, and
+ * store commitments for later. Update overall state with successfully
+ * processed headers.
+ * On failure, this invokes Finalize() and returns false.
+ */
+ bool ValidateAndStoreHeadersCommitments(const std::vector<CBlockHeader>& headers);
+
+ /** In PRESYNC, process and update state for a single header */
+ bool ValidateAndProcessSingleHeader(const CBlockHeader& current);
+
+ /** In REDOWNLOAD, check a header's commitment (if applicable) and add to
+ * buffer for later processing */
+ bool ValidateAndStoreRedownloadedHeader(const CBlockHeader& header);
+
+ /** Return a set of headers that satisfy our proof-of-work threshold */
+ std::vector<CBlockHeader> PopHeadersReadyForAcceptance();
+
+private:
+ /** NodeId of the peer (used for log messages) **/
+ const NodeId m_id;
+
+ /** We use the consensus params in our anti-DoS calculations */
+ const Consensus::Params& m_consensus_params;
+
+ /** Store the last block in our block index that the peer's chain builds from */
+ const CBlockIndex* m_chain_start{nullptr};
+
+ /** Minimum work that we're looking for on this chain. */
+ const arith_uint256 m_minimum_required_work;
+
+ /** Work that we've seen so far on the peer's chain */
+ arith_uint256 m_current_chain_work;
+
+ /** m_hasher is a salted hasher for making our 1-bit commitments to headers we've seen. */
+ const SaltedTxidHasher m_hasher;
+
+ /** A queue of commitment bits, created during the 1st phase, and verified during the 2nd. */
+ bitdeque<> m_header_commitments;
+
+ /** The (secret) offset on the heights for which to create commitments.
+ *
+ * m_header_commitments entries are created at any height h for which
+ * (h % HEADER_COMMITMENT_PERIOD) == m_commit_offset. */
+ const unsigned m_commit_offset;
+
+ /** m_max_commitments is a bound we calculate on how long an honest peer's chain could be,
+ * given the MTP rule.
+ *
+ * Any peer giving us more headers than this will have its sync aborted. This serves as a
+ * memory bound on m_header_commitments. */
+ uint64_t m_max_commitments{0};
+
+ /** Store the latest header received while in PRESYNC (initialized to m_chain_start) */
+ CBlockHeader m_last_header_received;
+
+ /** Height of m_last_header_received */
+ int64_t m_current_height{0};
+
+ /** During phase 2 (REDOWNLOAD), we buffer redownloaded headers in memory
+ * until enough commitments have been verified; those are stored in
+ * m_redownloaded_headers */
+ std::deque<CompressedHeader> m_redownloaded_headers;
+
+ /** Height of last header in m_redownloaded_headers */
+ int64_t m_redownload_buffer_last_height{0};
+
+ /** Hash of last header in m_redownloaded_headers (initialized to
+ * m_chain_start). We have to cache it because we don't have hashPrevBlock
+ * available in a CompressedHeader.
+ */
+ uint256 m_redownload_buffer_last_hash;
+
+ /** The hashPrevBlock entry for the first header in m_redownloaded_headers
+ * We need this to reconstruct the full header when it's time for
+ * processing.
+ */
+ uint256 m_redownload_buffer_first_prev_hash;
+
+ /** The accumulated work on the redownloaded chain. */
+ arith_uint256 m_redownload_chain_work;
+
+ /** Set this to true once we encounter the target blockheader during phase
+ * 2 (REDOWNLOAD). At this point, we can process and store all remaining
+ * headers still in m_redownloaded_headers.
+ */
+ bool m_process_all_remaining_headers{false};
+
+ /** Current state of our headers sync. */
+ State m_download_state{State::PRESYNC};
+};
+
+#endif // BITCOIN_HEADERSSYNC_H
diff --git a/src/httpserver.cpp b/src/httpserver.cpp
index 8e00a6278f..1a19555f76 100644
--- a/src/httpserver.cpp
+++ b/src/httpserver.cpp
@@ -21,11 +21,11 @@
#include <util/threadnames.h>
#include <util/translation.h>
+#include <cstdio>
+#include <cstdlib>
#include <deque>
#include <memory>
#include <optional>
-#include <stdio.h>
-#include <stdlib.h>
#include <string>
#include <sys/types.h>
@@ -142,7 +142,8 @@ static std::vector<CSubNet> rpc_allow_subnets;
//! Work queue for handling longer requests off the event loop thread
static std::unique_ptr<WorkQueue<HTTPClosure>> g_work_queue{nullptr};
//! Handlers for (sub)paths
-static std::vector<HTTPPathHandler> pathHandlers;
+static GlobalMutex g_httppathhandlers_mutex;
+static std::vector<HTTPPathHandler> pathHandlers GUARDED_BY(g_httppathhandlers_mutex);
//! Bound listening sockets
static std::vector<evhttp_bound_socket *> boundSockets;
@@ -243,6 +244,7 @@ static void http_request_cb(struct evhttp_request* req, void* arg)
// Find registered handler for prefix
std::string strURI = hreq->GetURI();
std::string path;
+ LOCK(g_httppathhandlers_mutex);
std::vector<HTTPPathHandler>::const_iterator i = pathHandlers.begin();
std::vector<HTTPPathHandler>::const_iterator iend = pathHandlers.end();
for (; i != iend; ++i) {
@@ -318,7 +320,7 @@ static bool HTTPBindAddresses(struct evhttp* http)
// Bind addresses
for (std::vector<std::pair<std::string, uint16_t> >::iterator i = endpoints.begin(); i != endpoints.end(); ++i) {
- LogPrint(BCLog::HTTP, "Binding RPC on address %s port %i\n", i->first, i->second);
+ LogPrintf("Binding RPC on address %s port %i\n", i->first, i->second);
evhttp_bound_socket *bind_handle = evhttp_bind_socket_with_handle(http, i->first.empty() ? nullptr : i->first.c_str(), i->second);
if (bind_handle) {
CNetAddr addr;
@@ -674,11 +676,13 @@ std::optional<std::string> GetQueryParameterFromUri(const char* uri, const std::
void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler)
{
LogPrint(BCLog::HTTP, "Registering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch);
+ LOCK(g_httppathhandlers_mutex);
pathHandlers.push_back(HTTPPathHandler(prefix, exactMatch, handler));
}
void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch)
{
+ LOCK(g_httppathhandlers_mutex);
std::vector<HTTPPathHandler>::iterator i = pathHandlers.begin();
std::vector<HTTPPathHandler>::iterator iend = pathHandlers.end();
for (; i != iend; ++i)
diff --git a/src/i2p.cpp b/src/i2p.cpp
index c45bcc15d2..28be8009dc 100644
--- a/src/i2p.cpp
+++ b/src/i2p.cpp
@@ -12,11 +12,11 @@
#include <netaddress.h>
#include <netbase.h>
#include <random.h>
-#include <util/strencodings.h>
#include <tinyformat.h>
#include <util/readwritefile.h>
#include <util/sock.h>
#include <util/spanparsing.h>
+#include <util/strencodings.h>
#include <util/system.h>
#include <chrono>
@@ -115,8 +115,19 @@ namespace sam {
Session::Session(const fs::path& private_key_file,
const CService& control_host,
CThreadInterrupt* interrupt)
- : m_private_key_file(private_key_file), m_control_host(control_host), m_interrupt(interrupt),
- m_control_sock(std::make_unique<Sock>(INVALID_SOCKET))
+ : m_private_key_file{private_key_file},
+ m_control_host{control_host},
+ m_interrupt{interrupt},
+ m_control_sock{std::make_unique<Sock>(INVALID_SOCKET)},
+ m_transient{false}
+{
+}
+
+Session::Session(const CService& control_host, CThreadInterrupt* interrupt)
+ : m_control_host{control_host},
+ m_interrupt{interrupt},
+ m_control_sock{std::make_unique<Sock>(INVALID_SOCKET)},
+ m_transient{true}
{
}
@@ -314,6 +325,7 @@ void Session::DestGenerate(const Sock& sock)
// https://geti2p.net/spec/common-structures#key-certificates
// "7" or "EdDSA_SHA512_Ed25519" - "Recent Router Identities and Destinations".
// Use "7" because i2pd <2.24.0 does not recognize the textual form.
+ // If SIGNATURE_TYPE is not specified, then the default one is DSA_SHA1.
const Reply& reply = SendRequestAndGetReply(sock, "DEST GENERATE SIGNATURE_TYPE=7", false);
m_private_key = DecodeI2PBase64(reply.Get("PRIV"));
@@ -355,29 +367,47 @@ void Session::CreateIfNotCreatedAlready()
return;
}
- Log("Creating SAM session with %s", m_control_host.ToString());
+ const auto session_type = m_transient ? "transient" : "persistent";
+ const auto session_id = GetRandHash().GetHex().substr(0, 10); // full is overkill, too verbose in the logs
+
+ Log("Creating %s SAM session %s with %s", session_type, session_id, m_control_host.ToString());
auto sock = Hello();
- const auto& [read_ok, data] = ReadBinaryFile(m_private_key_file);
- if (read_ok) {
- m_private_key.assign(data.begin(), data.end());
+ if (m_transient) {
+ // The destination (private key) is generated upon session creation and returned
+ // in the reply in DESTINATION=.
+ const Reply& reply = SendRequestAndGetReply(
+ *sock,
+ strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT SIGNATURE_TYPE=7", session_id));
+
+ m_private_key = DecodeI2PBase64(reply.Get("DESTINATION"));
} else {
- GenerateAndSavePrivateKey(*sock);
- }
+ // Read our persistent destination (private key) from disk or generate
+ // one and save it to disk. Then use it when creating the session.
+ const auto& [read_ok, data] = ReadBinaryFile(m_private_key_file);
+ if (read_ok) {
+ m_private_key.assign(data.begin(), data.end());
+ } else {
+ GenerateAndSavePrivateKey(*sock);
+ }
- const std::string& session_id = GetRandHash().GetHex().substr(0, 10); // full is an overkill, too verbose in the logs
- const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key));
+ const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key));
- SendRequestAndGetReply(*sock, strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s",
- session_id, private_key_b64));
+ SendRequestAndGetReply(*sock,
+ strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s",
+ session_id,
+ private_key_b64));
+ }
m_my_addr = CService(DestBinToAddr(MyDestination()), I2P_SAM31_PORT);
m_session_id = session_id;
m_control_sock = std::move(sock);
- LogPrintfCategory(BCLog::I2P, "SAM session created: session id=%s, my address=%s\n",
- m_session_id, m_my_addr.ToString());
+ Log("%s SAM session %s created, my address=%s",
+ Capitalize(session_type),
+ m_session_id,
+ m_my_addr.ToString());
}
std::unique_ptr<Sock> Session::StreamAccept()
@@ -405,9 +435,9 @@ void Session::Disconnect()
{
if (m_control_sock->Get() != INVALID_SOCKET) {
if (m_session_id.empty()) {
- Log("Destroying incomplete session");
+ Log("Destroying incomplete SAM session");
} else {
- Log("Destroying session %s", m_session_id);
+ Log("Destroying SAM session %s", m_session_id);
}
}
m_control_sock = std::make_unique<Sock>(INVALID_SOCKET);
diff --git a/src/i2p.h b/src/i2p.h
index eb0a10103d..ebbcb437da 100644
--- a/src/i2p.h
+++ b/src/i2p.h
@@ -71,6 +71,19 @@ public:
CThreadInterrupt* interrupt);
/**
+ * Construct a transient session which will generate its own I2P private key
+ * rather than read the one from disk (it will not be saved on disk either and
+ * will be lost once this object is destroyed). This will not initiate any IO,
+ * the session will be lazily created later when first used.
+ * @param[in] control_host Location of the SAM proxy.
+ * @param[in,out] interrupt If this is signaled then all operations are canceled as soon as
+ * possible and executing methods throw an exception. Notice: only a pointer to the
+ * `CThreadInterrupt` object is saved, so it must not be destroyed earlier than this
+ * `Session` object.
+ */
+ Session(const CService& control_host, CThreadInterrupt* interrupt);
+
+ /**
* Destroy the session, closing the internally used sockets. The sockets that have been
* returned by `Accept()` or `Connect()` will not be closed, but they will be closed by
* the SAM proxy because the session is destroyed. So they will return an error next time
@@ -262,6 +275,12 @@ private:
* SAM session id.
*/
std::string m_session_id GUARDED_BY(m_mutex);
+
+ /**
+ * Whether this is a transient session (the I2P private key will not be
+ * read or written to disk).
+ */
+ const bool m_transient;
};
} // namespace sam
diff --git a/src/index/base.cpp b/src/index/base.cpp
index 1ebe89ef7c..3eea09b17d 100644
--- a/src/index/base.cpp
+++ b/src/index/base.cpp
@@ -18,6 +18,9 @@
#include <validation.h> // For g_chainman
#include <warnings.h>
+#include <string>
+#include <utility>
+
using node::ReadBlockFromDisk;
constexpr uint8_t DB_BEST_BLOCK{'B'};
@@ -62,8 +65,8 @@ void BaseIndex::DB::WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator
batch.Write(DB_BEST_BLOCK, locator);
}
-BaseIndex::BaseIndex(std::unique_ptr<interfaces::Chain> chain)
- : m_chain{std::move(chain)} {}
+BaseIndex::BaseIndex(std::unique_ptr<interfaces::Chain> chain, std::string name)
+ : m_chain{std::move(chain)}, m_name{std::move(name)} {}
BaseIndex::~BaseIndex()
{
@@ -295,6 +298,10 @@ void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const
}
interfaces::BlockInfo block_info = kernel::MakeBlockInfo(pindex, block.get());
if (CustomAppend(block_info)) {
+ // Setting the best block index is intentionally the last step of this
+ // function, so BlockUntilSyncedToCurrentChain callers waiting for the
+ // best block index to be updated can rely on the block being fully
+ // processed, and the index object being safe to delete.
SetBestBlockIndex(pindex);
} else {
FatalError("%s: Failed to write block %s to index",
@@ -411,10 +418,17 @@ IndexSummary BaseIndex::GetSummary() const
void BaseIndex::SetBestBlockIndex(const CBlockIndex* block) {
assert(!node::fPruneMode || AllowPrune());
- m_best_block_index = block;
if (AllowPrune() && block) {
node::PruneLockInfo prune_lock;
prune_lock.height_first = block->nHeight;
WITH_LOCK(::cs_main, m_chainstate->m_blockman.UpdatePruneLock(GetName(), prune_lock));
}
+
+ // Intentionally set m_best_block_index as the last step in this function,
+ // after updating prune locks above, and after making any other references
+ // to *this, so the BlockUntilSyncedToCurrentChain function (which checks
+ // m_best_block_index as an optimization) can be used to wait for the last
+ // BlockConnected notification and safely assume that prune locks are
+ // updated and that the index object is safe to delete.
+ m_best_block_index = block;
}
diff --git a/src/index/base.h b/src/index/base.h
index 5a484377e7..349178a535 100644
--- a/src/index/base.h
+++ b/src/index/base.h
@@ -10,9 +10,11 @@
#include <threadinterrupt.h>
#include <validationinterface.h>
+#include <string>
+
class CBlock;
class CBlockIndex;
-class CChainState;
+class Chainstate;
namespace interfaces {
class Chain;
} // namespace interfaces
@@ -94,7 +96,8 @@ private:
protected:
std::unique_ptr<interfaces::Chain> m_chain;
- CChainState* m_chainstate{nullptr};
+ Chainstate* m_chainstate{nullptr};
+ const std::string m_name;
void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override;
@@ -117,13 +120,13 @@ protected:
virtual DB& GetDB() const = 0;
/// Get the name of the index for display in logs.
- virtual const char* GetName() const = 0;
+ const std::string& GetName() const LIFETIMEBOUND { return m_name; }
/// Update the internal best block index as well as the prune lock.
void SetBestBlockIndex(const CBlockIndex* block);
public:
- BaseIndex(std::unique_ptr<interfaces::Chain> chain);
+ BaseIndex(std::unique_ptr<interfaces::Chain> chain, std::string name);
/// Destructor interrupts sync thread if running and blocks until it exits.
virtual ~BaseIndex();
diff --git a/src/index/blockfilterindex.cpp b/src/index/blockfilterindex.cpp
index f4837f3456..292e11c874 100644
--- a/src/index/blockfilterindex.cpp
+++ b/src/index/blockfilterindex.cpp
@@ -97,7 +97,8 @@ static std::map<BlockFilterType, BlockFilterIndex> g_filter_indexes;
BlockFilterIndex::BlockFilterIndex(std::unique_ptr<interfaces::Chain> chain, BlockFilterType filter_type,
size_t n_cache_size, bool f_memory, bool f_wipe)
- : BaseIndex(std::move(chain)), m_filter_type(filter_type)
+ : BaseIndex(std::move(chain), BlockFilterTypeName(filter_type) + " block filter index")
+ , m_filter_type(filter_type)
{
const std::string& filter_name = BlockFilterTypeName(filter_type);
if (filter_name.empty()) throw std::invalid_argument("unknown filter_type");
@@ -105,7 +106,6 @@ BlockFilterIndex::BlockFilterIndex(std::unique_ptr<interfaces::Chain> chain, Blo
fs::path path = gArgs.GetDataDirNet() / "indexes" / "blockfilter" / fs::u8path(filter_name);
fs::create_directories(path);
- m_name = filter_name + " block filter index";
m_db = std::make_unique<BaseIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe);
m_filter_fileseq = std::make_unique<FlatFileSeq>(std::move(path), "fltr", FLTR_FILE_CHUNK_SIZE);
}
diff --git a/src/index/blockfilterindex.h b/src/index/blockfilterindex.h
index 968eccb6b3..9e69388dc8 100644
--- a/src/index/blockfilterindex.h
+++ b/src/index/blockfilterindex.h
@@ -5,12 +5,15 @@
#ifndef BITCOIN_INDEX_BLOCKFILTERINDEX_H
#define BITCOIN_INDEX_BLOCKFILTERINDEX_H
+#include <attributes.h>
#include <blockfilter.h>
#include <chain.h>
#include <flatfile.h>
#include <index/base.h>
#include <util/hasher.h>
+static const char* const DEFAULT_BLOCKFILTERINDEX = "0";
+
/** Interval between compact filter checkpoints. See BIP 157. */
static constexpr int CFCHECKPT_INTERVAL = 1000;
@@ -25,7 +28,6 @@ class BlockFilterIndex final : public BaseIndex
{
private:
BlockFilterType m_filter_type;
- std::string m_name;
std::unique_ptr<BaseIndex::DB> m_db;
FlatFilePos m_next_filter_pos;
@@ -49,9 +51,7 @@ protected:
bool CustomRewind(const interfaces::BlockKey& current_tip, const interfaces::BlockKey& new_tip) override;
- BaseIndex::DB& GetDB() const override { return *m_db; }
-
- const char* GetName() const override { return m_name.c_str(); }
+ BaseIndex::DB& GetDB() const LIFETIMEBOUND override { return *m_db; }
public:
/** Constructs the index, which becomes available to be queried. */
diff --git a/src/index/coinstatsindex.cpp b/src/index/coinstatsindex.cpp
index b9029e946a..d3559b1b75 100644
--- a/src/index/coinstatsindex.cpp
+++ b/src/index/coinstatsindex.cpp
@@ -6,6 +6,7 @@
#include <coins.h>
#include <crypto/muhash.h>
#include <index/coinstatsindex.h>
+#include <kernel/coinstats.h>
#include <node/blockstorage.h>
#include <serialize.h>
#include <txdb.h>
@@ -104,7 +105,7 @@ struct DBHashKey {
std::unique_ptr<CoinStatsIndex> g_coin_stats_index;
CoinStatsIndex::CoinStatsIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)
- : BaseIndex(std::move(chain))
+ : BaseIndex(std::move(chain), "coinstatsindex")
{
fs::path path{gArgs.GetDataDirNet() / "indexes" / "coinstats"};
fs::create_directories(path);
@@ -322,13 +323,13 @@ static bool LookUpOne(const CDBWrapper& db, const interfaces::BlockKey& block, D
return db.Read(DBHashKey(block.hash), result);
}
-std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex* block_index) const
+std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex& block_index) const
{
- CCoinsStats stats{Assert(block_index)->nHeight, block_index->GetBlockHash()};
+ CCoinsStats stats{block_index.nHeight, block_index.GetBlockHash()};
stats.index_used = true;
DBVal entry;
- if (!LookUpOne(*m_db, {block_index->GetBlockHash(), block_index->nHeight}, entry)) {
+ if (!LookUpOne(*m_db, {block_index.GetBlockHash(), block_index.nHeight}, entry)) {
return std::nullopt;
}
diff --git a/src/index/coinstatsindex.h b/src/index/coinstatsindex.h
index c4af223388..aa0d7f9fd5 100644
--- a/src/index/coinstatsindex.h
+++ b/src/index/coinstatsindex.h
@@ -5,11 +5,16 @@
#ifndef BITCOIN_INDEX_COINSTATSINDEX_H
#define BITCOIN_INDEX_COINSTATSINDEX_H
-#include <chain.h>
#include <crypto/muhash.h>
-#include <flatfile.h>
#include <index/base.h>
-#include <kernel/coinstats.h>
+
+class CBlockIndex;
+class CDBBatch;
+namespace kernel {
+struct CCoinsStats;
+}
+
+static constexpr bool DEFAULT_COINSTATSINDEX{false};
/**
* CoinStatsIndex maintains statistics on the UTXO set.
@@ -17,7 +22,6 @@
class CoinStatsIndex final : public BaseIndex
{
private:
- std::string m_name;
std::unique_ptr<BaseIndex::DB> m_db;
MuHash3072 m_muhash;
@@ -49,14 +53,12 @@ protected:
BaseIndex::DB& GetDB() const override { return *m_db; }
- const char* GetName() const override { return "coinstatsindex"; }
-
public:
// Constructs the index, which becomes available to be queried.
explicit CoinStatsIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory = false, bool f_wipe = false);
// Look up stats for a specific block using CBlockIndex
- std::optional<kernel::CCoinsStats> LookUpStats(const CBlockIndex* block_index) const;
+ std::optional<kernel::CCoinsStats> LookUpStats(const CBlockIndex& block_index) const;
};
/// The global UTXO set hash object.
diff --git a/src/index/txindex.cpp b/src/index/txindex.cpp
index b719aface8..a4fe1b611e 100644
--- a/src/index/txindex.cpp
+++ b/src/index/txindex.cpp
@@ -49,7 +49,7 @@ bool TxIndex::DB::WriteTxs(const std::vector<std::pair<uint256, CDiskTxPos>>& v_
}
TxIndex::TxIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)
- : BaseIndex(std::move(chain)), m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe))
+ : BaseIndex(std::move(chain), "txindex"), m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe))
{}
TxIndex::~TxIndex() = default;
diff --git a/src/index/txindex.h b/src/index/txindex.h
index be240c4582..4cea35045d 100644
--- a/src/index/txindex.h
+++ b/src/index/txindex.h
@@ -7,6 +7,8 @@
#include <index/base.h>
+static constexpr bool DEFAULT_TXINDEX{false};
+
/**
* TxIndex is used to look up transactions included in the blockchain by hash.
* The index is written to a LevelDB database and records the filesystem
@@ -27,8 +29,6 @@ protected:
BaseIndex::DB& GetDB() const override;
- const char* GetName() const override { return "txindex"; }
-
public:
/// Constructs the index, which becomes available to be queried.
explicit TxIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory = false, bool f_wipe = false);
diff --git a/src/init.cpp b/src/init.cpp
index a94bbe6460..24659de3df 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -11,6 +11,7 @@
#include <kernel/checks.h>
#include <kernel/mempool_persist.h>
+#include <kernel/validation_cache_sizes.h>
#include <addrman.h>
#include <banman.h>
@@ -31,7 +32,6 @@
#include <interfaces/init.h>
#include <interfaces/node.h>
#include <mapport.h>
-#include <mempool_args.h>
#include <net.h>
#include <net_permissions.h>
#include <net_processing.h>
@@ -40,10 +40,14 @@
#include <node/blockstorage.h>
#include <node/caches.h>
#include <node/chainstate.h>
+#include <node/chainstatemanager_args.h>
#include <node/context.h>
#include <node/interface_ui.h>
+#include <node/mempool_args.h>
#include <node/mempool_persist_args.h>
#include <node/miner.h>
+#include <node/txreconciliation.h>
+#include <node/validation_cache_args.h>
#include <policy/feerate.h>
#include <policy/fees.h>
#include <policy/fees_args.h>
@@ -105,7 +109,9 @@
#endif
using kernel::DumpMempool;
+using kernel::ValidationCacheSizes;
+using node::ApplyArgsManOptions;
using node::CacheSizes;
using node::CalculateCacheSizes;
using node::DEFAULT_PERSIST_MEMPOOL;
@@ -259,7 +265,7 @@ void Shutdown(NodeContext& node)
// FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing
if (node.chainman) {
LOCK(cs_main);
- for (CChainState* chainstate : node.chainman->GetAll()) {
+ for (Chainstate* chainstate : node.chainman->GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
}
@@ -290,7 +296,7 @@ void Shutdown(NodeContext& node)
if (node.chainman) {
LOCK(cs_main);
- for (CChainState* chainstate : node.chainman->GetAll()) {
+ for (Chainstate* chainstate : node.chainman->GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
chainstate->ResetCoinsViews();
@@ -472,7 +478,6 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-onlynet=<net>", "Make automatic outbound connections only to network <net> (" + Join(GetNetworkNames(), ", ") + "). Inbound and manual connections are not affected by this option. It can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
- argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
// TODO: remove the sentence "Nodes not using ... incoming connections." once the changes from
// https://github.com/bitcoin/bitcoin/pull/23542 have become widespread.
argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port>. Nodes not using the default ports (default: %u, testnet: %u, signet: %u, regtest: %u) are unlikely to get incoming connections. Not relevant for I2P (see doc/i2p.md).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
@@ -481,6 +486,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.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);
argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-timeout=<n>", strprintf("Specify socket connection timeout in milliseconds. If an initial attempt to connect is unsuccessful after this amount of time, drop it (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
+ argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-peertimeout=<n>", strprintf("Specify a p2p connection timeout delay in seconds. After connecting to a peer, wait this amount of time before considering disconnection based on inactivity (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
argsman.AddArg("-torcontrol=<ip>:<port>", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::CONNECTION);
@@ -548,20 +554,25 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-addrmantest", "Allows to test address relay on localhost", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-capturemessages", "Capture all P2P messages to disk", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-mocktime=<n>", "Replace actual time with " + UNIX_EPOCH_TIME + " (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
- argsman.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);
- argsman.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);
+ argsman.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_BYTES >> 20), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
+ argsman.AddArg("-maxtipage=<n>",
+ strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)",
+ Ticks<std::chrono::seconds>(DEFAULT_MAX_TIP_AGE)),
+ ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-printpriority", strprintf("Log transaction fee rate in " + CURRENCY_UNIT + "/kvB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-uacomment=<cmt>", "Append comment to the user agent string", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
SetupChainParamsBaseOptions(argsman);
argsman.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);
- argsman.AddArg("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kvB) 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);
+ argsman.AddArg("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kvB) used to define cost of relay, used for mempool limiting and replacement policy. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kvB) 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);
argsman.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);
argsman.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.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);
argsman.AddArg("-mempoolfullrbf", strprintf("Accept transaction replace-by-fee without requiring replaceability signaling (default: %u)", DEFAULT_MEMPOOL_FULL_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
+ argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY,
+ OptionsCategory::NODE_RELAY);
argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.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. (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
@@ -719,6 +730,16 @@ void InitParameterInteraction(ArgsManager& args)
if (args.SoftSetBoolArg("-whitelistrelay", true))
LogPrintf("%s: parameter interaction: -whitelistforcerelay=1 -> setting -whitelistrelay=1\n", __func__);
}
+ if (args.IsArgSet("-onlynet")) {
+ const auto onlynets = args.GetArgs("-onlynet");
+ bool clearnet_reachable = std::any_of(onlynets.begin(), onlynets.end(), [](const auto& net) {
+ const auto n = ParseNetwork(net);
+ return n == NET_IPV4 || n == NET_IPV6;
+ });
+ if (!clearnet_reachable && args.SoftSetBoolArg("-dnsseed", false)) {
+ LogPrintf("%s: parameter interaction: -onlynet excludes IPv4 and IPv6 -> setting -dnsseed=0\n", __func__);
+ }
+ }
}
/**
@@ -738,7 +759,7 @@ namespace { // Variables internal to initialization process only
int nMaxConnections;
int nUserMaxConnections;
int nFD;
-ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK | NODE_NETWORK_LIMITED | NODE_WITNESS);
+ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS);
int64_t peer_connect_timeout;
std::set<BlockFilterType> g_enabled_filter_types;
@@ -911,39 +932,7 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb
// ********************************************************* Step 3: parameter-to-internal-flags
init::SetLoggingCategories(args);
-
- fCheckBlockIndex = args.GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks());
- fCheckpointsEnabled = args.GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED);
-
- hashAssumeValid = uint256S(args.GetArg("-assumevalid", chainparams.GetConsensus().defaultAssumeValid.GetHex()));
- if (!hashAssumeValid.IsNull())
- LogPrintf("Assuming ancestors of block %s have valid signatures.\n", hashAssumeValid.GetHex());
- else
- LogPrintf("Validating signatures for all blocks.\n");
-
- if (args.IsArgSet("-minimumchainwork")) {
- const std::string minChainWorkStr = args.GetArg("-minimumchainwork", "");
- if (!IsHexNumber(minChainWorkStr)) {
- return InitError(strprintf(Untranslated("Invalid non-hex (%s) minimum chain work value specified"), minChainWorkStr));
- }
- nMinimumChainWork = UintToArith256(uint256S(minChainWorkStr));
- } else {
- nMinimumChainWork = UintToArith256(chainparams.GetConsensus().nMinimumChainWork);
- }
- LogPrintf("Setting nMinimumChainWork=%s\n", nMinimumChainWork.GetHex());
- if (nMinimumChainWork < UintToArith256(chainparams.GetConsensus().nMinimumChainWork)) {
- LogPrintf("Warning: nMinimumChainWork set below default value of %s\n", chainparams.GetConsensus().nMinimumChainWork.GetHex());
- }
-
- // 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 (args.IsArgSet("-incrementalrelayfee")) {
- if (std::optional<CAmount> inc_relay_fee = ParseMoney(args.GetArg("-incrementalrelayfee", ""))) {
- ::incrementalRelayFee = CFeeRate{inc_relay_fee.value()};
- } else {
- return InitError(AmountErrMsg("incrementalrelayfee", args.GetArg("-incrementalrelayfee", "")));
- }
- }
+ init::SetLoggingLevel(args);
// block pruning; get the amount of disk space (in MiB) to allot for block & undo files
int64_t nPruneArg = args.GetIntArg("-prune", 0);
@@ -952,14 +941,12 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb
}
nPruneTarget = (uint64_t) nPruneArg * 1024 * 1024;
if (nPruneArg == 1) { // manual pruning: -prune=1
- LogPrintf("Block pruning enabled. Use RPC call pruneblockchain(height) to manually prune block and undo files.\n");
nPruneTarget = std::numeric_limits<uint64_t>::max();
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));
}
- LogPrintf("Prune configured to target %u MiB on disk for block and undo files.\n", nPruneTarget / 1024 / 1024);
fPruneMode = true;
}
@@ -973,19 +960,6 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb
return InitError(Untranslated("peertimeout must be a positive integer."));
}
- if (args.IsArgSet("-minrelaytxfee")) {
- if (std::optional<CAmount> min_relay_fee = ParseMoney(args.GetArg("-minrelaytxfee", ""))) {
- // High fee check is done afterward in CWallet::Create()
- ::minRelayTxFee = CFeeRate{min_relay_fee.value()};
- } else {
- return InitError(AmountErrMsg("minrelaytxfee", args.GetArg("-minrelaytxfee", "")));
- }
- } else if (incrementalRelayFee > ::minRelayTxFee) {
- // Allow only setting incrementalRelayFee to control both
- ::minRelayTxFee = incrementalRelayFee;
- LogPrintf("Increasing minrelaytxfee to %s to match incrementalrelayfee\n",::minRelayTxFee.ToString());
- }
-
// Sanity check argument for min fee for including tx in block
// TODO: Harmonize which arguments need sanity checking and where that happens
if (args.IsArgSet("-blockmintxfee")) {
@@ -994,28 +968,10 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb
}
}
- // Feerate used to define dust. Shouldn't be changed lightly as old
- // implementations may inadvertently create non-standard transactions
- if (args.IsArgSet("-dustrelayfee")) {
- if (std::optional<CAmount> parsed = ParseMoney(args.GetArg("-dustrelayfee", ""))) {
- dustRelayFee = CFeeRate{parsed.value()};
- } else {
- return InitError(AmountErrMsg("dustrelayfee", args.GetArg("-dustrelayfee", "")));
- }
- }
-
- fRequireStandard = !args.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());
- if (!chainparams.IsTestChain() && !fRequireStandard) {
- return InitError(strprintf(Untranslated("acceptnonstdtxn is not currently supported for %s chain"), chainparams.NetworkIDString()));
- }
nBytesPerSigOp = args.GetIntArg("-bytespersigop", nBytesPerSigOp);
if (!g_wallet_init_interface.ParameterInteraction()) return false;
- fIsBareMultisigStd = args.GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG);
- fAcceptDatacarrier = args.GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER);
- nMaxDatacarrierBytes = args.GetIntArg("-datacarriersize", nMaxDatacarrierBytes);
-
// Option to startup with mocktime set (used for regression testing):
SetMockTime(args.GetIntArg("-mocktime", 0)); // SetMockTime(0) is a no-op
@@ -1028,8 +984,6 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb
if (args.GetIntArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) > 1)
return InitError(Untranslated("Unknown rpcserialversion requested."));
- nMaxTipAge = args.GetIntArg("-maxtipage", DEFAULT_MAX_TIP_AGE);
-
if (args.GetBoolArg("-reindex-chainstate", false)) {
// indexes that must be deactivated to prevent index corruption, see #24630
if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) {
@@ -1077,13 +1031,23 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb
}
#endif // USE_SYSCALL_SANDBOX
+ // Also report errors from parsing before daemonization
+ {
+ ChainstateManager::Options chainman_opts_dummy{
+ .chainparams = chainparams,
+ };
+ if (const auto error{ApplyArgsManOptions(args, chainman_opts_dummy)}) {
+ return InitError(*error);
+ }
+ }
+
return true;
}
static bool LockDataDirectory(bool probeOnly)
{
// Make sure only a single Bitcoin process is using the data directory.
- fs::path datadir = gArgs.GetDataDirNet();
+ const fs::path& datadir = gArgs.GetDataDirNet();
if (!DirIsWritable(datadir)) {
return InitError(strprintf(_("Cannot write to data directory '%s'; check permissions."), fs::PathToString(datadir)));
}
@@ -1156,8 +1120,13 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
args.GetArg("-datadir", ""), fs::PathToString(fs::current_path()));
}
- InitSignatureCache();
- InitScriptExecutionCache();
+ ValidationCacheSizes validation_cache_sizes{};
+ ApplyArgsManOptions(args, validation_cache_sizes);
+ if (!InitSignatureCache(validation_cache_sizes.signature_cache_bytes)
+ || !InitScriptExecutionCache(validation_cache_sizes.script_execution_cache_bytes))
+ {
+ return InitError(strprintf(_("Unable to allocate memory for -maxsigcachesize: '%s' MiB"), args.GetIntArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_BYTES >> 20)));
+ }
int script_threads = args.GetIntArg("-par", DEFAULT_SCRIPTCHECK_THREADS);
if (script_threads <= 0) {
@@ -1174,7 +1143,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
LogPrintf("Script verification uses %d additional threads\n", script_threads);
if (script_threads >= 1) {
- g_parallel_script_checks = true;
StartScriptCheckWorkerThreads(script_threads);
}
@@ -1285,6 +1253,51 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// as they would never get updated.
if (!ignores_incoming_txs) node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(args));
+ // Check port numbers
+ for (const std::string port_option : {
+ "-port",
+ "-rpcport",
+ }) {
+ if (args.IsArgSet(port_option)) {
+ const std::string port = args.GetArg(port_option, "");
+ uint16_t n;
+ if (!ParseUInt16(port, &n) || n == 0) {
+ return InitError(InvalidPortErrMsg(port_option, port));
+ }
+ }
+ }
+
+ for (const std::string port_option : {
+ "-i2psam",
+ "-onion",
+ "-proxy",
+ "-rpcbind",
+ "-torcontrol",
+ "-whitebind",
+ "-zmqpubhashblock",
+ "-zmqpubhashtx",
+ "-zmqpubrawblock",
+ "-zmqpubrawtx",
+ "-zmqpubsequence",
+ }) {
+ for (const std::string& socket_addr : args.GetArgs(port_option)) {
+ std::string host_out;
+ uint16_t port_out{0};
+ if (!SplitHostPort(socket_addr, port_out, host_out)) {
+ return InitError(InvalidPortErrMsg(port_option, socket_addr));
+ }
+ }
+ }
+
+ for (const std::string& socket_addr : args.GetArgs("-bind")) {
+ std::string host_out;
+ uint16_t port_out{0};
+ std::string bind_socket_addr = socket_addr.substr(0, socket_addr.rfind('='));
+ if (!SplitHostPort(bind_socket_addr, port_out, host_out)) {
+ return InitError(InvalidPortErrMsg("-bind", socket_addr));
+ }
+ }
+
// sanitize comments per BIP-0014, format user agent and check total size
std::vector<std::string> uacomments;
for (const std::string& cmt : args.GetArgs("-uacomment")) {
@@ -1315,6 +1328,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
}
if (!args.IsArgSet("-cjdnsreachable")) {
+ if (args.IsArgSet("-onlynet") && IsReachable(NET_CJDNS)) {
+ return InitError(
+ _("Outbound connections restricted to CJDNS (-onlynet=cjdns) but "
+ "-cjdnsreachable is not provided"));
+ }
SetReachable(NET_CJDNS, false);
}
// Now IsReachable(NET_CJDNS) is true if:
@@ -1322,6 +1340,13 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// 2.1. -onlynet is not given or
// 2.2. -onlynet=cjdns is given
+ // Requesting DNS seeds entails connecting to IPv4/IPv6, which -onlynet options may prohibit:
+ // If -dnsseed=1 is explicitly specified, abort. If it's left unspecified by the user, we skip
+ // the DNS seeds by adjusting -dnsseed in InitParameterInteraction.
+ if (args.GetBoolArg("-dnsseed") == true && !IsReachable(NET_IPV4) && !IsReachable(NET_IPV6)) {
+ return InitError(strprintf(_("Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6")));
+ };
+
// Check for host lookup allowed before parsing any network related parameters
fNameLookup = args.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);
@@ -1348,6 +1373,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
onion_proxy = addrProxy;
}
+ const bool onlynet_used_with_onion{args.IsArgSet("-onlynet") && IsReachable(NET_ONION)};
+
// -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses
// -noonion (or -onion=0) disables connecting to .onion entirely
// An empty string is used to not override the onion proxy (in which case it defaults to -proxy set above, or none)
@@ -1355,6 +1382,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
if (onionArg != "") {
if (onionArg == "0") { // Handle -noonion/-onion=0
onion_proxy = Proxy{};
+ if (onlynet_used_with_onion) {
+ return InitError(
+ _("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for "
+ "reaching the Tor network is explicitly forbidden: -onion=0"));
+ }
} else {
CService addr;
if (!Lookup(onionArg, addr, 9050, fNameLookup) || !addr.IsValid()) {
@@ -1367,11 +1399,14 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
if (onion_proxy.IsValid()) {
SetProxy(NET_ONION, onion_proxy);
} else {
- if (args.IsArgSet("-onlynet") && IsReachable(NET_ONION)) {
+ // If -listenonion is set, then we will (try to) connect to the Tor control port
+ // later from the torcontrol thread and may retrieve the onion proxy from there.
+ const bool listenonion_disabled{!args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)};
+ if (onlynet_used_with_onion && listenonion_disabled) {
return InitError(
_("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for "
- "reaching the Tor network is not provided (no -proxy= and no -onion= given) or "
- "it is explicitly forbidden (-onion=0)"));
+ "reaching the Tor network is not provided: none of -proxy, -onion or "
+ "-listenonion is given"));
}
SetReachable(NET_ONION, false);
}
@@ -1396,6 +1431,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
fReindex = args.GetBoolArg("-reindex", false);
bool fReindexChainState = args.GetBoolArg("-reindex-chainstate", false);
+ ChainstateManager::Options chainman_opts{
+ .chainparams = chainparams,
+ .adjusted_time_callback = GetAdjustedTime,
+ };
+ Assert(!ApplyArgsManOptions(args, chainman_opts)); // no error can happen, already checked in AppInitParameterInteraction
// cache size calculations
CacheSizes cache_sizes = CalculateCacheSizes(args, g_enabled_filter_types.size());
@@ -1418,7 +1458,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
.estimator = node.fee_estimator.get(),
.check_ratio = chainparams.DefaultConsistencyChecks() ? 1 : 0,
};
- ApplyArgsManOptions(args, mempool_opts);
+ if (const auto err{ApplyArgsManOptions(args, chainparams, mempool_opts)}) {
+ return InitError(*err);
+ }
mempool_opts.check_ratio = std::clamp<int>(mempool_opts.check_ratio, 0, 1'000'000);
int64_t descendant_limit_bytes = mempool_opts.limits.descendant_size_vbytes * 40;
@@ -1430,10 +1472,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
for (bool fLoaded = false; !fLoaded && !ShutdownRequested();) {
node.mempool = std::make_unique<CTxMemPool>(mempool_opts);
- const ChainstateManager::Options chainman_opts{
- .chainparams = chainparams,
- .adjusted_time_callback = GetAdjustedTime,
- };
node.chainman = std::make_unique<ChainstateManager>(chainman_opts);
ChainstateManager& chainman = *node.chainman;
@@ -1452,7 +1490,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
};
uiInterface.InitMessage(_("Loading block index…").translated);
- const int64_t load_block_index_start_time = GetTimeMillis();
+ const auto load_block_index_start_time{SteadyClock::now()};
auto catch_exceptions = [](auto&& f) {
try {
return f();
@@ -1471,7 +1509,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
std::tie(status, error) = catch_exceptions([&]{ return VerifyLoadedChainstate(chainman, options);});
if (status == node::ChainstateLoadStatus::SUCCESS) {
fLoaded = true;
- LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time);
+ LogPrintf(" block index %15dms\n", Ticks<std::chrono::milliseconds>(SteadyClock::now() - load_block_index_start_time));
}
}
@@ -1549,18 +1587,19 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// ********************************************************* Step 10: data directory maintenance
- // if pruning, unset the service bit and perform the initial blockstore prune
+ // if pruning, perform the initial blockstore prune
// after any wallet rescanning has taken place.
if (fPruneMode) {
- LogPrintf("Unsetting NODE_NETWORK on prune mode\n");
- nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK);
if (!fReindex) {
LOCK(cs_main);
- for (CChainState* chainstate : chainman.GetAll()) {
+ for (Chainstate* chainstate : chainman.GetAll()) {
uiInterface.InitMessage(_("Pruning blockstore…").translated);
chainstate->PruneAndFlush();
}
}
+ } else {
+ LogPrintf("Setting NODE_NETWORK on non-prune mode\n");
+ nLocalServices = ServiceFlags(nLocalServices | NODE_NETWORK);
}
// ********************************************************* Step 11: import blocks
@@ -1577,7 +1616,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// Either install a handler to notify us when genesis activates, or set fHaveGenesis directly.
// No locking, as this happens before any background thread is started.
boost::signals2::connection block_notify_genesis_wait_connection;
- if (chainman.ActiveChain().Tip() == nullptr) {
+ if (WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip() == nullptr)) {
block_notify_genesis_wait_connection = uiInterface.NotifyBlockTip_connect(std::bind(BlockNotifyGenesisWait, std::placeholders::_2));
} else {
fHaveGenesis = true;
@@ -1668,7 +1707,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
const auto BadPortWarning = [](const char* prefix, uint16_t port) {
return strprintf(_("%s request to listen on port %u. This port is considered \"bad\" and "
- "thus it is unlikely that any Bitcoin Core peers connect to it. See "
+ "thus it is unlikely that any peer will connect to it. See "
"doc/p2p-bad-ports.md for details and a full list."),
prefix,
port);
@@ -1767,6 +1806,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
}
SetProxy(NET_I2P, Proxy{addr});
} else {
+ if (args.IsArgSet("-onlynet") && IsReachable(NET_I2P)) {
+ return InitError(
+ _("Outbound connections restricted to i2p (-onlynet=i2p) but "
+ "-i2psam is not provided"));
+ }
SetReachable(NET_I2P, false);
}
@@ -1781,12 +1825,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// At this point, the RPC is "started", but still in warmup, which means it
// cannot yet be called. Before we make it callable, we need to make sure
// that the RPC's view of the best block is valid and consistent with
- // ChainstateManager's ActiveTip.
+ // ChainstateManager's active tip.
//
// If we do not do this, RPC's view of the best block will be height=0 and
// hash=0x0. This will lead to erroroneous responses for things like
// waitforblockheight.
- RPCNotifyBlockChange(chainman.ActiveTip());
+ RPCNotifyBlockChange(WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()));
SetRPCWarmupFinished();
uiInterface.InitMessage(_("Done loading").translated);
diff --git a/src/init/common.cpp b/src/init/common.cpp
index a0cdf44f47..f2d2c5640a 100644
--- a/src/init/common.cpp
+++ b/src/init/common.cpp
@@ -11,6 +11,7 @@
#include <logging.h>
#include <node/interface_ui.h>
#include <tinyformat.h>
+#include <util/string.h>
#include <util/system.h>
#include <util/time.h>
#include <util/translation.h>
@@ -23,11 +24,12 @@ namespace init {
void AddLoggingArgs(ArgsManager& argsman)
{
argsman.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);
- argsman.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: " + LogInstance().LogCategoriesString() + ". This option can be specified multiple times to output multiple categories.",
+ argsman.AddArg("-debug=<category>", "Output debug and trace logging (default: -nodebug, supplying <category> is optional). "
+ "If <category> is not supplied or if <category> = 1, output all debug and trace logging. <category> can be: " + LogInstance().LogCategoriesString() + ". This option can be specified multiple times to output multiple categories.",
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
- argsman.AddArg("-debugexclude=<category>", "Exclude debugging information for a category. Can be used in conjunction with -debug=1 to output debug logs for all categories except the specified category. This option can be specified multiple times to exclude multiple categories.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
+ argsman.AddArg("-debugexclude=<category>", "Exclude debug and trace logging for a category. Can be used in conjunction with -debug=1 to output debug and trace logging for all categories except the specified category. This option can be specified multiple times to exclude multiple categories.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-logips", strprintf("Include IP addresses in debug output (default: %u)", DEFAULT_LOGIPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
+ argsman.AddArg("-loglevel=<level>|<category>:<level>", strprintf("Set the global or per-category severity level for logging categories enabled with the -debug configuration option or the logging RPC: %s (default=%s); warning and error levels are always logged. If <category>:<level> is supplied, the setting will override the global one and may be specified multiple times to set multiple category-specific levels. <category> can be: %s.", LogInstance().LogLevelsString(), LogInstance().LogLevelToStr(BCLog::DEFAULT_LOG_LEVEL), LogInstance().LogCategoriesString()), ArgsManager::DISALLOW_NEGATION | ArgsManager::DISALLOW_ELISION | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-logtimestamps", strprintf("Prepend debug output with timestamp (default: %u)", DEFAULT_LOGTIMESTAMPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
#ifdef HAVE_THREAD_LOCAL
argsman.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);
@@ -55,6 +57,26 @@ void SetLoggingOptions(const ArgsManager& args)
fLogIPs = args.GetBoolArg("-logips", DEFAULT_LOGIPS);
}
+void SetLoggingLevel(const ArgsManager& args)
+{
+ if (args.IsArgSet("-loglevel")) {
+ for (const std::string& level_str : args.GetArgs("-loglevel")) {
+ if (level_str.find_first_of(':', 3) == std::string::npos) {
+ // user passed a global log level, i.e. -loglevel=<level>
+ if (!LogInstance().SetLogLevel(level_str)) {
+ InitWarning(strprintf(_("Unsupported global logging level -loglevel=%s. Valid values: %s."), level_str, LogInstance().LogLevelsString()));
+ }
+ } else {
+ // user passed a category-specific log level, i.e. -loglevel=<category>:<level>
+ const auto& toks = SplitString(level_str, ':');
+ if (!(toks.size() == 2 && LogInstance().SetCategoryLogLevel(toks[0], toks[1]))) {
+ InitWarning(strprintf(_("Unsupported category-specific logging level -loglevel=%s. Expected -loglevel=<category>:<loglevel>. Valid categories: %s. Valid loglevels: %s."), level_str, LogInstance().LogCategoriesString(), LogInstance().LogLevelsString()));
+ }
+ }
+ }
+ }
+}
+
void SetLoggingCategories(const ArgsManager& args)
{
if (args.IsArgSet("-debug")) {
@@ -99,7 +121,7 @@ bool StartLogging(const ArgsManager& args)
LogPrintf("Using data directory %s\n", fs::PathToString(gArgs.GetDataDirNet()));
// Only log conf file usage message if conf file actually exists.
- fs::path config_file_path = GetConfigFile(args.GetArg("-conf", BITCOIN_CONF_FILENAME));
+ fs::path config_file_path = GetConfigFile(args.GetPathArg("-conf", BITCOIN_CONF_FILENAME));
if (fs::exists(config_file_path)) {
LogPrintf("Config file: %s\n", fs::PathToString(config_file_path));
} else if (args.IsArgSet("-conf")) {
diff --git a/src/init/common.h b/src/init/common.h
index 2c7f485908..53c860c297 100644
--- a/src/init/common.h
+++ b/src/init/common.h
@@ -14,6 +14,7 @@ namespace init {
void AddLoggingArgs(ArgsManager& args);
void SetLoggingOptions(const ArgsManager& args);
void SetLoggingCategories(const ArgsManager& args);
+void SetLoggingLevel(const ArgsManager& args);
bool StartLogging(const ArgsManager& args);
void LogPackageVersion();
} // namespace init
diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h
index 5fc0e540a9..7a3d88b18f 100644
--- a/src/interfaces/chain.h
+++ b/src/interfaces/chain.h
@@ -5,6 +5,7 @@
#ifndef BITCOIN_INTERFACES_CHAIN_H
#define BITCOIN_INTERFACES_CHAIN_H
+#include <blockfilter.h>
#include <primitives/transaction.h> // For CTransactionRef
#include <util/settings.h> // For util::SettingsValue
@@ -143,6 +144,13 @@ public:
//! or one of its ancestors.
virtual std::optional<int> findLocatorFork(const CBlockLocator& locator) = 0;
+ //! Returns whether a block filter index is available.
+ virtual bool hasBlockFilterIndex(BlockFilterType filter_type) = 0;
+
+ //! Returns whether any of the elements match the block via a BIP 157 block filter
+ //! or std::nullopt if the block filter for this block couldn't be found.
+ virtual std::optional<bool> blockFilterMatchesAny(BlockFilterType filter_type, const uint256& block_hash, const GCSFilter::ElementSet& filter_set) = 0;
+
//! Return whether node has the block and optionally return block metadata
//! or contents.
virtual bool findBlock(const uint256& hash, const FoundBlock& block={}) = 0;
diff --git a/src/interfaces/node.h b/src/interfaces/node.h
index 2c31e12ada..dbdb21eb91 100644
--- a/src/interfaces/node.h
+++ b/src/interfaces/node.h
@@ -260,7 +260,7 @@ public:
//! Register handler for header tip messages.
using NotifyHeaderTipFn =
- std::function<void(SynchronizationState, interfaces::BlockTip tip, double verification_progress)>;
+ std::function<void(SynchronizationState, interfaces::BlockTip tip, bool presync)>;
virtual std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) = 0;
//! Get and set internal node context. Useful for testing, but not
diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h
index e29fefae56..1148dc7e4c 100644
--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -88,7 +88,7 @@ public:
virtual std::string getWalletName() = 0;
// Get a new address.
- virtual BResult<CTxDestination> getNewDestination(const OutputType type, const std::string label) = 0;
+ virtual util::Result<CTxDestination> getNewDestination(const OutputType type, const std::string& label) = 0;
//! Get public key.
virtual bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) = 0;
@@ -139,7 +139,7 @@ public:
virtual void listLockedCoins(std::vector<COutPoint>& outputs) = 0;
//! Create transaction.
- virtual BResult<CTransactionRef> createTransaction(const std::vector<wallet::CRecipient>& recipients,
+ virtual util::Result<CTransactionRef> createTransaction(const std::vector<wallet::CRecipient>& recipients,
const wallet::CCoinControl& coin_control,
bool sign,
int& change_pos,
@@ -183,7 +183,7 @@ public:
virtual WalletTx getWalletTx(const uint256& txid) = 0;
//! Get list of all wallet transactions.
- virtual std::vector<WalletTx> getWalletTxs() = 0;
+ virtual std::set<WalletTx> getWalletTxs() = 0;
//! Try to get updated status for a particular transaction, if possible without blocking.
virtual bool tryGetTxStatus(const uint256& txid,
@@ -320,31 +320,31 @@ class WalletLoader : public ChainClient
{
public:
//! Create new wallet.
- virtual std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
+ virtual util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) = 0;
- //! Load existing wallet.
- virtual std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
+ //! Load existing wallet.
+ virtual util::Result<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings) = 0;
- //! Return default wallet directory.
- virtual std::string getWalletDir() = 0;
+ //! Return default wallet directory.
+ virtual std::string getWalletDir() = 0;
- //! Restore backup wallet
- virtual BResult<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) = 0;
+ //! Restore backup wallet
+ virtual util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) = 0;
- //! Return available wallets in wallet directory.
- virtual std::vector<std::string> listWalletDir() = 0;
+ //! Return available wallets in wallet directory.
+ virtual std::vector<std::string> listWalletDir() = 0;
- //! Return interfaces for accessing wallets (if any).
- virtual std::vector<std::unique_ptr<Wallet>> getWallets() = 0;
+ //! Return interfaces for accessing wallets (if any).
+ virtual std::vector<std::unique_ptr<Wallet>> getWallets() = 0;
- //! Register handler for load wallet messages. This callback is triggered by
- //! createWallet and loadWallet above, and also triggered when wallets are
- //! loaded at startup or by RPC.
- using LoadWalletFn = std::function<void(std::unique_ptr<Wallet> wallet)>;
- virtual std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) = 0;
+ //! Register handler for load wallet messages. This callback is triggered by
+ //! createWallet and loadWallet above, and also triggered when wallets are
+ //! loaded at startup or by RPC.
+ using LoadWalletFn = std::function<void(std::unique_ptr<Wallet> wallet)>;
+ virtual std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) = 0;
- //! Return pointer to internal context, useful for testing.
- virtual wallet::WalletContext* context() { return nullptr; }
+ //! Return pointer to internal context, useful for testing.
+ virtual wallet::WalletContext* context() { return nullptr; }
};
//! Information about one wallet address.
@@ -395,6 +395,8 @@ struct WalletTx
int64_t time;
std::map<std::string, std::string> value_map;
bool is_coinbase;
+
+ bool operator<(const WalletTx& a) const { return tx->GetHash() < a.tx->GetHash(); }
};
//! Updated transaction status.
diff --git a/src/ipc/interfaces.cpp b/src/ipc/interfaces.cpp
index 580590fde9..ee0d4123ce 100644
--- a/src/ipc/interfaces.cpp
+++ b/src/ipc/interfaces.cpp
@@ -12,11 +12,11 @@
#include <tinyformat.h>
#include <util/system.h>
+#include <cstdio>
+#include <cstdlib>
#include <functional>
#include <memory>
#include <stdexcept>
-#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
#include <string>
#include <unistd.h>
diff --git a/src/ipc/process.cpp b/src/ipc/process.cpp
index 9036b80c45..9474ad2c4a 100644
--- a/src/ipc/process.cpp
+++ b/src/ipc/process.cpp
@@ -10,10 +10,10 @@
#include <util/strencodings.h>
#include <cstdint>
+#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
-#include <stdlib.h>
#include <string.h>
#include <system_error>
#include <unistd.h>
diff --git a/src/kernel/chainstatemanager_opts.h b/src/kernel/chainstatemanager_opts.h
index 510a1f9edc..226bb6031e 100644
--- a/src/kernel/chainstatemanager_opts.h
+++ b/src/kernel/chainstatemanager_opts.h
@@ -5,11 +5,19 @@
#ifndef BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H
#define BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H
+#include <arith_uint256.h>
+#include <uint256.h>
+#include <util/time.h>
+
#include <cstdint>
#include <functional>
+#include <optional>
class CChainParams;
+static constexpr bool DEFAULT_CHECKPOINTS_ENABLED{true};
+static constexpr auto DEFAULT_MAX_TIP_AGE{24h};
+
namespace kernel {
/**
@@ -19,7 +27,15 @@ namespace kernel {
*/
struct ChainstateManagerOpts {
const CChainParams& chainparams;
- const std::function<int64_t()> adjusted_time_callback{nullptr};
+ const std::function<NodeClock::time_point()> adjusted_time_callback{nullptr};
+ std::optional<bool> check_block_index{};
+ bool checkpoints_enabled{DEFAULT_CHECKPOINTS_ENABLED};
+ //! If set, it will override the minimum work we will assume exists on some valid chain.
+ std::optional<arith_uint256> minimum_chain_work{};
+ //! If set, it will override the block hash whose ancestors we will assume to have valid scripts without checking them.
+ std::optional<uint256> assumed_valid_block{};
+ //! If the tip is older than this, the node is considered to be in initial block download.
+ std::chrono::seconds max_tip_age{DEFAULT_MAX_TIP_AGE};
};
} // namespace kernel
diff --git a/src/kernel/mempool_limits.h b/src/kernel/mempool_limits.h
index e192e7e6cd..8d4495c3cb 100644
--- a/src/kernel/mempool_limits.h
+++ b/src/kernel/mempool_limits.h
@@ -24,6 +24,15 @@ struct MemPoolLimits {
int64_t descendant_count{DEFAULT_DESCENDANT_LIMIT};
//! The maximum allowed size in virtual bytes of an entry and its descendants within a package.
int64_t descendant_size_vbytes{DEFAULT_DESCENDANT_SIZE_LIMIT_KVB * 1'000};
+
+ /**
+ * @return MemPoolLimits with all the limits set to the maximum
+ */
+ static constexpr MemPoolLimits NoLimits()
+ {
+ int64_t no_limit{std::numeric_limits<int64_t>::max()};
+ return {no_limit, no_limit, no_limit, no_limit};
+ }
};
} // namespace kernel
diff --git a/src/kernel/mempool_options.h b/src/kernel/mempool_options.h
index 07953b443b..dad6f14c39 100644
--- a/src/kernel/mempool_options.h
+++ b/src/kernel/mempool_options.h
@@ -6,8 +6,13 @@
#include <kernel/mempool_limits.h>
+#include <policy/feerate.h>
+#include <policy/policy.h>
+#include <script/standard.h>
+
#include <chrono>
#include <cstdint>
+#include <optional>
class CBlockPolicyEstimator;
@@ -33,6 +38,20 @@ struct MemPoolOptions {
int check_ratio{0};
int64_t max_size_bytes{DEFAULT_MAX_MEMPOOL_SIZE_MB * 1'000'000};
std::chrono::seconds expiry{std::chrono::hours{DEFAULT_MEMPOOL_EXPIRY_HOURS}};
+ CFeeRate incremental_relay_feerate{DEFAULT_INCREMENTAL_RELAY_FEE};
+ /** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */
+ CFeeRate min_relay_feerate{DEFAULT_MIN_RELAY_TX_FEE};
+ CFeeRate dust_relay_feerate{DUST_RELAY_TX_FEE};
+ /**
+ * A data carrying output is an unspendable output containing data. The script
+ * type is designated as TxoutType::NULL_DATA.
+ *
+ * Maximum size of TxoutType::NULL_DATA scripts that this node considers standard.
+ * If nullopt, any size is nonstandard.
+ */
+ std::optional<unsigned> max_datacarrier_bytes{DEFAULT_ACCEPT_DATACARRIER ? std::optional{MAX_OP_RETURN_RELAY} : std::nullopt};
+ bool permit_bare_multisig{DEFAULT_PERMIT_BAREMULTISIG};
+ bool require_standard{true};
bool full_rbf{DEFAULT_MEMPOOL_FULL_RBF};
MemPoolLimits limits{};
};
diff --git a/src/kernel/mempool_persist.cpp b/src/kernel/mempool_persist.cpp
index 1a1cf2bbdc..a14b2e6163 100644
--- a/src/kernel/mempool_persist.cpp
+++ b/src/kernel/mempool_persist.cpp
@@ -37,7 +37,7 @@ namespace kernel {
static const uint64_t MEMPOOL_DUMP_VERSION = 1;
-bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, CChainState& active_chainstate, FopenFn mockable_fopen_function)
+bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, FopenFn mockable_fopen_function)
{
if (load_path.empty()) return false;
diff --git a/src/kernel/mempool_persist.h b/src/kernel/mempool_persist.h
index 9a15ec6dca..ca4917e38b 100644
--- a/src/kernel/mempool_persist.h
+++ b/src/kernel/mempool_persist.h
@@ -7,7 +7,7 @@
#include <fs.h>
-class CChainState;
+class Chainstate;
class CTxMemPool;
namespace kernel {
@@ -19,7 +19,7 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path,
/** Load the mempool from disk. */
bool LoadMempool(CTxMemPool& pool, const fs::path& load_path,
- CChainState& active_chainstate,
+ Chainstate& active_chainstate,
fsbridge::FopenFn mockable_fopen_function = fsbridge::fopen);
} // namespace kernel
diff --git a/src/kernel/validation_cache_sizes.h b/src/kernel/validation_cache_sizes.h
new file mode 100644
index 0000000000..72e4d1a52c
--- /dev/null
+++ b/src/kernel/validation_cache_sizes.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2022 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_KERNEL_VALIDATION_CACHE_SIZES_H
+#define BITCOIN_KERNEL_VALIDATION_CACHE_SIZES_H
+
+#include <script/sigcache.h>
+
+#include <cstddef>
+#include <limits>
+
+namespace kernel {
+struct ValidationCacheSizes {
+ size_t signature_cache_bytes{DEFAULT_MAX_SIG_CACHE_BYTES / 2};
+ size_t script_execution_cache_bytes{DEFAULT_MAX_SIG_CACHE_BYTES / 2};
+};
+}
+
+#endif // BITCOIN_KERNEL_VALIDATION_CACHE_SIZES_H
diff --git a/src/key.cpp b/src/key.cpp
index 9b0971a2dd..199808505d 100644
--- a/src/key.cpp
+++ b/src/key.cpp
@@ -333,6 +333,7 @@ bool CKey::Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const
}
bool CExtKey::Derive(CExtKey &out, unsigned int _nChild) const {
+ if (nDepth == std::numeric_limits<unsigned char>::max()) return false;
out.nDepth = nDepth + 1;
CKeyID id = key.GetPubKey().GetID();
memcpy(out.vchFingerprint, &id, 4);
diff --git a/src/key.h b/src/key.h
index 12d03778a0..e9b78ebd44 100644
--- a/src/key.h
+++ b/src/key.h
@@ -146,7 +146,7 @@ public:
bool SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root, const uint256& aux) const;
//! Derive BIP32 child key.
- bool Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
+ [[nodiscard]] bool Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
/**
* Verify thoroughly whether a private key and a public key match.
@@ -176,7 +176,7 @@ struct CExtKey {
void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
- bool Derive(CExtKey& out, unsigned int nChild) const;
+ [[nodiscard]] bool Derive(CExtKey& out, unsigned int nChild) const;
CExtPubKey Neuter() const;
void SetSeed(Span<const std::byte> seed);
};
diff --git a/src/leveldb/util/env_windows.cc b/src/leveldb/util/env_windows.cc
index 4dcba222a1..aafcdcc3be 100644
--- a/src/leveldb/util/env_windows.cc
+++ b/src/leveldb/util/env_windows.cc
@@ -194,7 +194,7 @@ class WindowsRandomAccessFile : public RandomAccessFile {
Status Read(uint64_t offset, size_t n, Slice* result,
char* scratch) const override {
DWORD bytes_read = 0;
- OVERLAPPED overlapped = {0};
+ OVERLAPPED overlapped = {};
overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32);
overlapped.Offset = static_cast<DWORD>(offset);
diff --git a/src/logging.cpp b/src/logging.cpp
index 1e2c1d5a77..ed0c2a56a5 100644
--- a/src/logging.cpp
+++ b/src/logging.cpp
@@ -5,15 +5,17 @@
#include <fs.h>
#include <logging.h>
-#include <util/threadnames.h>
#include <util/string.h>
+#include <util/threadnames.h>
#include <util/time.h>
#include <algorithm>
#include <array>
#include <mutex>
+#include <optional>
const char * const DEFAULT_DEBUGLOGFILE = "debug.log";
+constexpr auto MAX_USER_SETABLE_SEVERITY_LEVEL{BCLog::Level::Info};
BCLog::Logger& LogInstance()
{
@@ -122,6 +124,19 @@ bool BCLog::Logger::WillLogCategory(BCLog::LogFlags category) const
return (m_categories.load(std::memory_order_relaxed) & category) != 0;
}
+bool BCLog::Logger::WillLogCategoryLevel(BCLog::LogFlags category, BCLog::Level level) const
+{
+ // Log messages at Warning and Error level unconditionally, so that
+ // important troubleshooting information doesn't get lost.
+ if (level >= BCLog::Level::Warning) return true;
+
+ if (!WillLogCategory(category)) return false;
+
+ StdLockGuard scoped_lock(m_cs);
+ const auto it{m_category_log_levels.find(category)};
+ return level >= (it == m_category_log_levels.end() ? LogLevel() : it->second);
+}
+
bool BCLog::Logger::DefaultShrinkDebugFile() const
{
return m_categories == BCLog::NONE;
@@ -135,7 +150,7 @@ struct CLogCategoryDesc {
const CLogCategoryDesc LogCategories[] =
{
{BCLog::NONE, "0"},
- {BCLog::NONE, "none"},
+ {BCLog::NONE, ""},
{BCLog::NET, "net"},
{BCLog::TOR, "tor"},
{BCLog::MEMPOOL, "mempool"},
@@ -165,6 +180,8 @@ const CLogCategoryDesc LogCategories[] =
#endif
{BCLog::UTIL, "util"},
{BCLog::BLOCKSTORE, "blockstorage"},
+ {BCLog::TXRECONCILIATION, "txreconciliation"},
+ {BCLog::SCAN, "scan"},
{BCLog::ALL, "1"},
{BCLog::ALL, "all"},
};
@@ -184,11 +201,11 @@ bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str)
return false;
}
-std::string LogLevelToStr(BCLog::Level level)
+std::string BCLog::Logger::LogLevelToStr(BCLog::Level level) const
{
switch (level) {
- case BCLog::Level::None:
- return "none";
+ case BCLog::Level::Trace:
+ return "trace";
case BCLog::Level::Debug:
return "debug";
case BCLog::Level::Info:
@@ -197,6 +214,8 @@ std::string LogLevelToStr(BCLog::Level level)
return "warning";
case BCLog::Level::Error:
return "error";
+ case BCLog::Level::None:
+ return "";
}
assert(false);
}
@@ -206,7 +225,7 @@ std::string LogCategoryToStr(BCLog::LogFlags category)
// Each log category string representation should sync with LogCategories
switch (category) {
case BCLog::LogFlags::NONE:
- return "none";
+ return "";
case BCLog::LogFlags::NET:
return "net";
case BCLog::LogFlags::TOR:
@@ -263,12 +282,35 @@ std::string LogCategoryToStr(BCLog::LogFlags category)
return "util";
case BCLog::LogFlags::BLOCKSTORE:
return "blockstorage";
+ case BCLog::LogFlags::TXRECONCILIATION:
+ return "txreconciliation";
+ case BCLog::LogFlags::SCAN:
+ return "scan";
case BCLog::LogFlags::ALL:
return "all";
}
assert(false);
}
+static std::optional<BCLog::Level> GetLogLevel(const std::string& level_str)
+{
+ if (level_str == "trace") {
+ return BCLog::Level::Trace;
+ } else if (level_str == "debug") {
+ return BCLog::Level::Debug;
+ } else if (level_str == "info") {
+ return BCLog::Level::Info;
+ } else if (level_str == "warning") {
+ return BCLog::Level::Warning;
+ } else if (level_str == "error") {
+ return BCLog::Level::Error;
+ } else if (level_str == "none") {
+ return BCLog::Level::None;
+ } else {
+ return std::nullopt;
+ }
+}
+
std::vector<LogCategory> BCLog::Logger::LogCategoriesList() const
{
// Sort log categories by alphabetical order.
@@ -287,6 +329,18 @@ std::vector<LogCategory> BCLog::Logger::LogCategoriesList() const
return ret;
}
+/** Log severity levels that can be selected by the user. */
+static constexpr std::array<BCLog::Level, 3> LogLevelsList()
+{
+ return {BCLog::Level::Info, BCLog::Level::Debug, BCLog::Level::Trace};
+}
+
+std::string BCLog::Logger::LogLevelsString() const
+{
+ const auto& levels = LogLevelsList();
+ return Join(std::vector<BCLog::Level>{levels.begin(), levels.end()}, ", ", [this](BCLog::Level level) { return LogLevelToStr(level); });
+}
+
std::string BCLog::Logger::LogTimestampStr(const std::string& str)
{
std::string strStamped;
@@ -334,7 +388,7 @@ namespace BCLog {
}
} // namespace BCLog
-void BCLog::Logger::LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line, const BCLog::LogFlags category, const BCLog::Level level)
+void BCLog::Logger::LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, int source_line, BCLog::LogFlags category, BCLog::Level level)
{
StdLockGuard scoped_lock(m_cs);
std::string str_prefixed = LogEscapeMessage(str);
@@ -364,7 +418,7 @@ void BCLog::Logger::LogPrintStr(const std::string& str, const std::string& loggi
}
if (m_log_threadnames && m_started_new_line) {
- const auto threadname = util::ThreadGetInternalName();
+ const auto& threadname = util::ThreadGetInternalName();
str_prefixed.insert(0, "[" + (threadname.empty() ? "unknown" : threadname) + "] ");
}
@@ -443,3 +497,24 @@ void BCLog::Logger::ShrinkDebugFile()
else if (file != nullptr)
fclose(file);
}
+
+bool BCLog::Logger::SetLogLevel(const std::string& level_str)
+{
+ const auto level = GetLogLevel(level_str);
+ if (!level.has_value() || level.value() > MAX_USER_SETABLE_SEVERITY_LEVEL) return false;
+ m_log_level = level.value();
+ return true;
+}
+
+bool BCLog::Logger::SetCategoryLogLevel(const std::string& category_str, const std::string& level_str)
+{
+ BCLog::LogFlags flag;
+ if (!GetLogCategory(flag, category_str)) return false;
+
+ const auto level = GetLogLevel(level_str);
+ if (!level.has_value() || level.value() > MAX_USER_SETABLE_SEVERITY_LEVEL) return false;
+
+ StdLockGuard scoped_lock(m_cs);
+ m_category_log_levels[flag] = level.value();
+ return true;
+}
diff --git a/src/logging.h b/src/logging.h
index 50869ad89a..14a0f08f8d 100644
--- a/src/logging.h
+++ b/src/logging.h
@@ -7,8 +7,8 @@
#define BITCOIN_LOGGING_H
#include <fs.h>
-#include <tinyformat.h>
#include <threadsafety.h>
+#include <tinyformat.h>
#include <util/string.h>
#include <atomic>
@@ -17,6 +17,7 @@
#include <list>
#include <mutex>
#include <string>
+#include <unordered_map>
#include <vector>
static const bool DEFAULT_LOGTIMEMICROS = false;
@@ -65,15 +66,19 @@ namespace BCLog {
#endif
UTIL = (1 << 25),
BLOCKSTORE = (1 << 26),
+ TXRECONCILIATION = (1 << 27),
+ SCAN = (1 << 28),
ALL = ~(uint32_t)0,
};
enum class Level {
- Debug = 0,
- None = 1,
- Info = 2,
- Warning = 3,
- Error = 4,
+ Trace = 0, // High-volume or detailed logging for development/debugging
+ Debug, // Reasonably noisy logging, but still usable in production
+ Info, // Default
+ Warning,
+ Error,
+ None, // Internal use only
};
+ constexpr auto DEFAULT_LOG_LEVEL{Level::Debug};
class Logger
{
@@ -91,6 +96,13 @@ namespace BCLog {
*/
std::atomic_bool m_started_new_line{true};
+ //! Category-specific log level. Overrides `m_log_level`.
+ std::unordered_map<LogFlags, Level> m_category_log_levels GUARDED_BY(m_cs);
+
+ //! If there is no category-specific log level, all logs with a severity
+ //! level lower than `m_log_level` will be ignored.
+ std::atomic<Level> m_log_level{DEFAULT_LOG_LEVEL};
+
/** Log categories bitfield. */
std::atomic<uint32_t> m_categories{0};
@@ -112,7 +124,7 @@ namespace BCLog {
std::atomic<bool> m_reopen_file{false};
/** Send a string to the log output */
- void LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line, const BCLog::LogFlags category, const BCLog::Level level);
+ void LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, int source_line, BCLog::LogFlags category, BCLog::Level level);
/** Returns whether logs will be written to any output */
bool Enabled() const
@@ -143,6 +155,22 @@ namespace BCLog {
void ShrinkDebugFile();
+ std::unordered_map<LogFlags, Level> CategoryLevels() const
+ {
+ StdLockGuard scoped_lock(m_cs);
+ return m_category_log_levels;
+ }
+ void SetCategoryLogLevel(const std::unordered_map<LogFlags, Level>& levels)
+ {
+ StdLockGuard scoped_lock(m_cs);
+ m_category_log_levels = levels;
+ }
+ bool SetCategoryLogLevel(const std::string& category_str, const std::string& level_str);
+
+ Level LogLevel() const { return m_log_level.load(); }
+ void SetLogLevel(Level level) { m_log_level = level; }
+ bool SetLogLevel(const std::string& level);
+
uint32_t GetCategoryMask() const { return m_categories.load(); }
void EnableCategory(LogFlags flag);
@@ -151,6 +179,8 @@ namespace BCLog {
bool DisableCategory(const std::string& str);
bool WillLogCategory(LogFlags category) const;
+ bool WillLogCategoryLevel(LogFlags category, Level level) const;
+
/** Returns a vector of the log categories in alphabetical order. */
std::vector<LogCategory> LogCategoriesList() const;
/** Returns a string with the log categories in alphabetical order. */
@@ -159,6 +189,12 @@ namespace BCLog {
return Join(LogCategoriesList(), ", ", [&](const LogCategory& i) { return i.category; });
};
+ //! Returns a string with all user-selectable log levels.
+ std::string LogLevelsString() const;
+
+ //! Returns the string representation of a log level.
+ std::string LogLevelToStr(BCLog::Level level) const;
+
bool DefaultShrinkDebugFile() const;
};
@@ -169,12 +205,7 @@ BCLog::Logger& LogInstance();
/** Return true if log accepts specified category, at the specified level. */
static inline bool LogAcceptCategory(BCLog::LogFlags category, BCLog::Level level)
{
- // Log messages at Warning and Error level unconditionally, so that
- // important troubleshooting information doesn't get lost.
- if (level >= BCLog::Level::Warning) {
- return true;
- }
- return LogInstance().WillLogCategory(category);
+ return LogInstance().WillLogCategoryLevel(category, level);
}
/** Return true if str parses as a log category and set the flag */
diff --git a/src/mempool_args.cpp b/src/mempool_args.cpp
deleted file mode 100644
index 77caa127e9..0000000000
--- a/src/mempool_args.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) 2022 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 <mempool_args.h>
-
-#include <kernel/mempool_limits.h>
-#include <kernel/mempool_options.h>
-
-#include <util/system.h>
-
-using kernel::MemPoolLimits;
-using kernel::MemPoolOptions;
-
-namespace {
-void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolLimits& mempool_limits)
-{
- mempool_limits.ancestor_count = argsman.GetIntArg("-limitancestorcount", mempool_limits.ancestor_count);
-
- if (auto vkb = argsman.GetIntArg("-limitancestorsize")) mempool_limits.ancestor_size_vbytes = *vkb * 1'000;
-
- mempool_limits.descendant_count = argsman.GetIntArg("-limitdescendantcount", mempool_limits.descendant_count);
-
- if (auto vkb = argsman.GetIntArg("-limitdescendantsize")) mempool_limits.descendant_size_vbytes = *vkb * 1'000;
-}
-}
-
-void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolOptions& mempool_opts)
-{
- mempool_opts.check_ratio = argsman.GetIntArg("-checkmempool", mempool_opts.check_ratio);
-
- if (auto mb = argsman.GetIntArg("-maxmempool")) mempool_opts.max_size_bytes = *mb * 1'000'000;
-
- if (auto hours = argsman.GetIntArg("-mempoolexpiry")) mempool_opts.expiry = std::chrono::hours{*hours};
-
- mempool_opts.full_rbf = argsman.GetBoolArg("-mempoolfullrbf", mempool_opts.full_rbf);
-
- ApplyArgsManOptions(argsman, mempool_opts.limits);
-}
diff --git a/src/memusage.h b/src/memusage.h
index a6e894129a..fd85a7956c 100644
--- a/src/memusage.h
+++ b/src/memusage.h
@@ -8,9 +8,8 @@
#include <indirectmap.h>
#include <prevector.h>
-#include <stdlib.h>
-
#include <cassert>
+#include <cstdlib>
#include <map>
#include <memory>
#include <set>
diff --git a/src/minisketch/configure.ac b/src/minisketch/configure.ac
index 9dc66e7fd2..83910448a2 100644
--- a/src/minisketch/configure.ac
+++ b/src/minisketch/configure.ac
@@ -124,9 +124,6 @@ if test "x$use_ccache" != "xno"; then
fi
AC_MSG_RESULT($use_ccache)
fi
-if test "x$use_ccache" = "xyes"; then
- AX_CHECK_COMPILE_FLAG([-Qunused-arguments],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Qunused-arguments"],,[[$CXXFLAG_WERROR]])
-fi
VERIFY_DEFINES=-DMINISKETCH_VERIFY
RELEASE_DEFINES=
diff --git a/src/minisketch/src/bench.cpp b/src/minisketch/src/bench.cpp
index f55944a448..dc44379fdb 100644
--- a/src/minisketch/src/bench.cpp
+++ b/src/minisketch/src/bench.cpp
@@ -62,13 +62,11 @@ int main(int argc, char** argv) {
if (!states[0]) {
printf(" -\t");
} else {
- double total = 0.0;
for (auto& state : states) {
auto start = std::chrono::steady_clock::now();
minisketch_decode(state, 2 * syndromes, roots.data());
auto stop = std::chrono::steady_clock::now();
std::chrono::duration<double> dur(stop - start);
- total += dur.count();
benches.push_back(dur.count());
}
std::sort(benches.begin(), benches.end());
@@ -98,7 +96,6 @@ int main(int argc, char** argv) {
if (!states[0]) {
printf(" -\t");
} else {
- double total = 0.0;
for (auto& state : states) {
auto start = std::chrono::steady_clock::now();
for (auto val : data) {
@@ -106,7 +103,6 @@ int main(int argc, char** argv) {
}
auto stop = std::chrono::steady_clock::now();
std::chrono::duration<double> dur(stop - start);
- total += dur.count();
benches.push_back(dur.count());
}
std::sort(benches.begin(), benches.end());
diff --git a/src/minisketch/src/int_utils.h b/src/minisketch/src/int_utils.h
index 62b2c38a29..d21ba56f33 100644
--- a/src/minisketch/src/int_utils.h
+++ b/src/minisketch/src/int_utils.h
@@ -129,17 +129,7 @@ constexpr inline I Mask() { return ((I((I(-1)) << (std::numeric_limits<I>::digit
/** Compute the smallest power of two that is larger than val. */
template<typename I>
static inline int CountBits(I val, int max) {
-#ifdef HAVE_CLZ
- (void)max;
- if (val == 0) return 0;
- if (std::numeric_limits<unsigned>::digits >= std::numeric_limits<I>::digits) {
- return std::numeric_limits<unsigned>::digits - __builtin_clz(val);
- } else if (std::numeric_limits<unsigned long>::digits >= std::numeric_limits<I>::digits) {
- return std::numeric_limits<unsigned long>::digits - __builtin_clzl(val);
- } else {
- return std::numeric_limits<unsigned long long>::digits - __builtin_clzll(val);
- }
-#elif _MSC_VER
+#ifdef _MSC_VER
(void)max;
unsigned long index;
unsigned char ret;
@@ -149,7 +139,17 @@ static inline int CountBits(I val, int max) {
ret = _BitScanReverse64(&index, val);
}
if (!ret) return 0;
- return index;
+ return index + 1;
+#elif HAVE_CLZ
+ (void)max;
+ if (val == 0) return 0;
+ if (std::numeric_limits<unsigned>::digits >= std::numeric_limits<I>::digits) {
+ return std::numeric_limits<unsigned>::digits - __builtin_clz(val);
+ } else if (std::numeric_limits<unsigned long>::digits >= std::numeric_limits<I>::digits) {
+ return std::numeric_limits<unsigned long>::digits - __builtin_clzl(val);
+ } else {
+ return std::numeric_limits<unsigned long long>::digits - __builtin_clzll(val);
+ }
#else
while (max && (val >> (max - 1) == 0)) --max;
return max;
diff --git a/src/minisketch/src/test.cpp b/src/minisketch/src/test.cpp
index 417937ea5f..85b9e9e396 100644
--- a/src/minisketch/src/test.cpp
+++ b/src/minisketch/src/test.cpp
@@ -9,6 +9,7 @@
#include <limits>
#include <random>
#include <stdexcept>
+#include <string>
#include <vector>
#include "../include/minisketch.h"
diff --git a/src/net.cpp b/src/net.cpp
index c4aaac4986..3c28b9eddf 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -91,13 +91,12 @@ static constexpr auto FEELER_SLEEP_WINDOW{1s};
/** Used to pass flags to the Bind() function */
enum BindFlags {
BF_NONE = 0,
- BF_EXPLICIT = (1U << 0),
- BF_REPORT_ERROR = (1U << 1),
+ BF_REPORT_ERROR = (1U << 0),
/**
* Do not call AddLocal() for our special addresses, e.g., for incoming
* Tor connections, to prevent gossiping them over the network.
*/
- BF_DONT_ADVERTISE = (1U << 2),
+ BF_DONT_ADVERTISE = (1U << 1),
};
// The set of sockets cannot be modified while waiting
@@ -208,12 +207,11 @@ static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t> &vSeedsIn)
// one by discovery.
CService GetLocalAddress(const CNetAddr& addrPeer)
{
- CService ret{CNetAddr(), GetListenPort()};
CService addr;
if (GetLocal(addr, &addrPeer)) {
- ret = CService{addr};
+ return addr;
}
- return ret;
+ return CService{CNetAddr(), GetListenPort()};
}
static int GetnScore(const CService& addr)
@@ -454,7 +452,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "trying connection %s lastseen=%.1fhrs\n",
pszDest ? pszDest : addrConnect.ToString(),
- Ticks<HoursDouble>(pszDest ? 0h : AdjustedTime() - addrConnect.nTime));
+ Ticks<HoursDouble>(pszDest ? 0h : Now<NodeSeconds>() - addrConnect.nTime));
// Resolve
const uint16_t default_port{pszDest != nullptr ? Params().GetDefaultPort(pszDest) :
@@ -485,18 +483,27 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
Proxy proxy;
CAddress addr_bind;
assert(!addr_bind.IsValid());
+ std::unique_ptr<i2p::sam::Session> i2p_transient_session;
if (addrConnect.IsValid()) {
+ const bool use_proxy{GetProxy(addrConnect.GetNetwork(), proxy)};
bool proxyConnectionFailed = false;
- if (addrConnect.GetNetwork() == NET_I2P && m_i2p_sam_session.get() != nullptr) {
+ if (addrConnect.GetNetwork() == NET_I2P && use_proxy) {
i2p::Connection conn;
- if (m_i2p_sam_session->Connect(addrConnect, conn, proxyConnectionFailed)) {
- connected = true;
+
+ if (m_i2p_sam_session) {
+ connected = m_i2p_sam_session->Connect(addrConnect, conn, proxyConnectionFailed);
+ } else {
+ i2p_transient_session = std::make_unique<i2p::sam::Session>(proxy.proxy, &interruptNet);
+ connected = i2p_transient_session->Connect(addrConnect, conn, proxyConnectionFailed);
+ }
+
+ if (connected) {
sock = std::move(conn.sock);
addr_bind = CAddress{conn.me, NODE_NONE};
}
- } else if (GetProxy(addrConnect.GetNetwork(), proxy)) {
+ } else if (use_proxy) {
sock = CreateSock(proxy.proxy);
if (!sock) {
return nullptr;
@@ -547,7 +554,8 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
addr_bind,
pszDest ? pszDest : "",
conn_type,
- /*inbound_onion=*/false);
+ /*inbound_onion=*/false,
+ CNodeOptions{ .i2p_sam_session = std::move(i2p_transient_session) });
pnode->AddRef();
// We're making a new connection, harvest entropy from the time (and our peer count)
@@ -564,6 +572,7 @@ void CNode::CloseSocketDisconnect()
LogPrint(BCLog::NET, "disconnecting peer=%d\n", id);
m_sock.reset();
}
+ m_i2p_sam_session.reset();
}
void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const {
@@ -627,7 +636,7 @@ void CNode::CopyStats(CNodeStats& stats)
X(mapRecvBytesPerMsgType);
X(nRecvBytes);
}
- X(m_permissionFlags);
+ X(m_permission_flags);
X(m_last_ping_time);
X(m_min_ping_time);
@@ -787,7 +796,8 @@ CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds
return msg;
}
-void V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) {
+void V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) const
+{
// create dbl-sha256 checksum
uint256 hash = Hash(msg.data);
@@ -925,27 +935,27 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
const CAddress addr_bind{MaybeFlipIPv6toCJDNS(GetBindAddress(*sock)), NODE_NONE};
- NetPermissionFlags permissionFlags = NetPermissionFlags::None;
- hListenSocket.AddSocketPermissionFlags(permissionFlags);
+ NetPermissionFlags permission_flags = NetPermissionFlags::None;
+ hListenSocket.AddSocketPermissionFlags(permission_flags);
- CreateNodeFromAcceptedSocket(std::move(sock), permissionFlags, addr_bind, addr);
+ CreateNodeFromAcceptedSocket(std::move(sock), permission_flags, addr_bind, addr);
}
void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
- NetPermissionFlags permissionFlags,
+ NetPermissionFlags permission_flags,
const CAddress& addr_bind,
const CAddress& addr)
{
int nInbound = 0;
int nMaxInbound = nMaxConnections - m_max_outbound;
- AddWhitelistPermissionFlags(permissionFlags, addr);
- if (NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::Implicit)) {
- NetPermissions::ClearFlag(permissionFlags, NetPermissionFlags::Implicit);
- if (gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) NetPermissions::AddFlag(permissionFlags, NetPermissionFlags::ForceRelay);
- if (gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) NetPermissions::AddFlag(permissionFlags, NetPermissionFlags::Relay);
- NetPermissions::AddFlag(permissionFlags, NetPermissionFlags::Mempool);
- NetPermissions::AddFlag(permissionFlags, NetPermissionFlags::NoBan);
+ AddWhitelistPermissionFlags(permission_flags, addr);
+ if (NetPermissions::HasFlag(permission_flags, NetPermissionFlags::Implicit)) {
+ NetPermissions::ClearFlag(permission_flags, NetPermissionFlags::Implicit);
+ if (gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) NetPermissions::AddFlag(permission_flags, NetPermissionFlags::ForceRelay);
+ if (gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) NetPermissions::AddFlag(permission_flags, NetPermissionFlags::Relay);
+ NetPermissions::AddFlag(permission_flags, NetPermissionFlags::Mempool);
+ NetPermissions::AddFlag(permission_flags, NetPermissionFlags::NoBan);
}
{
@@ -960,8 +970,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
return;
}
- if (!IsSelectableSocket(sock->Get()))
- {
+ if (!sock->IsSelectable()) {
LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToString());
return;
}
@@ -976,7 +985,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
// Don't accept connections from banned peers.
bool banned = m_banman && m_banman->IsBanned(addr);
- if (!NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::NoBan) && banned)
+ if (!NetPermissions::HasFlag(permission_flags, NetPermissionFlags::NoBan) && banned)
{
LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToString());
return;
@@ -984,7 +993,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
// Only accept connections from discouraged peers if our inbound slots aren't (almost) full.
bool discouraged = m_banman && m_banman->IsDiscouraged(addr);
- if (!NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::NoBan) && nInbound + 1 >= nMaxInbound && discouraged)
+ if (!NetPermissions::HasFlag(permission_flags, NetPermissionFlags::NoBan) && nInbound + 1 >= nMaxInbound && discouraged)
{
LogPrint(BCLog::NET, "connection from %s dropped (discouraged)\n", addr.ToString());
return;
@@ -1003,7 +1012,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize();
ServiceFlags nodeServices = nLocalServices;
- if (NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::BloomFilter)) {
+ if (NetPermissions::HasFlag(permission_flags, NetPermissionFlags::BloomFilter)) {
nodeServices = static_cast<ServiceFlags>(nodeServices | NODE_BLOOM);
}
@@ -1016,10 +1025,12 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
addr_bind,
/*addrNameIn=*/"",
ConnectionType::INBOUND,
- inbound_onion);
+ inbound_onion,
+ CNodeOptions{
+ .permission_flags = permission_flags,
+ .prefer_evict = discouraged,
+ });
pnode->AddRef();
- pnode->m_permissionFlags = permissionFlags;
- pnode->m_prefer_evict = discouraged;
m_msgproc->InitializeNode(*pnode, nodeServices);
LogPrint(BCLog::NET, "connection from %s accepted\n", addr.ToString());
@@ -1487,12 +1498,12 @@ void CConnman::ThreadDNSAddressSeed()
void CConnman::DumpAddresses()
{
- int64_t nStart = GetTimeMillis();
+ const auto start{SteadyClock::now()};
DumpPeerAddresses(::gArgs, addrman);
LogPrint(BCLog::NET, "Flushed %d addresses to peers.dat %dms\n",
- addrman.size(), GetTimeMillis() - nStart);
+ addrman.size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
}
void CConnman::ProcessAddrFetch()
@@ -1629,15 +1640,28 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
LOCK2(m_addr_fetches_mutex, m_added_nodes_mutex);
if (m_addr_fetches.empty() && m_added_nodes.empty()) {
add_fixed_seeds_now = true;
- LogPrintf("Adding fixed seeds as -dnsseed=0, -addnode is not provided and all -seednode(s) attempted\n");
+ LogPrintf("Adding fixed seeds as -dnsseed=0 (or IPv4/IPv6 connections are disabled via -onlynet), -addnode is not provided and all -seednode(s) attempted\n");
}
}
if (add_fixed_seeds_now) {
+ std::vector<CAddress> seed_addrs{ConvertSeeds(Params().FixedSeeds())};
+ // We will not make outgoing connections to peers that are unreachable
+ // (e.g. because of -onlynet configuration).
+ // Therefore, we do not add them to addrman in the first place.
+ // Note that if you change -onlynet setting from one network to another,
+ // peers.dat will contain only peers of unreachable networks and
+ // manual intervention will be needed (either delete peers.dat after
+ // configuration change or manually add some reachable peer using addnode),
+ // see <https://github.com/bitcoin/bitcoin/issues/26035> for details.
+ seed_addrs.erase(std::remove_if(seed_addrs.begin(), seed_addrs.end(),
+ [](const CAddress& addr) { return !IsReachable(addr); }),
+ seed_addrs.end());
CNetAddr local;
local.SetInternal("fixedseeds");
- addrman.Add(ConvertSeeds(Params().FixedSeeds()), local);
+ addrman.Add(seed_addrs, local);
add_fixed_seeds = false;
+ LogPrintf("Added %d fixed seeds from reachable networks.\n", seed_addrs.size());
}
}
@@ -1735,7 +1759,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
addrman.ResolveCollisions();
- const auto nANow{AdjustedTime()};
+ const auto current_time{NodeClock::now()};
int nTries = 0;
while (!interruptNet)
{
@@ -1798,7 +1822,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
continue;
// only consider very recently tried nodes after 30 failed attempts
- if (nANow - addr_last_try < 10min && nTries < 30) {
+ if (current_time - addr_last_try < 10min && nTries < 30) {
continue;
}
@@ -1965,8 +1989,12 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai
}
}
+Mutex NetEventsInterface::g_msgproc_mutex;
+
void CConnman::ThreadMessageHandler()
{
+ LOCK(NetEventsInterface::g_msgproc_mutex);
+
SetSyscallSandboxPolicy(SyscallSandboxPolicy::MESSAGE_HANDLER);
while (!flagInterruptMsgProc)
{
@@ -1988,10 +2016,7 @@ void CConnman::ThreadMessageHandler()
if (flagInterruptMsgProc)
return;
// Send messages
- {
- LOCK(pnode->cs_sendProcessing);
- m_msgproc->SendMessages(pnode);
- }
+ m_msgproc->SendMessages(pnode);
if (flagInterruptMsgProc)
return;
@@ -2204,9 +2229,6 @@ bool CConnman::Bind(const CService& addr_, unsigned int flags, NetPermissionFlag
{
const CService addr{MaybeFlipIPv6toCJDNS(addr_)};
- if (!(flags & BF_EXPLICIT) && !IsReachable(addr)) {
- return false;
- }
bilingual_str strError;
if (!BindListenPort(addr, strError, permissions)) {
if ((flags & BF_REPORT_ERROR) && m_client_interface) {
@@ -2226,13 +2248,13 @@ bool CConnman::InitBinds(const Options& options)
{
bool fBound = false;
for (const auto& addrBind : options.vBinds) {
- fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR), NetPermissionFlags::None);
+ fBound |= Bind(addrBind, BF_REPORT_ERROR, NetPermissionFlags::None);
}
for (const auto& addrBind : options.vWhiteBinds) {
- fBound |= Bind(addrBind.m_service, (BF_EXPLICIT | BF_REPORT_ERROR), addrBind.m_flags);
+ fBound |= Bind(addrBind.m_service, BF_REPORT_ERROR, addrBind.m_flags);
}
for (const auto& addr_bind : options.onion_binds) {
- fBound |= Bind(addr_bind, BF_EXPLICIT | BF_DONT_ADVERTISE, NetPermissionFlags::None);
+ fBound |= Bind(addr_bind, BF_DONT_ADVERTISE, NetPermissionFlags::None);
}
if (options.bind_on_any) {
struct in_addr inaddr_any;
@@ -2259,7 +2281,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
}
Proxy i2p_sam;
- if (GetProxy(NET_I2P, i2p_sam)) {
+ if (GetProxy(NET_I2P, i2p_sam) && connOptions.m_i2p_accept_incoming) {
m_i2p_sam_session = std::make_unique<i2p::sam::Session>(gArgs.GetDataDirNet() / "i2p_private_key",
i2p_sam.proxy, &interruptNet);
}
@@ -2333,7 +2355,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
// Process messages
threadMessageHandler = std::thread(&util::TraceThread, "msghand", [this] { ThreadMessageHandler(); });
- if (connOptions.m_i2p_accept_incoming && m_i2p_sam_session.get() != nullptr) {
+ if (m_i2p_sam_session) {
threadI2PAcceptIncoming =
std::thread(&util::TraceThread, "i2paccept", [this] { ThreadI2PAcceptIncoming(); });
}
@@ -2702,20 +2724,31 @@ ServiceFlags CConnman::GetLocalServices() const
unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; }
-CNode::CNode(NodeId idIn, std::shared_ptr<Sock> sock, const CAddress& addrIn,
- uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn,
- const CAddress& addrBindIn, const std::string& addrNameIn,
- ConnectionType conn_type_in, bool inbound_onion)
- : m_sock{sock},
+CNode::CNode(NodeId idIn,
+ std::shared_ptr<Sock> sock,
+ const CAddress& addrIn,
+ uint64_t nKeyedNetGroupIn,
+ uint64_t nLocalHostNonceIn,
+ const CAddress& addrBindIn,
+ const std::string& addrNameIn,
+ ConnectionType conn_type_in,
+ bool inbound_onion,
+ CNodeOptions&& node_opts)
+ : m_deserializer{std::make_unique<V1TransportDeserializer>(V1TransportDeserializer(Params(), idIn, SER_NETWORK, INIT_PROTO_VERSION))},
+ m_serializer{std::make_unique<V1TransportSerializer>(V1TransportSerializer())},
+ m_permission_flags{node_opts.permission_flags},
+ m_sock{sock},
m_connected{GetTime<std::chrono::seconds>()},
- addr(addrIn),
- addrBind(addrBindIn),
+ addr{addrIn},
+ addrBind{addrBindIn},
m_addr_name{addrNameIn.empty() ? addr.ToStringIPPort() : addrNameIn},
- m_inbound_onion(inbound_onion),
- nKeyedNetGroup(nKeyedNetGroupIn),
- id(idIn),
- nLocalHostNonce(nLocalHostNonceIn),
- m_conn_type(conn_type_in)
+ m_inbound_onion{inbound_onion},
+ m_prefer_evict{node_opts.prefer_evict},
+ nKeyedNetGroup{nKeyedNetGroupIn},
+ id{idIn},
+ nLocalHostNonce{nLocalHostNonceIn},
+ m_conn_type{conn_type_in},
+ m_i2p_sam_session{std::move(node_opts.i2p_sam_session)}
{
if (inbound_onion) assert(conn_type_in == ConnectionType::INBOUND);
@@ -2728,9 +2761,6 @@ CNode::CNode(NodeId idIn, std::shared_ptr<Sock> sock, const CAddress& addrIn,
} else {
LogPrint(BCLog::NET, "Added connection peer=%d\n", id);
}
-
- m_deserializer = std::make_unique<V1TransportDeserializer>(V1TransportDeserializer(Params(), id, SER_NETWORK, INIT_PROTO_VERSION));
- m_serializer = std::make_unique<V1TransportSerializer>(V1TransportSerializer());
}
bool CConnman::NodeFullyConnected(const CNode* pnode)
diff --git a/src/net.h b/src/net.h
index 2036e9078c..245f14731b 100644
--- a/src/net.h
+++ b/src/net.h
@@ -164,6 +164,7 @@ bool SeenLocal(const CService& addr);
bool IsLocal(const CService& addr);
bool GetLocal(CService &addr, const CNetAddr *paddrPeer = nullptr);
CService GetLocalAddress(const CNetAddr& addrPeer);
+CService MaybeFlipIPv6toCJDNS(const CService& service);
extern bool fDiscover;
@@ -204,7 +205,7 @@ public:
mapMsgTypeSize mapSendBytesPerMsgType;
uint64_t nRecvBytes;
mapMsgTypeSize mapRecvBytesPerMsgType;
- NetPermissionFlags m_permissionFlags;
+ NetPermissionFlags m_permission_flags;
std::chrono::microseconds m_last_ping_time;
std::chrono::microseconds m_min_ping_time;
// Our address, as reported by the peer
@@ -325,13 +326,20 @@ public:
class TransportSerializer {
public:
// prepare message for transport (header construction, error-correction computation, payload encryption, etc.)
- virtual void prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) = 0;
+ virtual void prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) const = 0;
virtual ~TransportSerializer() {}
};
-class V1TransportSerializer : public TransportSerializer {
+class V1TransportSerializer : public TransportSerializer {
public:
- void prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) override;
+ void prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) const override;
+};
+
+struct CNodeOptions
+{
+ NetPermissionFlags permission_flags = NetPermissionFlags::None;
+ std::unique_ptr<i2p::sam::Session> i2p_sam_session = nullptr;
+ bool prefer_evict = false;
};
/** Information about a peer */
@@ -341,10 +349,10 @@ class CNode
friend struct ConnmanTestMsg;
public:
- std::unique_ptr<TransportDeserializer> m_deserializer;
- std::unique_ptr<TransportSerializer> m_serializer;
+ const std::unique_ptr<TransportDeserializer> m_deserializer; // Used only by SocketHandler thread
+ const std::unique_ptr<const TransportSerializer> m_serializer;
- NetPermissionFlags m_permissionFlags{NetPermissionFlags::None};
+ const NetPermissionFlags m_permission_flags;
/**
* Socket used for communication with the node.
@@ -368,9 +376,7 @@ public:
RecursiveMutex cs_vProcessMsg;
std::list<CNetMessage> vProcessMsg GUARDED_BY(cs_vProcessMsg);
- size_t nProcessQueueSize{0};
-
- RecursiveMutex cs_sendProcessing;
+ size_t nProcessQueueSize GUARDED_BY(cs_vProcessMsg){0};
uint64_t nRecvBytes GUARDED_BY(cs_vRecv){0};
@@ -393,9 +399,9 @@ public:
* from the wire. This cleaned string can safely be logged or displayed.
*/
std::string cleanSubVer GUARDED_BY(m_subver_mutex){};
- bool m_prefer_evict{false}; // This peer is preferred for eviction.
+ const bool m_prefer_evict{false}; // This peer is preferred for eviction.
bool HasPermission(NetPermissionFlags permission) const {
- return NetPermissions::HasFlag(m_permissionFlags, permission);
+ return NetPermissions::HasFlag(m_permission_flags, permission);
}
/** fSuccessfullyConnected is set to true on receiving VERACK from the peer. */
std::atomic_bool fSuccessfullyConnected{false};
@@ -483,10 +489,8 @@ public:
/** Whether this peer provides all services that we want. Used for eviction decisions */
std::atomic_bool m_has_all_wanted_services{false};
- /** Whether we should relay transactions to this peer (their version
- * message did not include fRelay=false and this is not a block-relay-only
- * connection). This only changes from false to true. It will never change
- * back to false. Used only in inbound eviction logic. */
+ /** Whether we should relay transactions to this peer. This only changes
+ * from false to true. It will never change back to false. */
std::atomic_bool m_relays_txs{false};
/** Whether this peer has loaded a bloom filter. Used only in inbound
@@ -513,10 +517,16 @@ public:
* criterium in CConnman::AttemptToEvictConnection. */
std::atomic<std::chrono::microseconds> m_min_ping_time{std::chrono::microseconds::max()};
- CNode(NodeId id, std::shared_ptr<Sock> sock, const CAddress& addrIn,
- uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn,
- const CAddress& addrBindIn, const std::string& addrNameIn,
- ConnectionType conn_type_in, bool inbound_onion);
+ CNode(NodeId id,
+ std::shared_ptr<Sock> sock,
+ const CAddress& addrIn,
+ uint64_t nKeyedNetGroupIn,
+ uint64_t nLocalHostNonceIn,
+ const CAddress& addrBindIn,
+ const std::string& addrNameIn,
+ ConnectionType conn_type_in,
+ bool inbound_onion,
+ CNodeOptions&& node_opts = {});
CNode(const CNode&) = delete;
CNode& operator=(const CNode&) = delete;
@@ -596,6 +606,18 @@ private:
mapMsgTypeSize mapSendBytesPerMsgType GUARDED_BY(cs_vSend);
mapMsgTypeSize mapRecvBytesPerMsgType GUARDED_BY(cs_vRecv);
+
+ /**
+ * If an I2P session is created per connection (for outbound transient I2P
+ * connections) then it is stored here so that it can be destroyed when the
+ * socket is closed. I2P sessions involve a data/transport socket (in `m_sock`)
+ * and a control socket (in `m_i2p_sam_session`). For transient sessions, once
+ * the data socket is closed, the control socket is not going to be used anymore
+ * and is just taking up resources. So better close it as soon as `m_sock` is
+ * closed.
+ * Otherwise this unique_ptr is empty.
+ */
+ std::unique_ptr<i2p::sam::Session> m_i2p_sam_session GUARDED_BY(m_sock_mutex);
};
/**
@@ -604,6 +626,9 @@ private:
class NetEventsInterface
{
public:
+ /** Mutex for anything that is only accessed via the msg processing thread */
+ static Mutex g_msgproc_mutex;
+
/** Initialize a peer (setup state, queue any initial messages) */
virtual void InitializeNode(CNode& node, ServiceFlags our_services) = 0;
@@ -617,7 +642,7 @@ public:
* @param[in] interrupt Interrupt condition for processing threads
* @return True if there is more work to be done
*/
- virtual bool ProcessMessages(CNode* pnode, std::atomic<bool>& interrupt) = 0;
+ virtual bool ProcessMessages(CNode* pnode, std::atomic<bool>& interrupt) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex) = 0;
/**
* Send queued protocol messages to a given node.
@@ -625,7 +650,7 @@ public:
* @param[in] pnode The node which we are sending messages to.
* @return True if there is more work to be done
*/
- virtual bool SendMessages(CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(pnode->cs_sendProcessing) = 0;
+ virtual bool SendMessages(CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex) = 0;
protected:
@@ -872,12 +897,12 @@ private:
* Create a `CNode` object from a socket that has just been accepted and add the node to
* the `m_nodes` member.
* @param[in] sock Connected socket to communicate with the peer.
- * @param[in] permissionFlags The peer's permissions.
+ * @param[in] permission_flags The peer's permissions.
* @param[in] addr_bind The address and port at our side of the connection.
* @param[in] addr The address and port at the peer's side of the connection.
*/
void CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
- NetPermissionFlags permissionFlags,
+ NetPermissionFlags permission_flags,
const CAddress& addr_bind,
const CAddress& addr);
@@ -1072,7 +1097,8 @@ private:
/**
* I2P SAM session.
- * Used to accept incoming and make outgoing I2P connections.
+ * Used to accept incoming and make outgoing I2P connections from a persistent
+ * address.
*/
std::unique_ptr<i2p::sam::Session> m_i2p_sam_session;
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 0e10fa5f9d..6aaacd5068 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -14,11 +14,13 @@
#include <consensus/validation.h>
#include <deploymentstatus.h>
#include <hash.h>
+#include <headerssync.h>
#include <index/blockfilterindex.h>
#include <merkleblock.h>
#include <netbase.h>
#include <netmessagemaker.h>
#include <node/blockstorage.h>
+#include <node/txreconciliation.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <policy/settings.h>
@@ -263,19 +265,14 @@ struct Peer {
/** The feerate in the most recent BIP133 `feefilter` message sent to the peer.
* It is *not* a p2p protocol violation for the peer to send us
* transactions with a lower fee rate than this. See BIP133. */
- CAmount m_fee_filter_sent{0};
+ CAmount m_fee_filter_sent GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0};
/** Timestamp after which we will send the next BIP133 `feefilter` message
* to the peer. */
- std::chrono::microseconds m_next_send_feefilter{0};
+ std::chrono::microseconds m_next_send_feefilter GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0};
struct TxRelay {
mutable RecursiveMutex m_bloom_filter_mutex;
- /** Whether the peer wishes to receive transaction announcements.
- *
- * This is initially set based on the fRelay flag in the received
- * `version` message. If initially set to false, it can only be flipped
- * to true if we have offered the peer NODE_BLOOM services and it sends
- * us a `filterload` or `filterclear` message. See BIP37. */
+ /** Whether we relay transactions to this peer. */
bool m_relay_txs GUARDED_BY(m_bloom_filter_mutex){false};
/** A bloom filter for which transactions to announce to the peer. See BIP37. */
std::unique_ptr<CBloomFilter> m_bloom_filter PT_GUARDED_BY(m_bloom_filter_mutex) GUARDED_BY(m_bloom_filter_mutex){nullptr};
@@ -289,7 +286,7 @@ struct Peer {
* non-wtxid-relay peers, wtxid for wtxid-relay peers). We use the
* mempool to sort transactions in dependency order before relay, so
* this does not have to be sorted. */
- std::set<uint256> m_tx_inventory_to_send;
+ std::set<uint256> m_tx_inventory_to_send GUARDED_BY(m_tx_inventory_mutex);
/** Whether the peer has requested us to send our complete mempool. Only
* permitted if the peer has NetPermissionFlags::Mempool. See BIP35. */
bool m_send_mempool GUARDED_BY(m_tx_inventory_mutex){false};
@@ -297,7 +294,7 @@ struct Peer {
std::atomic<std::chrono::seconds> m_last_mempool_req{0s};
/** The next time after which we will send an `inv` message containing
* transaction announcements to this peer. */
- std::chrono::microseconds m_next_inv_send_time{0};
+ std::chrono::microseconds m_next_inv_send_time GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0};
/** Minimum fee rate with which to filter transaction announcements to this node. See BIP133. */
std::atomic<CAmount> m_fee_filter_received{0};
@@ -318,7 +315,7 @@ struct Peer {
};
/** A vector of addresses to send to the peer, limited to MAX_ADDR_TO_SEND. */
- std::vector<CAddress> m_addrs_to_send;
+ std::vector<CAddress> m_addrs_to_send GUARDED_BY(NetEventsInterface::g_msgproc_mutex);
/** Probabilistic filter to track recent addr messages relayed with this
* peer. Used to avoid relaying redundant addresses to this peer.
*
@@ -328,7 +325,7 @@ struct Peer {
*
* Presence of this filter must correlate with m_addr_relay_enabled.
**/
- std::unique_ptr<CRollingBloomFilter> m_addr_known;
+ std::unique_ptr<CRollingBloomFilter> m_addr_known GUARDED_BY(NetEventsInterface::g_msgproc_mutex);
/** Whether we are participating in address relay with this connection.
*
* We set this bool to true for outbound peers (other than
@@ -345,7 +342,7 @@ struct Peer {
* initialized.*/
std::atomic_bool m_addr_relay_enabled{false};
/** Whether a getaddr request to this peer is outstanding. */
- bool m_getaddr_sent{false};
+ bool m_getaddr_sent GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false};
/** Guards address sending timers. */
mutable Mutex m_addr_send_times_mutex;
/** Time point to send the next ADDR message to this peer. */
@@ -356,12 +353,12 @@ struct Peer {
* messages, indicating a preference to receive ADDRv2 instead of ADDR ones. */
std::atomic_bool m_wants_addrv2{false};
/** Whether this peer has already sent us a getaddr message. */
- bool m_getaddr_recvd{false};
+ bool m_getaddr_recvd GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false};
/** Number of addresses that can be processed from this peer. Start at 1 to
* permit self-announcement. */
- double m_addr_token_bucket{1.0};
+ double m_addr_token_bucket GUARDED_BY(NetEventsInterface::g_msgproc_mutex){1.0};
/** When m_addr_token_bucket was last updated */
- std::chrono::microseconds m_addr_token_timestamp{GetTime<std::chrono::microseconds>()};
+ std::chrono::microseconds m_addr_token_timestamp GUARDED_BY(NetEventsInterface::g_msgproc_mutex){GetTime<std::chrono::microseconds>()};
/** Total number of addresses that were dropped due to rate limiting. */
std::atomic<uint64_t> m_addr_rate_limited{0};
/** Total number of addresses that were processed (excludes rate-limited ones). */
@@ -370,13 +367,25 @@ struct Peer {
/** Set of txids to reconsider once their parent transactions have been accepted **/
std::set<uint256> m_orphan_work_set GUARDED_BY(g_cs_orphans);
+ /** Whether we've sent this peer a getheaders in response to an inv prior to initial-headers-sync completing */
+ bool m_inv_triggered_getheaders_before_sync GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false};
+
/** Protects m_getdata_requests **/
Mutex m_getdata_requests_mutex;
/** Work queue of items requested by this peer **/
std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex);
/** Time of the last getheaders message to this peer */
- NodeClock::time_point m_last_getheaders_timestamp{};
+ NodeClock::time_point m_last_getheaders_timestamp GUARDED_BY(NetEventsInterface::g_msgproc_mutex){};
+
+ /** Protects m_headers_sync **/
+ Mutex m_headers_sync_mutex;
+ /** Headers-sync state for this peer (eg for initial sync, or syncing large
+ * reorgs) **/
+ std::unique_ptr<HeadersSyncState> m_headers_sync PT_GUARDED_BY(m_headers_sync_mutex) GUARDED_BY(m_headers_sync_mutex) {};
+
+ /** Whether we've sent our peer a sendheaders message. **/
+ std::atomic<bool> m_sent_sendheaders{false};
explicit Peer(NodeId id, ServiceFlags our_services)
: m_id{id}
@@ -386,9 +395,7 @@ struct Peer {
private:
Mutex m_tx_relay_mutex;
- /** Transaction relay data. Will be a nullptr if we're not relaying
- * transactions with this peer (e.g. if it's a block-relay-only peer or
- * the peer has sent us fRelay=false with bloom filters disabled). */
+ /** Transaction relay data. May be a nullptr. */
std::unique_ptr<TxRelay> m_tx_relay GUARDED_BY(m_tx_relay_mutex);
};
@@ -500,11 +507,11 @@ public:
/** Implement NetEventsInterface */
void InitializeNode(CNode& node, ServiceFlags our_services) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
- void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
+ void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex);
bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override
- EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex);
- bool SendMessages(CNode* pto) override EXCLUSIVE_LOCKS_REQUIRED(pto->cs_sendProcessing)
- EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex);
+ EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex);
+ bool SendMessages(CNode* pto) override
+ EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, g_msgproc_mutex);
/** Implement PeerManager */
void StartScheduledTasks(CScheduler& scheduler) override;
@@ -519,12 +526,12 @@ public:
void UnitTestMisbehaving(NodeId peer_id, int howmuch) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex) { Misbehaving(*Assert(GetPeerRef(peer_id)), howmuch, ""); };
void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv,
const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override
- EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex);
+ EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex);
void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) override;
private:
/** Consider evicting an outbound peer based on the amount of time they've been behind our tip */
- void ConsiderEviction(CNode& pto, Peer& peer, std::chrono::seconds time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ void ConsiderEviction(CNode& pto, Peer& peer, std::chrono::seconds time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_msgproc_mutex);
/** If we have extra outbound peers, try to disconnect the one with the oldest block announcement */
void EvictExtraOutboundPeers(std::chrono::seconds now) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@@ -578,23 +585,74 @@ private:
void ProcessOrphanTx(std::set<uint256>& orphan_work_set) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans)
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
- /** Process a single headers message from a peer. */
+ /** Process a single headers message from a peer.
+ *
+ * @param[in] pfrom CNode of the peer
+ * @param[in] peer The peer sending us the headers
+ * @param[in] headers The headers received. Note that this may be modified within ProcessHeadersMessage.
+ * @param[in] via_compact_block Whether this header came in via compact block handling.
+ */
void ProcessHeadersMessage(CNode& pfrom, Peer& peer,
- const std::vector<CBlockHeader>& headers,
+ std::vector<CBlockHeader>&& headers,
bool via_compact_block)
- EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
+ EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex, g_msgproc_mutex);
/** Various helpers for headers processing, invoked by ProcessHeadersMessage() */
+ /** Return true if headers are continuous and have valid proof-of-work (DoS points assigned on failure) */
+ bool CheckHeadersPoW(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams, Peer& peer);
+ /** Calculate an anti-DoS work threshold for headers chains */
+ arith_uint256 GetAntiDoSWorkThreshold();
/** Deal with state tracking and headers sync for peers that send the
* occasional non-connecting header (this can happen due to BIP 130 headers
* announcements for blocks interacting with the 2hr (MAX_FUTURE_BLOCK_TIME) rule). */
- void HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers);
+ void HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
/** Return true if the headers connect to each other, false otherwise */
bool CheckHeadersAreContinuous(const std::vector<CBlockHeader>& headers) const;
+ /** Try to continue a low-work headers sync that has already begun.
+ * Assumes the caller has already verified the headers connect, and has
+ * checked that each header satisfies the proof-of-work target included in
+ * the header.
+ * @param[in] peer The peer we're syncing with.
+ * @param[in] pfrom CNode of the peer
+ * @param[in,out] headers The headers to be processed.
+ * @return True if the passed in headers were successfully processed
+ * as the continuation of a low-work headers sync in progress;
+ * false otherwise.
+ * If false, the passed in headers will be returned back to
+ * the caller.
+ * If true, the returned headers may be empty, indicating
+ * there is no more work for the caller to do; or the headers
+ * may be populated with entries that have passed anti-DoS
+ * checks (and therefore may be validated for block index
+ * acceptance by the caller).
+ */
+ bool IsContinuationOfLowWorkHeadersSync(Peer& peer, CNode& pfrom,
+ std::vector<CBlockHeader>& headers)
+ EXCLUSIVE_LOCKS_REQUIRED(peer.m_headers_sync_mutex, !m_headers_presync_mutex, g_msgproc_mutex);
+ /** Check work on a headers chain to be processed, and if insufficient,
+ * initiate our anti-DoS headers sync mechanism.
+ *
+ * @param[in] peer The peer whose headers we're processing.
+ * @param[in] pfrom CNode of the peer
+ * @param[in] chain_start_header Where these headers connect in our index.
+ * @param[in,out] headers The headers to be processed.
+ *
+ * @return True if chain was low work (headers will be empty after
+ * calling); false otherwise.
+ */
+ bool TryLowWorkHeadersSync(Peer& peer, CNode& pfrom,
+ const CBlockIndex* chain_start_header,
+ std::vector<CBlockHeader>& headers)
+ EXCLUSIVE_LOCKS_REQUIRED(!peer.m_headers_sync_mutex, !m_peer_mutex, !m_headers_presync_mutex, g_msgproc_mutex);
+
+ /** Return true if the given header is an ancestor of
+ * m_chainman.m_best_header or our current tip */
+ bool IsAncestorOfBestHeaderOrTip(const CBlockIndex* header) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+
/** Request further headers from this peer with a given locator.
* We don't issue a getheaders message if we have a recent one outstanding.
* This returns true if a getheaders is actually sent, and false otherwise.
*/
- bool MaybeSendGetHeaders(CNode& pfrom, const CBlockLocator& locator, Peer& peer);
+ bool MaybeSendGetHeaders(CNode& pfrom, const CBlockLocator& locator, Peer& peer) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
/** Potentially fetch blocks from this peer upon receipt of a new headers tip */
void HeadersDirectFetchBlocks(CNode& pfrom, const Peer& peer, const CBlockIndex* pindexLast);
/** Update peer state based on received headers message */
@@ -618,7 +676,10 @@ private:
void MaybeSendPing(CNode& node_to, Peer& peer, std::chrono::microseconds now);
/** Send `addr` messages on a regular schedule. */
- void MaybeSendAddr(CNode& node, Peer& peer, std::chrono::microseconds current_time);
+ void MaybeSendAddr(CNode& node, Peer& peer, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
+
+ /** Send a single `sendheaders` message, after we have completed headers sync with a peer. */
+ void MaybeSendSendHeaders(CNode& node, Peer& peer) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
/** Relay (gossip) an address to a few randomly chosen nodes.
*
@@ -627,10 +688,10 @@ private:
* @param[in] fReachable Whether the address' network is reachable. We relay unreachable
* addresses less.
*/
- void RelayAddress(NodeId originator, const CAddress& addr, bool fReachable) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
+ void RelayAddress(NodeId originator, const CAddress& addr, bool fReachable) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex);
/** Send `feefilter` message. */
- void MaybeSendFeefilter(CNode& node, Peer& peer, std::chrono::microseconds current_time);
+ void MaybeSendFeefilter(CNode& node, Peer& peer, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
const CChainParams& m_chainparams;
CConnman& m_connman;
@@ -640,12 +701,13 @@ private:
ChainstateManager& m_chainman;
CTxMemPool& m_mempool;
TxRequestTracker m_txrequest GUARDED_BY(::cs_main);
+ std::unique_ptr<TxReconciliationTracker> m_txreconciliation;
/** The height of the best chain */
std::atomic<int> m_best_height{-1};
/** Next time to check for stale tip */
- std::chrono::seconds m_stale_tip_check_time{0s};
+ std::chrono::seconds m_stale_tip_check_time GUARDED_BY(cs_main){0s};
/** Whether this node is running in -blocksonly mode */
const bool m_ignore_incoming_txs;
@@ -654,7 +716,7 @@ private:
/** Whether we've completed initial sync yet, for determining when to turn
* on extra block-relay-only peers. */
- bool m_initial_sync_finished{false};
+ bool m_initial_sync_finished GUARDED_BY(cs_main){false};
/** Protects m_peer_map. This mutex must not be locked while holding a lock
* on any of the mutexes inside a Peer object. */
@@ -682,6 +744,9 @@ private:
/** Number of nodes with fSyncStarted. */
int nSyncStarted GUARDED_BY(cs_main) = 0;
+ /** Hash of the last block we received via INV */
+ uint256 m_last_block_inv_triggering_headers_sync GUARDED_BY(g_msgproc_mutex){};
+
/**
* Sources of received blocks, saved to be able punish them when processing
* happens afterwards.
@@ -773,8 +838,26 @@ private:
std::shared_ptr<const CBlockHeaderAndShortTxIDs> m_most_recent_compact_block GUARDED_BY(m_most_recent_block_mutex);
uint256 m_most_recent_block_hash GUARDED_BY(m_most_recent_block_mutex);
+ // Data about the low-work headers synchronization, aggregated from all peers' HeadersSyncStates.
+ /** Mutex guarding the other m_headers_presync_* variables. */
+ Mutex m_headers_presync_mutex;
+ /** A type to represent statistics about a peer's low-work headers sync.
+ *
+ * - The first field is the total verified amount of work in that synchronization.
+ * - The second is:
+ * - nullopt: the sync is in REDOWNLOAD phase (phase 2).
+ * - {height, timestamp}: the sync has the specified tip height and block timestamp (phase 1).
+ */
+ using HeadersPresyncStats = std::pair<arith_uint256, std::optional<std::pair<int64_t, uint32_t>>>;
+ /** Statistics for all peers in low-work headers sync. */
+ std::map<NodeId, HeadersPresyncStats> m_headers_presync_stats GUARDED_BY(m_headers_presync_mutex) {};
+ /** The peer with the most-work entry in m_headers_presync_stats. */
+ NodeId m_headers_presync_bestpeer GUARDED_BY(m_headers_presync_mutex) {-1};
+ /** The m_headers_presync_stats improved, and needs signalling. */
+ std::atomic_bool m_headers_presync_should_signal{false};
+
/** Height of the highest block announced using BIP 152 high-bandwidth mode. */
- int m_highest_fast_announce{0};
+ int m_highest_fast_announce GUARDED_BY(::cs_main){0};
/** Have we requested this block from a peer */
bool IsBlockRequested(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@@ -810,7 +893,7 @@ private:
EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, peer.m_getdata_requests_mutex) LOCKS_EXCLUDED(::cs_main);
/** Process a new block. Perform any post-processing housekeeping */
- void ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing);
+ void ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked);
/** Relay map (txid or wtxid -> CTransactionRef) */
typedef std::map<uint256, CTransactionRef> MapRelay;
@@ -921,7 +1004,10 @@ private:
* @return True if address relay is enabled with peer
* False if address relay is disallowed
*/
- bool SetupAddressRelay(const CNode& node, Peer& peer);
+ bool SetupAddressRelay(const CNode& node, Peer& peer) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
+
+ void AddAddressKnown(Peer& peer, const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
+ void PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& insecure_rand) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
};
const CNodeState* PeerManagerImpl::State(NodeId pnode) const EXCLUSIVE_LOCKS_REQUIRED(cs_main)
@@ -947,13 +1033,13 @@ static bool IsAddrCompatible(const Peer& peer, const CAddress& addr)
return peer.m_wants_addrv2 || addr.IsAddrV1Compatible();
}
-static void AddAddressKnown(Peer& peer, const CAddress& addr)
+void PeerManagerImpl::AddAddressKnown(Peer& peer, const CAddress& addr)
{
assert(peer.m_addr_known);
peer.m_addr_known->insert(addr.GetKey());
}
-static void PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& insecure_rand)
+void PeerManagerImpl::PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& insecure_rand)
{
// Known checking here is only to save space from duplicates.
// Before sending, we'll filter it again for known addresses that were
@@ -1145,7 +1231,7 @@ bool PeerManagerImpl::TipMayBeStale()
bool PeerManagerImpl::CanDirectFetch()
{
- return m_chainman.ActiveChain().Tip()->GetBlockTime() > GetAdjustedTime() - m_chainparams.GetConsensus().nPowTargetSpacing * 20;
+ return m_chainman.ActiveChain().Tip()->Time() > GetAdjustedTime() - m_chainparams.GetConsensus().PowTargetSpacing() * 20;
}
static bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
@@ -1202,7 +1288,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(const Peer& peer, unsigned int co
// Make sure pindexBestKnownBlock is up to date, we'll need it.
ProcessBlockAvailability(peer.m_id);
- if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < m_chainman.ActiveChain().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) {
+ if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < m_chainman.ActiveChain().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < m_chainman.MinimumChainWork()) {
// This peer has nothing interesting.
return;
}
@@ -1291,7 +1377,7 @@ void PeerManagerImpl::PushNodeVersion(CNode& pnode, const Peer& peer)
CService addr_you = addr.IsRoutable() && !IsProxy(addr) && addr.IsAddrV1Compatible() ? addr : CService();
uint64_t your_services{addr.nServices};
- const bool tx_relay = !m_ignore_incoming_txs && !pnode.IsBlockOnlyConn() && !pnode.IsFeelerConn();
+ const bool tx_relay{!RejectIncomingTxs(pnode)};
m_connman.PushMessage(&pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, my_services, nTime,
your_services, addr_you, // Together the pre-version-31402 serialization of CAddress "addrYou" (without nTime)
my_services, CService(), // Together the pre-version-31402 serialization of CAddress "addrMe" (without nTime)
@@ -1405,6 +1491,7 @@ void PeerManagerImpl::FinalizeNode(const CNode& node)
}
WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid));
m_txrequest.DisconnectedPeer(nodeid);
+ if (m_txreconciliation) m_txreconciliation->ForgetPeer(nodeid);
m_num_preferred_download_peers -= state->fPreferredDownload;
m_peers_downloading_from -= (state->nBlocksInFlight != 0);
assert(m_peers_downloading_from >= 0);
@@ -1431,6 +1518,10 @@ void PeerManagerImpl::FinalizeNode(const CNode& node)
// fSuccessfullyConnected set.
m_addrman.Connected(node.addr);
}
+ {
+ LOCK(m_headers_presync_mutex);
+ m_headers_presync_stats.erase(nodeid);
+ }
LogPrint(BCLog::NET, "Cleared nodestate for peer=%d\n", nodeid);
}
@@ -1495,6 +1586,12 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c
stats.m_addr_processed = peer->m_addr_processed.load();
stats.m_addr_rate_limited = peer->m_addr_rate_limited.load();
stats.m_addr_relay_enabled = peer->m_addr_relay_enabled.load();
+ {
+ LOCK(peer->m_headers_sync_mutex);
+ if (peer->m_headers_sync) {
+ stats.presync_height = peer->m_headers_sync->GetPresyncHeight();
+ }
+ }
return true;
}
@@ -1538,6 +1635,10 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati
switch (state.GetResult()) {
case BlockValidationResult::BLOCK_RESULT_UNSET:
break;
+ case BlockValidationResult::BLOCK_HEADER_LOW_WORK:
+ // We didn't try to process the block because the header chain may have
+ // too little work.
+ break;
// The node is providing invalid data:
case BlockValidationResult::BLOCK_CONSENSUS:
case BlockValidationResult::BLOCK_MUTATED:
@@ -1675,6 +1776,11 @@ PeerManagerImpl::PeerManagerImpl(CConnman& connman, AddrMan& addrman,
m_mempool(pool),
m_ignore_incoming_txs(ignore_incoming_txs)
{
+ // While Erlay support is incomplete, it must be enabled explicitly via -txreconciliation.
+ // This argument can go away after Erlay support is complete.
+ if (gArgs.GetBoolArg("-txreconciliation", DEFAULT_TXRECONCILIATION_ENABLE)) {
+ m_txreconciliation = std::make_unique<TxReconciliationTracker>(TXRECONCILIATION_VERSION);
+ }
}
void PeerManagerImpl::StartScheduledTasks(CScheduler& scheduler)
@@ -2257,6 +2363,35 @@ void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, Peer& peer, const CBlo
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCKTXN, resp));
}
+bool PeerManagerImpl::CheckHeadersPoW(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams, Peer& peer)
+{
+ // Do these headers have proof-of-work matching what's claimed?
+ if (!HasValidProofOfWork(headers, consensusParams)) {
+ Misbehaving(peer, 100, "header with invalid proof of work");
+ return false;
+ }
+
+ // Are these headers connected to each other?
+ if (!CheckHeadersAreContinuous(headers)) {
+ Misbehaving(peer, 20, "non-continuous headers sequence");
+ return false;
+ }
+ return true;
+}
+
+arith_uint256 PeerManagerImpl::GetAntiDoSWorkThreshold()
+{
+ arith_uint256 near_chaintip_work = 0;
+ LOCK(cs_main);
+ if (m_chainman.ActiveChain().Tip() != nullptr) {
+ const CBlockIndex *tip = m_chainman.ActiveChain().Tip();
+ // Use a 144 block buffer, so that we'll accept headers that fork from
+ // near our tip.
+ near_chaintip_work = tip->nChainWork - std::min<arith_uint256>(144*GetBlockProof(*tip), tip->nChainWork);
+ }
+ return std::max(near_chaintip_work, m_chainman.MinimumChainWork());
+}
+
/**
* Special handling for unconnecting headers that might be part of a block
* announcement.
@@ -2279,7 +2414,7 @@ void PeerManagerImpl::HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer,
nodestate->nUnconnectingHeaders++;
// Try to fill in the missing headers.
- if (MaybeSendGetHeaders(pfrom, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), peer)) {
+ if (MaybeSendGetHeaders(pfrom, GetLocator(m_chainman.m_best_header), peer)) {
LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n",
headers[0].GetHash().ToString(),
headers[0].hashPrevBlock.ToString(),
@@ -2310,6 +2445,150 @@ bool PeerManagerImpl::CheckHeadersAreContinuous(const std::vector<CBlockHeader>&
return true;
}
+bool PeerManagerImpl::IsContinuationOfLowWorkHeadersSync(Peer& peer, CNode& pfrom, std::vector<CBlockHeader>& headers)
+{
+ if (peer.m_headers_sync) {
+ auto result = peer.m_headers_sync->ProcessNextHeaders(headers, headers.size() == MAX_HEADERS_RESULTS);
+ if (result.request_more) {
+ auto locator = peer.m_headers_sync->NextHeadersRequestLocator();
+ // If we were instructed to ask for a locator, it should not be empty.
+ Assume(!locator.vHave.empty());
+ if (!locator.vHave.empty()) {
+ // It should be impossible for the getheaders request to fail,
+ // because we should have cleared the last getheaders timestamp
+ // when processing the headers that triggered this call. But
+ // it may be possible to bypass this via compactblock
+ // processing, so check the result before logging just to be
+ // safe.
+ bool sent_getheaders = MaybeSendGetHeaders(pfrom, locator, peer);
+ if (sent_getheaders) {
+ LogPrint(BCLog::NET, "more getheaders (from %s) to peer=%d\n",
+ locator.vHave.front().ToString(), pfrom.GetId());
+ } else {
+ LogPrint(BCLog::NET, "error sending next getheaders (from %s) to continue sync with peer=%d\n",
+ locator.vHave.front().ToString(), pfrom.GetId());
+ }
+ }
+ }
+
+ if (peer.m_headers_sync->GetState() == HeadersSyncState::State::FINAL) {
+ peer.m_headers_sync.reset(nullptr);
+
+ // Delete this peer's entry in m_headers_presync_stats.
+ // If this is m_headers_presync_bestpeer, it will be replaced later
+ // by the next peer that triggers the else{} branch below.
+ LOCK(m_headers_presync_mutex);
+ m_headers_presync_stats.erase(pfrom.GetId());
+ } else {
+ // Build statistics for this peer's sync.
+ HeadersPresyncStats stats;
+ stats.first = peer.m_headers_sync->GetPresyncWork();
+ if (peer.m_headers_sync->GetState() == HeadersSyncState::State::PRESYNC) {
+ stats.second = {peer.m_headers_sync->GetPresyncHeight(),
+ peer.m_headers_sync->GetPresyncTime()};
+ }
+
+ // Update statistics in stats.
+ LOCK(m_headers_presync_mutex);
+ m_headers_presync_stats[pfrom.GetId()] = stats;
+ auto best_it = m_headers_presync_stats.find(m_headers_presync_bestpeer);
+ bool best_updated = false;
+ if (best_it == m_headers_presync_stats.end()) {
+ // If the cached best peer is outdated, iterate over all remaining ones (including
+ // newly updated one) to find the best one.
+ NodeId peer_best{-1};
+ const HeadersPresyncStats* stat_best{nullptr};
+ for (const auto& [peer, stat] : m_headers_presync_stats) {
+ if (!stat_best || stat > *stat_best) {
+ peer_best = peer;
+ stat_best = &stat;
+ }
+ }
+ m_headers_presync_bestpeer = peer_best;
+ best_updated = (peer_best == pfrom.GetId());
+ } else if (best_it->first == pfrom.GetId() || stats > best_it->second) {
+ // pfrom was and remains the best peer, or pfrom just became best.
+ m_headers_presync_bestpeer = pfrom.GetId();
+ best_updated = true;
+ }
+ if (best_updated && stats.second.has_value()) {
+ // If the best peer updated, and it is in its first phase, signal.
+ m_headers_presync_should_signal = true;
+ }
+ }
+
+ if (result.success) {
+ // We only overwrite the headers passed in if processing was
+ // successful.
+ headers.swap(result.pow_validated_headers);
+ }
+
+ return result.success;
+ }
+ // Either we didn't have a sync in progress, or something went wrong
+ // processing these headers, or we are returning headers to the caller to
+ // process.
+ return false;
+}
+
+bool PeerManagerImpl::TryLowWorkHeadersSync(Peer& peer, CNode& pfrom, const CBlockIndex* chain_start_header, std::vector<CBlockHeader>& headers)
+{
+ // Calculate the total work on this chain.
+ arith_uint256 total_work = chain_start_header->nChainWork + CalculateHeadersWork(headers);
+
+ // Our dynamic anti-DoS threshold (minimum work required on a headers chain
+ // before we'll store it)
+ arith_uint256 minimum_chain_work = GetAntiDoSWorkThreshold();
+
+ // Avoid DoS via low-difficulty-headers by only processing if the headers
+ // are part of a chain with sufficient work.
+ if (total_work < minimum_chain_work) {
+ // Only try to sync with this peer if their headers message was full;
+ // otherwise they don't have more headers after this so no point in
+ // trying to sync their too-little-work chain.
+ if (headers.size() == MAX_HEADERS_RESULTS) {
+ // Note: we could advance to the last header in this set that is
+ // known to us, rather than starting at the first header (which we
+ // may already have); however this is unlikely to matter much since
+ // ProcessHeadersMessage() already handles the case where all
+ // headers in a received message are already known and are
+ // ancestors of m_best_header or chainActive.Tip(), by skipping
+ // this logic in that case. So even if the first header in this set
+ // of headers is known, some header in this set must be new, so
+ // advancing to the first unknown header would be a small effect.
+ LOCK(peer.m_headers_sync_mutex);
+ peer.m_headers_sync.reset(new HeadersSyncState(peer.m_id, m_chainparams.GetConsensus(),
+ chain_start_header, minimum_chain_work));
+
+ // Now a HeadersSyncState object for tracking this synchronization
+ // is created, process the headers using it as normal. Failures are
+ // handled inside of IsContinuationOfLowWorkHeadersSync.
+ (void)IsContinuationOfLowWorkHeadersSync(peer, pfrom, headers);
+ } else {
+ LogPrint(BCLog::NET, "Ignoring low-work chain (height=%u) from peer=%d\n", chain_start_header->nHeight + headers.size(), pfrom.GetId());
+ }
+
+ // The peer has not yet given us a chain that meets our work threshold,
+ // so we want to prevent further processing of the headers in any case.
+ headers = {};
+ return true;
+ }
+
+ return false;
+}
+
+bool PeerManagerImpl::IsAncestorOfBestHeaderOrTip(const CBlockIndex* header)
+{
+ if (header == nullptr) {
+ return false;
+ } else if (m_chainman.m_best_header != nullptr && header == m_chainman.m_best_header->GetAncestor(header->nHeight)) {
+ return true;
+ } else if (m_chainman.ActiveChain().Contains(header)) {
+ return true;
+ }
+ return false;
+}
+
bool PeerManagerImpl::MaybeSendGetHeaders(CNode& pfrom, const CBlockLocator& locator, Peer& peer)
{
const CNetMsgMaker msgMaker(pfrom.GetCommonVersion());
@@ -2424,14 +2703,14 @@ void PeerManagerImpl::UpdatePeerStateForReceivedHeaders(CNode& pfrom,
if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && !may_have_more_headers) {
// If the peer has no more headers to give us, then we know we have
// their tip.
- if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < nMinimumChainWork) {
+ if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < m_chainman.MinimumChainWork()) {
// This peer has too little work on their headers chain to help
// us sync -- disconnect if it is an outbound disconnection
// candidate.
- // Note: We compare their tip to nMinimumChainWork (rather than
+ // Note: We compare their tip to the minimum chain work (rather than
// m_chainman.ActiveChain().Tip()) because we won't start block download
// until we have a headers chain that has at least
- // nMinimumChainWork, even if a peer has a chain past our tip,
+ // the minimum chain work, even if a peer has a chain past our tip,
// as an anti-DoS measure.
if (pfrom.IsOutboundOrBlockRelayConn()) {
LogPrintf("Disconnecting outbound peer %d -- headers chain has insufficient work\n", pfrom.GetId());
@@ -2455,21 +2734,73 @@ void PeerManagerImpl::UpdatePeerStateForReceivedHeaders(CNode& pfrom,
}
void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
- const std::vector<CBlockHeader>& headers,
+ std::vector<CBlockHeader>&& headers,
bool via_compact_block)
{
- const CNetMsgMaker msgMaker(pfrom.GetCommonVersion());
size_t nCount = headers.size();
if (nCount == 0) {
// Nothing interesting. Stop asking this peers for more headers.
+ // If we were in the middle of headers sync, receiving an empty headers
+ // message suggests that the peer suddenly has nothing to give us
+ // (perhaps it reorged to our chain). Clear download state for this peer.
+ LOCK(peer.m_headers_sync_mutex);
+ if (peer.m_headers_sync) {
+ peer.m_headers_sync.reset(nullptr);
+ LOCK(m_headers_presync_mutex);
+ m_headers_presync_stats.erase(pfrom.GetId());
+ }
+ return;
+ }
+
+ // Before we do any processing, make sure these pass basic sanity checks.
+ // We'll rely on headers having valid proof-of-work further down, as an
+ // anti-DoS criteria (note: this check is required before passing any
+ // headers into HeadersSyncState).
+ if (!CheckHeadersPoW(headers, m_chainparams.GetConsensus(), peer)) {
+ // Misbehaving() calls are handled within CheckHeadersPoW(), so we can
+ // just return. (Note that even if a header is announced via compact
+ // block, the header itself should be valid, so this type of error can
+ // always be punished.)
return;
}
const CBlockIndex *pindexLast = nullptr;
+ // We'll set already_validated_work to true if these headers are
+ // successfully processed as part of a low-work headers sync in progress
+ // (either in PRESYNC or REDOWNLOAD phase).
+ // If true, this will mean that any headers returned to us (ie during
+ // REDOWNLOAD) can be validated without further anti-DoS checks.
+ bool already_validated_work = false;
+
+ // If we're in the middle of headers sync, let it do its magic.
+ bool have_headers_sync = false;
+ {
+ LOCK(peer.m_headers_sync_mutex);
+
+ already_validated_work = IsContinuationOfLowWorkHeadersSync(peer, pfrom, headers);
+
+ // The headers we passed in may have been:
+ // - untouched, perhaps if no headers-sync was in progress, or some
+ // failure occurred
+ // - erased, such as if the headers were successfully processed and no
+ // additional headers processing needs to take place (such as if we
+ // are still in PRESYNC)
+ // - replaced with headers that are now ready for validation, such as
+ // during the REDOWNLOAD phase of a low-work headers sync.
+ // So just check whether we still have headers that we need to process,
+ // or not.
+ if (headers.empty()) {
+ return;
+ }
+
+ have_headers_sync = !!peer.m_headers_sync;
+ }
+
// Do these headers connect to something in our block index?
- bool headers_connect_blockindex{WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(headers[0].hashPrevBlock) != nullptr)};
+ const CBlockIndex *chain_start_header{WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(headers[0].hashPrevBlock))};
+ bool headers_connect_blockindex{chain_start_header != nullptr};
if (!headers_connect_blockindex) {
if (nCount <= MAX_BLOCKS_TO_ANNOUNCE) {
@@ -2483,28 +2814,58 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
return;
}
+ // If the headers we received are already in memory and an ancestor of
+ // m_best_header or our tip, skip anti-DoS checks. These headers will not
+ // use any more memory (and we are not leaking information that could be
+ // used to fingerprint us).
+ const CBlockIndex *last_received_header{nullptr};
+ {
+ LOCK(cs_main);
+ last_received_header = m_chainman.m_blockman.LookupBlockIndex(headers.back().GetHash());
+ if (IsAncestorOfBestHeaderOrTip(last_received_header)) {
+ already_validated_work = true;
+ }
+ }
+
+ // If our peer has NetPermissionFlags::NoBan privileges, then bypass our
+ // anti-DoS logic (this saves bandwidth when we connect to a trusted peer
+ // on startup).
+ if (pfrom.HasPermission(NetPermissionFlags::NoBan)) {
+ already_validated_work = true;
+ }
+
// At this point, the headers connect to something in our block index.
- if (!CheckHeadersAreContinuous(headers)) {
- Misbehaving(peer, 20, "non-continuous headers sequence");
+ // Do anti-DoS checks to determine if we should process or store for later
+ // processing.
+ if (!already_validated_work && TryLowWorkHeadersSync(peer, pfrom,
+ chain_start_header, headers)) {
+ // If we successfully started a low-work headers sync, then there
+ // should be no headers to process any further.
+ Assume(headers.empty());
return;
}
+ // At this point, we have a set of headers with sufficient work on them
+ // which can be processed.
+
// If we don't have the last header, then this peer will have given us
// something new (if these headers are valid).
- bool received_new_header{WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(headers.back().GetHash()) == nullptr)};
+ bool received_new_header{last_received_header == nullptr};
+ // Now process all the headers.
BlockValidationState state;
- if (!m_chainman.ProcessNewBlockHeaders(headers, state, &pindexLast)) {
+ if (!m_chainman.ProcessNewBlockHeaders(headers, /*min_pow_checked=*/true, state, &pindexLast)) {
if (state.IsInvalid()) {
MaybePunishNodeForBlock(pfrom.GetId(), state, via_compact_block, "invalid header received");
return;
}
}
+ Assume(pindexLast);
- // Consider fetching more headers.
- if (nCount == MAX_HEADERS_RESULTS) {
+ // Consider fetching more headers if we are not using our headers-sync mechanism.
+ if (nCount == MAX_HEADERS_RESULTS && !have_headers_sync) {
// Headers message had its maximum size; the peer may have more headers.
- if (MaybeSendGetHeaders(pfrom, m_chainman.ActiveChain().GetLocator(pindexLast), peer)) {
+ if (MaybeSendGetHeaders(pfrom, GetLocator(pindexLast), peer)) {
LogPrint(BCLog::NET, "more getheaders (%d) to end to peer=%d (startheight:%d)\n",
pindexLast->nHeight, pfrom.GetId(), peer.m_starting_height);
}
@@ -2765,10 +3126,10 @@ void PeerManagerImpl::ProcessGetCFCheckPt(CNode& node, Peer& peer, CDataStream&
m_connman.PushMessage(&node, std::move(msg));
}
-void PeerManagerImpl::ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing)
+void PeerManagerImpl::ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked)
{
bool new_block{false};
- m_chainman.ProcessNewBlock(block, force_processing, &new_block);
+ m_chainman.ProcessNewBlock(block, force_processing, min_pow_checked, &new_block);
if (new_block) {
node.m_last_block_time = GetTime<std::chrono::seconds>();
} else {
@@ -2781,6 +3142,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
const std::chrono::microseconds time_received,
const std::atomic<bool>& interruptMsgProc)
{
+ AssertLockHeld(g_msgproc_mutex);
+
LogPrint(BCLog::NET, "received: %s (%u bytes) peer=%d\n", SanitizeString(msg_type), vRecv.size(), pfrom.GetId());
PeerRef peer = GetPeerRef(pfrom.GetId());
@@ -2882,8 +3245,6 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDADDRV2));
}
- m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK));
-
pfrom.m_has_all_wanted_services = HasAllDesirableServiceFlags(nServices);
peer->m_their_services = nServices;
pfrom.SetAddrLocal(addrMe);
@@ -2893,11 +3254,14 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
}
peer->m_starting_height = starting_height;
- // We only initialize the m_tx_relay data structure if:
- // - this isn't an outbound block-relay-only connection; and
- // - fRelay=true or we're offering NODE_BLOOM to this peer
- // (NODE_BLOOM means that the peer may turn on tx relay later)
+ // Only initialize the Peer::TxRelay m_relay_txs data structure if:
+ // - this isn't an outbound block-relay-only connection, and
+ // - this isn't an outbound feeler connection, and
+ // - fRelay=true (the peer wishes to receive transaction announcements)
+ // or we're offering NODE_BLOOM to this peer. NODE_BLOOM means that
+ // the peer may turn on transaction relay later.
if (!pfrom.IsBlockOnlyConn() &&
+ !pfrom.IsFeelerConn() &&
(fRelay || (peer->m_our_services & NODE_BLOOM))) {
auto* const tx_relay = peer->SetTxRelay();
{
@@ -2907,6 +3271,25 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
if (fRelay) pfrom.m_relays_txs = true;
}
+ if (greatest_common_version >= WTXID_RELAY_VERSION && m_txreconciliation) {
+ // Per BIP-330, we announce txreconciliation support if:
+ // - protocol version per the VERSION message supports WTXID_RELAY;
+ // - we intended to exchange transactions over this connection while establishing it
+ // and the peer indicated support for transaction relay in the VERSION message;
+ // - we are not in -blocksonly mode.
+ if (pfrom.m_relays_txs && !m_ignore_incoming_txs) {
+ const uint64_t recon_salt = m_txreconciliation->PreRegisterPeer(pfrom.GetId());
+ // We suggest our txreconciliation role (initiator/responder) based on
+ // the connection direction.
+ m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDTXRCNCL,
+ !pfrom.IsInboundConn(),
+ pfrom.IsInboundConn(),
+ TXRECONCILIATION_VERSION, recon_salt));
+ }
+ }
+
+ m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK));
+
// Potentially mark this peer as a preferred download peer.
{
LOCK(cs_main);
@@ -2930,7 +3313,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// indicate to the peer that we will participate in addr relay.
if (fListen && !m_chainman.ActiveChainstate().IsInitialBlockDownload())
{
- CAddress addr{GetLocalAddress(pfrom.addr), peer->m_our_services, AdjustedTime()};
+ CAddress addr{GetLocalAddress(pfrom.addr), peer->m_our_services, Now<NodeSeconds>()};
FastRandomContext insecure_rand;
if (addr.IsRoutable())
{
@@ -3026,13 +3409,6 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
pfrom.ConnectionTypeAsString());
}
- if (pfrom.GetCommonVersion() >= SENDHEADERS_VERSION) {
- // Tell our peer we prefer to receive headers rather than inv's
- // We send this to non-NODE NETWORK peers as well, because even
- // non-NODE NETWORK peers can announce blocks (such as pruning
- // nodes)
- m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDHEADERS));
- }
if (pfrom.GetCommonVersion() >= SHORT_IDS_BLOCKS_VERSION) {
// Tell our peer we are willing to provide version 2 cmpctblocks.
// However, we do not request new block announcements using
@@ -3041,6 +3417,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// they may wish to request compact blocks from us
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/false, /*version=*/CMPCTBLOCKS_VERSION));
}
+
+ if (m_txreconciliation) {
+ if (!peer->m_wtxid_relay || !m_txreconciliation->IsPeerRegistered(pfrom.GetId())) {
+ // We could have optimistically pre-registered/registered the peer. In that case,
+ // we should forget about the reconciliation state here if this wasn't followed
+ // by WTXIDRELAY (since WTXIDRELAY can't be announced later).
+ m_txreconciliation->ForgetPeer(pfrom.GetId());
+ }
+ }
+
pfrom.fSuccessfullyConnected = true;
return;
}
@@ -3104,6 +3490,58 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return;
}
+ // Received from a peer demonstrating readiness to announce transactions via reconciliations.
+ // This feature negotiation must happen between VERSION and VERACK to avoid relay problems
+ // from switching announcement protocols after the connection is up.
+ if (msg_type == NetMsgType::SENDTXRCNCL) {
+ if (!m_txreconciliation) {
+ LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "sendtxrcncl from peer=%d ignored, as our node does not have txreconciliation enabled\n", pfrom.GetId());
+ return;
+ }
+
+ if (pfrom.fSuccessfullyConnected) {
+ // Disconnect peers that send a SENDTXRCNCL message after VERACK.
+ LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "sendtxrcncl received after verack from peer=%d; disconnecting\n", pfrom.GetId());
+ pfrom.fDisconnect = true;
+ return;
+ }
+
+ if (!peer->GetTxRelay()) {
+ // Disconnect peers that send a SENDTXRCNCL message even though we indicated we don't
+ // support transaction relay.
+ LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "sendtxrcncl received from peer=%d to which we indicated no tx relay; disconnecting\n", pfrom.GetId());
+ pfrom.fDisconnect = true;
+ return;
+ }
+
+ bool is_peer_initiator, is_peer_responder;
+ uint32_t peer_txreconcl_version;
+ uint64_t remote_salt;
+ vRecv >> is_peer_initiator >> is_peer_responder >> peer_txreconcl_version >> remote_salt;
+
+ if (m_txreconciliation->IsPeerRegistered(pfrom.GetId())) {
+ // A peer is already registered, meaning we already received SENDTXRCNCL from them.
+ LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "txreconciliation protocol violation from peer=%d (sendtxrcncl received from already registered peer); disconnecting\n", pfrom.GetId());
+ pfrom.fDisconnect = true;
+ return;
+ }
+
+ const ReconciliationRegisterResult result = m_txreconciliation->RegisterPeer(pfrom.GetId(), pfrom.IsInboundConn(),
+ is_peer_initiator, is_peer_responder,
+ peer_txreconcl_version,
+ remote_salt);
+
+ // If it's a protocol violation, disconnect.
+ // If the peer was not found (but something unexpected happened) or it was registered,
+ // nothing to be done.
+ if (result == ReconciliationRegisterResult::PROTOCOL_VIOLATION) {
+ LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "txreconciliation protocol violation from peer=%d; disconnecting\n", pfrom.GetId());
+ pfrom.fDisconnect = true;
+ return;
+ }
+ return;
+ }
+
if (!pfrom.fSuccessfullyConnected) {
LogPrint(BCLog::NET, "Unsupported message \"%s\" prior to verack from peer=%d\n", SanitizeString(msg_type), pfrom.GetId());
return;
@@ -3135,7 +3573,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// Store the new addresses
std::vector<CAddress> vAddrOk;
- const auto current_a_time{AdjustedTime()};
+ const auto current_a_time{Now<NodeSeconds>()};
// Update/increment addr rate limiting bucket.
const auto current_time{GetTime<std::chrono::microseconds>()};
@@ -3240,8 +3678,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
UpdateBlockAvailability(pfrom.GetId(), inv.hash);
if (!fAlreadyHave && !fImporting && !fReindex && !IsBlockRequested(inv.hash)) {
// Headers-first is the primary method of announcement on
- // the network. If a node fell back to sending blocks by inv,
- // it's probably for a re-org. The final block hash
+ // the network. If a node fell back to sending blocks by
+ // inv, it may be for a re-org, or because we haven't
+ // completed initial headers sync. The final block hash
// provided should be the highest, so send a getheaders and
// then fetch the blocks we need to catch up.
best_block = &inv.hash;
@@ -3266,10 +3705,30 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
}
if (best_block != nullptr) {
- if (MaybeSendGetHeaders(pfrom, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), *peer)) {
- LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n",
- m_chainman.m_best_header->nHeight, best_block->ToString(),
- pfrom.GetId());
+ // If we haven't started initial headers-sync with this peer, then
+ // consider sending a getheaders now. On initial startup, there's a
+ // reliability vs bandwidth tradeoff, where we are only trying to do
+ // initial headers sync with one peer at a time, with a long
+ // timeout (at which point, if the sync hasn't completed, we will
+ // disconnect the peer and then choose another). In the meantime,
+ // as new blocks are found, we are willing to add one new peer per
+ // block to sync with as well, to sync quicker in the case where
+ // our initial peer is unresponsive (but less bandwidth than we'd
+ // use if we turned on sync with all peers).
+ CNodeState& state{*Assert(State(pfrom.GetId()))};
+ if (state.fSyncStarted || (!peer->m_inv_triggered_getheaders_before_sync && *best_block != m_last_block_inv_triggering_headers_sync)) {
+ if (MaybeSendGetHeaders(pfrom, GetLocator(m_chainman.m_best_header), *peer)) {
+ LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n",
+ m_chainman.m_best_header->nHeight, best_block->ToString(),
+ pfrom.GetId());
+ }
+ if (!state.fSyncStarted) {
+ peer->m_inv_triggered_getheaders_before_sync = true;
+ // Update the last block hash that triggered a new headers
+ // sync, so that we don't turn on headers sync with more
+ // than 1 new peer every new block.
+ m_last_block_inv_triggering_headers_sync = *best_block;
+ }
}
}
@@ -3437,12 +3896,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// Note that if we were to be on a chain that forks from the checkpointed
// chain, then serving those headers to a peer that has seen the
// checkpointed chain would cause that peer to disconnect us. Requiring
- // that our chainwork exceed nMinimumChainWork is a protection against
+ // that our chainwork exceed the minimum chain work is a protection against
// being fed a bogus chain when we started up for the first time and
// getting partitioned off the honest network for serving that chain to
// others.
if (m_chainman.ActiveTip() == nullptr ||
- (m_chainman.ActiveTip()->nChainWork < nMinimumChainWork && !pfrom.HasPermission(NetPermissionFlags::Download))) {
+ (m_chainman.ActiveTip()->nChainWork < m_chainman.MinimumChainWork() && !pfrom.HasPermission(NetPermissionFlags::Download))) {
LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because active chain has too little work; sending empty response\n", pfrom.GetId());
// Just respond with an empty headers message, to tell the peer to
// go away but not treat us as unresponsive.
@@ -3722,12 +4181,17 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
{
LOCK(cs_main);
- if (!m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock)) {
+ const CBlockIndex* prev_block = m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock);
+ if (!prev_block) {
// Doesn't connect (or is genesis), instead of DoSing in AcceptBlockHeader, request deeper headers
if (!m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
- MaybeSendGetHeaders(pfrom, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), *peer);
+ MaybeSendGetHeaders(pfrom, GetLocator(m_chainman.m_best_header), *peer);
}
return;
+ } else if (prev_block->nChainWork + CalculateHeadersWork({cmpctblock.header}) < GetAntiDoSWorkThreshold()) {
+ // If we get a low-work header in a compact block, we can ignore it.
+ LogPrint(BCLog::NET, "Ignoring low-work compact block from peer %d\n", pfrom.GetId());
+ return;
}
if (!m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.GetHash())) {
@@ -3737,7 +4201,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
const CBlockIndex *pindex = nullptr;
BlockValidationState state;
- if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, state, &pindex)) {
+ if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, /*min_pow_checked=*/true, state, &pindex)) {
if (state.IsInvalid()) {
MaybePunishNodeForBlock(pfrom.GetId(), state, /*via_compact_block=*/true, "invalid header via cmpctblock");
return;
@@ -3901,10 +4365,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// (eg disk space). Because we only try to reconstruct blocks when
// we're close to caught up (via the CanDirectFetch() requirement
// above, combined with the behavior of not requesting blocks until
- // we have a chain with at least nMinimumChainWork), and we ignore
+ // we have a chain with at least the minimum chain work), and we ignore
// compact blocks with less work than our tip, it is safe to treat
// reconstructed compact blocks as having been requested.
- ProcessBlock(pfrom, pblock, /*force_processing=*/true);
+ ProcessBlock(pfrom, pblock, /*force_processing=*/true, /*min_pow_checked=*/true);
LOCK(cs_main); // hold cs_main for CBlockIndex::IsValid()
if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS)) {
// Clear download state for this block, which is in
@@ -3987,7 +4451,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// disk-space attacks), but this should be safe due to the
// protections in the compact block handler -- see related comment
// in compact block optimistic reconstruction handling.
- ProcessBlock(pfrom, pblock, /*force_processing=*/true);
+ ProcessBlock(pfrom, pblock, /*force_processing=*/true, /*min_pow_checked=*/true);
}
return;
}
@@ -4018,7 +4482,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
ReadCompactSize(vRecv); // ignore tx count; assume it is 0.
}
- return ProcessHeadersMessage(pfrom, *peer, headers, /*via_compact_block=*/false);
+ ProcessHeadersMessage(pfrom, *peer, std::move(headers), /*via_compact_block=*/false);
+
+ // Check if the headers presync progress needs to be reported to validation.
+ // This needs to be done without holding the m_headers_presync_mutex lock.
+ if (m_headers_presync_should_signal.exchange(false)) {
+ HeadersPresyncStats stats;
+ {
+ LOCK(m_headers_presync_mutex);
+ auto it = m_headers_presync_stats.find(m_headers_presync_bestpeer);
+ if (it != m_headers_presync_stats.end()) stats = it->second;
+ }
+ if (stats.second) {
+ m_chainman.ReportHeadersPresync(stats.first, stats.second->first, stats.second->second);
+ }
+ }
+
+ return;
}
if (msg_type == NetMsgType::BLOCK)
@@ -4036,6 +4516,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
bool forceProcessing = false;
const uint256 hash(pblock->GetHash());
+ bool min_pow_checked = false;
{
LOCK(cs_main);
// Always process the block if we requested it, since we may
@@ -4046,8 +4527,14 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// which peers send us compact blocks, so the race between here and
// cs_main in ProcessNewBlock is fine.
mapBlockSource.emplace(hash, std::make_pair(pfrom.GetId(), true));
+
+ // Check work on this block against our anti-dos thresholds.
+ const CBlockIndex* prev_block = m_chainman.m_blockman.LookupBlockIndex(pblock->hashPrevBlock);
+ if (prev_block && prev_block->nChainWork + CalculateHeadersWork({pblock->GetBlockHeader()}) >= GetAntiDoSWorkThreshold()) {
+ min_pow_checked = true;
+ }
}
- ProcessBlock(pfrom, pblock, forceProcessing);
+ ProcessBlock(pfrom, pblock, forceProcessing, min_pow_checked);
return;
}
@@ -4352,6 +4839,8 @@ bool PeerManagerImpl::MaybeDiscourageAndDisconnect(CNode& pnode, Peer& peer)
bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interruptMsgProc)
{
+ AssertLockHeld(g_msgproc_mutex);
+
bool fMoreWork = false;
PeerRef peer = GetPeerRef(pfrom->GetId());
@@ -4475,7 +4964,7 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, Peer& peer, std::chrono::seco
// getheaders in-flight already, in which case the peer should
// still respond to us with a sufficiently high work chain tip.
MaybeSendGetHeaders(pto,
- m_chainman.ActiveChain().GetLocator(state.m_chain_sync.m_work_header->pprev),
+ GetLocator(state.m_chain_sync.m_work_header->pprev),
peer);
LogPrint(BCLog::NET, "sending getheaders to outbound peer=%d to verify chain work (current best known block:%s, benchmark blockhash: %s)\n", pto.GetId(), state.pindexBestKnownBlock != nullptr ? state.pindexBestKnownBlock->GetBlockHash().ToString() : "<none>", state.m_chain_sync.m_work_header->GetBlockHash().ToString());
state.m_chain_sync.m_sent_getheaders = true;
@@ -4683,7 +5172,7 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros
peer.m_addr_known->reset();
}
if (std::optional<CService> local_service = GetLocalAddrForPeer(node)) {
- CAddress local_addr{*local_service, peer.m_our_services, AdjustedTime()};
+ CAddress local_addr{*local_service, peer.m_our_services, Now<NodeSeconds>()};
FastRandomContext insecure_rand;
PushAddress(peer, local_addr, insecure_rand);
}
@@ -4703,7 +5192,7 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros
// Remove addr records that the peer already knows about, and add new
// addrs to the m_addr_known filter on the same pass.
- auto addr_already_known = [&peer](const CAddress& addr) {
+ auto addr_already_known = [&peer](const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex) {
bool ret = peer.m_addr_known->contains(addr.GetKey());
if (!ret) peer.m_addr_known->insert(addr.GetKey());
return ret;
@@ -4732,6 +5221,27 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros
}
}
+void PeerManagerImpl::MaybeSendSendHeaders(CNode& node, Peer& peer)
+{
+ // Delay sending SENDHEADERS (BIP 130) until we're done with an
+ // initial-headers-sync with this peer. Receiving headers announcements for
+ // new blocks while trying to sync their headers chain is problematic,
+ // because of the state tracking done.
+ if (!peer.m_sent_sendheaders && node.GetCommonVersion() >= SENDHEADERS_VERSION) {
+ LOCK(cs_main);
+ CNodeState &state = *State(node.GetId());
+ if (state.pindexBestKnownBlock != nullptr &&
+ state.pindexBestKnownBlock->nChainWork > m_chainman.MinimumChainWork()) {
+ // Tell our peer we prefer to receive headers rather than inv's
+ // We send this to non-NODE NETWORK peers as well, because even
+ // non-NODE NETWORK peers can announce blocks (such as pruning
+ // nodes)
+ m_connman.PushMessage(&node, CNetMsgMaker(node.GetCommonVersion()).Make(NetMsgType::SENDHEADERS));
+ peer.m_sent_sendheaders = true;
+ }
+ }
+}
+
void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::microseconds current_time)
{
if (m_ignore_incoming_txs) return;
@@ -4759,8 +5269,8 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::mi
}
if (current_time > peer.m_next_send_feefilter) {
CAmount filterToSend = g_filter_rounder.round(currentFilter);
- // We always have a fee filter of at least minRelayTxFee
- filterToSend = std::max(filterToSend, ::minRelayTxFee.GetFeePerK());
+ // We always have a fee filter of at least the min relay fee
+ filterToSend = std::max(filterToSend, m_mempool.m_min_relay_feerate.GetFeePerK());
if (filterToSend != peer.m_fee_filter_sent) {
m_connman.PushMessage(&pto, CNetMsgMaker(pto.GetCommonVersion()).Make(NetMsgType::FEEFILTER, filterToSend));
peer.m_fee_filter_sent = filterToSend;
@@ -4800,6 +5310,7 @@ bool PeerManagerImpl::RejectIncomingTxs(const CNode& peer) const
{
// block-relay-only peers may never send txs to us
if (peer.IsBlockOnlyConn()) return true;
+ if (peer.IsFeelerConn()) return true;
// In -blocksonly mode, peers need the 'relay' permission to send txs to us
if (m_ignore_incoming_txs && !peer.HasPermission(NetPermissionFlags::Relay)) return true;
return false;
@@ -4823,6 +5334,8 @@ bool PeerManagerImpl::SetupAddressRelay(const CNode& node, Peer& peer)
bool PeerManagerImpl::SendMessages(CNode* pto)
{
+ AssertLockHeld(g_msgproc_mutex);
+
PeerRef peer = GetPeerRef(pto->GetId());
if (!peer) return false;
const Consensus::Params& consensusParams = m_chainparams.GetConsensus();
@@ -4853,6 +5366,8 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
MaybeSendAddr(*pto, *peer, current_time);
+ MaybeSendSendHeaders(*pto, *peer);
+
{
LOCK(cs_main);
@@ -4886,7 +5401,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
if (!state.fSyncStarted && CanServeBlocks(*peer) && !fImporting && !fReindex) {
// Only actively request headers from a single peer, unless we're close to today.
- if ((nSyncStarted == 0 && sync_blocks_and_headers_from_peer) || m_chainman.m_best_header->GetBlockTime() > GetAdjustedTime() - 24 * 60 * 60) {
+ if ((nSyncStarted == 0 && sync_blocks_and_headers_from_peer) || m_chainman.m_best_header->Time() > GetAdjustedTime() - 24h) {
const CBlockIndex* pindexStart = m_chainman.m_best_header;
/* If possible, start at the block preceding the currently
best known header. This ensures that we always get a
@@ -4897,7 +5412,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
got back an empty response. */
if (pindexStart->pprev)
pindexStart = pindexStart->pprev;
- if (MaybeSendGetHeaders(*pto, m_chainman.ActiveChain().GetLocator(pindexStart), *peer)) {
+ if (MaybeSendGetHeaders(*pto, GetLocator(pindexStart), *peer)) {
LogPrint(BCLog::NET, "initial getheaders (%d) to peer=%d (startheight:%d)\n", pindexStart->nHeight, pto->GetId(), peer->m_starting_height);
state.fSyncStarted = true;
@@ -4906,7 +5421,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
// Convert HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER to microseconds before scaling
// to maintain precision
std::chrono::microseconds{HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER} *
- (GetAdjustedTime() - m_chainman.m_best_header->GetBlockTime()) / consensusParams.nPowTargetSpacing
+ Ticks<std::chrono::seconds>(GetAdjustedTime() - m_chainman.m_best_header->Time()) / consensusParams.nPowTargetSpacing
);
nSyncStarted++;
}
@@ -5223,7 +5738,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
// Check for headers sync timeouts
if (state.fSyncStarted && state.m_headers_sync_timeout < std::chrono::microseconds::max()) {
// Detect whether this is a stalling initial-headers-sync peer
- if (m_chainman.m_best_header->GetBlockTime() <= GetAdjustedTime() - 24 * 60 * 60) {
+ if (m_chainman.m_best_header->Time() <= GetAdjustedTime() - 24h) {
if (current_time > state.m_headers_sync_timeout && nSyncStarted == 1 && (m_num_preferred_download_peers - state.fPreferredDownload >= 1)) {
// Disconnect a peer (without NetPermissionFlags::NoBan permission) if it is our only sync peer,
// and we have others we could be using instead.
diff --git a/src/net_processing.h b/src/net_processing.h
index bcda9614d4..0d0842fa8e 100644
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -35,6 +35,7 @@ struct CNodeStateStats {
uint64_t m_addr_rate_limited = 0;
bool m_addr_relay_enabled{false};
ServiceFlags their_services;
+ int64_t presync_height{-1};
};
class PeerManager : public CValidationInterface, public NetEventsInterface
@@ -83,7 +84,7 @@ public:
/** Process a single message from a peer. Public for fuzz testing */
virtual void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv,
- const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) = 0;
+ const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex) = 0;
/** This function is used for testing the stale tip eviction logic, see denialofservice_tests.cpp */
virtual void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) = 0;
diff --git a/src/netbase.cpp b/src/netbase.cpp
index 4b8d2f8d0c..8169b40ea6 100644
--- a/src/netbase.cpp
+++ b/src/netbase.cpp
@@ -304,8 +304,7 @@ enum class IntrRecvError {
* read.
*
* @see This function can be interrupted by calling InterruptSocks5(bool).
- * Sockets can be made non-blocking with SetSocketNonBlocking(const
- * SOCKET&).
+ * Sockets can be made non-blocking with Sock::SetNonBlocking().
*/
static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, const Sock& sock)
{
@@ -387,7 +386,7 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a
return error("Error sending to proxy");
}
uint8_t pchRet1[2];
- if ((recvr = InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {
+ if (InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port);
return false;
}
@@ -410,7 +409,7 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a
}
LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password);
uint8_t pchRetA[2];
- if ((recvr = InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {
+ if (InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
return error("Error reading proxy authentication response");
}
if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) {
@@ -476,7 +475,7 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a
if (recvr != IntrRecvError::OK) {
return error("Error reading from proxy");
}
- if ((recvr = InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {
+ if (InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
return error("Error reading from proxy");
}
LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest);
@@ -503,7 +502,7 @@ std::unique_ptr<Sock> CreateSockTCP(const CService& address_family)
// Ensure that waiting for I/O on this socket won't result in undefined
// behavior.
- if (!IsSelectableSocket(sock->Get())) {
+ if (!sock->IsSelectable()) {
LogPrintf("Cannot create connection: non-selectable socket created (fd >= FD_SETSIZE ?)\n");
return nullptr;
}
@@ -525,7 +524,7 @@ std::unique_ptr<Sock> CreateSockTCP(const CService& address_family)
}
// Set the non-blocking option on the socket.
- if (!SetSocketNonBlocking(sock->Get())) {
+ if (!sock->SetNonBlocking()) {
LogPrintf("Error setting socket to non-blocking: %s\n", NetworkErrorString(WSAGetLastError()));
return nullptr;
}
@@ -717,21 +716,6 @@ bool LookupSubNet(const std::string& subnet_str, CSubNet& subnet_out)
return false;
}
-bool SetSocketNonBlocking(const SOCKET& hSocket)
-{
-#ifdef WIN32
- u_long nOne = 1;
- if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR) {
-#else
- int fFlags = fcntl(hSocket, F_GETFL, 0);
- if (fcntl(hSocket, F_SETFL, fFlags | O_NONBLOCK) == SOCKET_ERROR) {
-#endif
- return false;
- }
-
- return true;
-}
-
void InterruptSocks5(bool interrupt)
{
interruptSocks5Recv = interrupt;
diff --git a/src/netbase.h b/src/netbase.h
index fadc8b418e..f7816f5d1d 100644
--- a/src/netbase.h
+++ b/src/netbase.h
@@ -221,8 +221,6 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT
*/
bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed);
-/** Enable non-blocking mode for a socket */
-bool SetSocketNonBlocking(const SOCKET& hSocket);
void InterruptSocks5(bool interrupt);
/**
diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp
index 601d0bdf58..04d46f4361 100644
--- a/src/node/blockstorage.cpp
+++ b/src/node/blockstorage.cpp
@@ -415,7 +415,7 @@ void CleanupBlockRevFiles()
// Remove the rev files immediately and insert the blk file paths into an
// ordered map keyed by block file index.
LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for -reindex with -prune\n");
- fs::path blocksdir = gArgs.GetBlocksDirPath();
+ const fs::path& blocksdir = gArgs.GetBlocksDirPath();
for (fs::directory_iterator it(blocksdir); it != fs::directory_iterator(); it++) {
const std::string path = fs::PathToString(it->path().filename());
if (fs::is_regular_file(*it) &&
@@ -524,6 +524,16 @@ void BlockManager::FlushUndoFile(int block_file, bool finalize)
void BlockManager::FlushBlockFile(bool fFinalize, bool finalize_undo)
{
LOCK(cs_LastBlockFile);
+
+ if (m_blockfile_info.size() < 1) {
+ // Return if we haven't loaded any blockfiles yet. This happens during
+ // chainstate init, when we call ChainstateManager::MaybeRebalanceCaches() (which
+ // then calls FlushStateToDisk()), resulting in a call to this function before we
+ // have populated `m_blockfile_info` via LoadBlockIndexDB().
+ return;
+ }
+ assert(static_cast<int>(m_blockfile_info.size()) > m_last_blockfile);
+
FlatFilePos block_pos_old(m_last_blockfile, m_blockfile_info[m_last_blockfile].nSize);
if (!BlockFileSeq().Flush(block_pos_old, fFinalize)) {
AbortNode("Flushing block file to disk failed. This is likely the result of an I/O error.");
@@ -789,19 +799,24 @@ bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos, c
return true;
}
-/** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */
FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const CChainParams& chainparams, const FlatFilePos* dbp)
{
unsigned int nBlockSize = ::GetSerializeSize(block, CLIENT_VERSION);
FlatFilePos blockPos;
- if (dbp != nullptr) {
+ const auto position_known {dbp != nullptr};
+ if (position_known) {
blockPos = *dbp;
+ } else {
+ // when known, blockPos.nPos points at the offset of the block data in the blk file. that already accounts for
+ // the serialization header present in the file (the 4 magic message start bytes + the 4 length bytes = 8 bytes = BLOCK_SERIALIZATION_HEADER_SIZE).
+ // we add BLOCK_SERIALIZATION_HEADER_SIZE only for new blocks since they will have the serialization header added when written to disk.
+ nBlockSize += static_cast<unsigned int>(BLOCK_SERIALIZATION_HEADER_SIZE);
}
- if (!FindBlockPos(blockPos, nBlockSize + 8, nHeight, active_chain, block.GetBlockTime(), dbp != nullptr)) {
+ if (!FindBlockPos(blockPos, nBlockSize, nHeight, active_chain, block.GetBlockTime(), position_known)) {
error("%s: FindBlockPos failed", __func__);
return FlatFilePos();
}
- if (dbp == nullptr) {
+ if (!position_known) {
if (!WriteBlockToDisk(block, blockPos, chainparams.MessageStart())) {
AbortNode("Failed to write block");
return FlatFilePos();
@@ -882,7 +897,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
// We can't hold cs_main during ActivateBestChain even though we're accessing
// the chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve
// the relevant pointers before the ABC call.
- for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
+ for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(state, nullptr)) {
LogPrintf("Failed to connect best block (%s)\n", state.ToString());
diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h
index 9b76371aae..29501c1959 100644
--- a/src/node/blockstorage.h
+++ b/src/node/blockstorage.h
@@ -26,7 +26,7 @@ class CBlockFileInfo;
class CBlockUndo;
class CChain;
class CChainParams;
-class CChainState;
+class Chainstate;
class ChainstateManager;
struct CCheckpointData;
struct FlatFilePos;
@@ -44,6 +44,9 @@ static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB
/** The maximum size of a blk?????.dat file (since 0.8) */
static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB
+/** Size of header written by WriteBlockToDisk before a serialized CBlock */
+static constexpr size_t BLOCK_SERIALIZATION_HEADER_SIZE = CMessageHeader::MESSAGE_START_SIZE + sizeof(unsigned int);
+
extern std::atomic_bool fImporting;
extern std::atomic_bool fReindex;
/** Pruning-related variables and constants */
@@ -75,12 +78,12 @@ struct PruneLockInfo {
* Maintains a tree of blocks (stored in `m_block_index`) which is consulted
* to determine where the most-work tip is.
*
- * This data is used mostly in `CChainState` - information about, e.g.,
+ * This data is used mostly in `Chainstate` - information about, e.g.,
* candidate tips is not maintained here.
*/
class BlockManager
{
- friend CChainState;
+ friend Chainstate;
friend ChainstateManager;
private:
@@ -171,6 +174,7 @@ public:
bool WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex* pindex, const CChainParams& chainparams)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ /** Store block on disk. If dbp is not nullptr, then it provides the known position of the block within a block file on disk. */
FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const CChainParams& chainparams, const FlatFilePos* dbp);
/** Calculate the amount of disk space the block & undo files currently use */
diff --git a/src/node/caches.cpp b/src/node/caches.cpp
index f168332ee6..a39ad7aeb6 100644
--- a/src/node/caches.cpp
+++ b/src/node/caches.cpp
@@ -4,9 +4,9 @@
#include <node/caches.h>
+#include <index/txindex.h>
#include <txdb.h>
#include <util/system.h>
-#include <validation.h>
namespace node {
CacheSizes CalculateCacheSizes(const ArgsManager& args, size_t n_indexes)
diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp
index ad9293f172..60acb614b4 100644
--- a/src/node/chainstate.cpp
+++ b/src/node/chainstate.cpp
@@ -4,9 +4,11 @@
#include <node/chainstate.h>
+#include <arith_uint256.h>
#include <chain.h>
#include <coins.h>
#include <consensus/params.h>
+#include <logging.h>
#include <node/blockstorage.h>
#include <node/caches.h>
#include <sync.h>
@@ -21,6 +23,7 @@
#include <algorithm>
#include <atomic>
#include <cassert>
+#include <limits>
#include <memory>
#include <vector>
@@ -28,15 +31,35 @@ namespace node {
ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
const ChainstateLoadOptions& options)
{
- auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
+ auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull();
};
+ if (!chainman.AssumedValidBlock().IsNull()) {
+ LogPrintf("Assuming ancestors of block %s have valid signatures.\n", chainman.AssumedValidBlock().GetHex());
+ } else {
+ LogPrintf("Validating signatures for all blocks.\n");
+ }
+ LogPrintf("Setting nMinimumChainWork=%s\n", chainman.MinimumChainWork().GetHex());
+ if (chainman.MinimumChainWork() < UintToArith256(chainman.GetConsensus().nMinimumChainWork)) {
+ LogPrintf("Warning: nMinimumChainWork set below default value of %s\n", chainman.GetConsensus().nMinimumChainWork.GetHex());
+ }
+ if (nPruneTarget == std::numeric_limits<uint64_t>::max()) {
+ LogPrintf("Block pruning enabled. Use RPC call pruneblockchain(height) to manually prune block and undo files.\n");
+ } else if (nPruneTarget) {
+ LogPrintf("Prune configured to target %u MiB on disk for block and undo files.\n", nPruneTarget / 1024 / 1024);
+ }
+
LOCK(cs_main);
- chainman.InitializeChainstate(options.mempool);
chainman.m_total_coinstip_cache = cache_sizes.coins;
chainman.m_total_coinsdb_cache = cache_sizes.coins_db;
+ // Load the fully validated chainstate.
+ chainman.InitializeChainstate(options.mempool);
+
+ // Load a chain created from a UTXO snapshot, if any exist.
+ chainman.DetectSnapshotChainstate(options.mempool);
+
auto& pblocktree{chainman.m_blockman.m_block_tree_db};
// new CBlockTreeDB tries to delete the existing file, which
// fails if it's still open from the previous loop. Close it first:
@@ -83,12 +106,20 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")};
}
+ // Conservative value which is arbitrarily chosen, as it will ultimately be changed
+ // by a call to `chainman.MaybeRebalanceCaches()`. We just need to make sure
+ // that the sum of the two caches (40%) does not exceed the allowable amount
+ // during this temporary initialization state.
+ double init_cache_fraction = 0.2;
+
// At this point we're either in reindex or we've loaded a useful
// block tree into BlockIndex()!
- for (CChainState* chainstate : chainman.GetAll()) {
+ for (Chainstate* chainstate : chainman.GetAll()) {
+ LogPrintf("Initializing chainstate %s\n", chainstate->ToString());
+
chainstate->InitCoinsDB(
- /*cache_size_bytes=*/cache_sizes.coins_db,
+ /*cache_size_bytes=*/chainman.m_total_coinsdb_cache * init_cache_fraction,
/*in_memory=*/options.coins_db_in_memory,
/*should_wipe=*/options.reindex || options.reindex_chainstate);
@@ -110,7 +141,7 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
}
// The on-disk coinsdb is now in a good state, create the cache
- chainstate->InitCoinsCache(cache_sizes.coins);
+ chainstate->InitCoinsCache(chainman.m_total_coinstip_cache * init_cache_fraction);
assert(chainstate->CanFlushToDisk());
if (!is_coinsview_empty(chainstate)) {
@@ -125,24 +156,29 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
if (!options.reindex) {
auto chainstates{chainman.GetAll()};
if (std::any_of(chainstates.begin(), chainstates.end(),
- [](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) {
+ [](const Chainstate* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) {
return {ChainstateLoadStatus::FAILURE, strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."),
chainman.GetConsensus().SegwitHeight)};
};
}
+ // Now that chainstates are loaded and we're able to flush to
+ // disk, rebalance the coins caches to desired levels based
+ // on the condition of each chainstate.
+ chainman.MaybeRebalanceCaches();
+
return {ChainstateLoadStatus::SUCCESS, {}};
}
ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options)
{
- auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
+ auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull();
};
LOCK(cs_main);
- for (CChainState* chainstate : chainman.GetAll()) {
+ for (Chainstate* chainstate : chainman.GetAll()) {
if (!is_coinsview_empty(chainstate)) {
const CBlockIndex* tip = chainstate->m_chain.Tip();
if (tip && tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) {
diff --git a/src/node/chainstatemanager_args.cpp b/src/node/chainstatemanager_args.cpp
new file mode 100644
index 0000000000..b0d929626b
--- /dev/null
+++ b/src/node/chainstatemanager_args.cpp
@@ -0,0 +1,39 @@
+// Copyright (c) 2022 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/chainstatemanager_args.h>
+
+#include <arith_uint256.h>
+#include <tinyformat.h>
+#include <uint256.h>
+#include <util/strencodings.h>
+#include <util/system.h>
+#include <util/translation.h>
+#include <validation.h>
+
+#include <chrono>
+#include <optional>
+#include <string>
+
+namespace node {
+std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, ChainstateManager::Options& opts)
+{
+ if (auto value{args.GetBoolArg("-checkblockindex")}) opts.check_block_index = *value;
+
+ if (auto value{args.GetBoolArg("-checkpoints")}) opts.checkpoints_enabled = *value;
+
+ if (auto value{args.GetArg("-minimumchainwork")}) {
+ if (!IsHexNumber(*value)) {
+ return strprintf(Untranslated("Invalid non-hex (%s) minimum chain work value specified"), *value);
+ }
+ opts.minimum_chain_work = UintToArith256(uint256S(*value));
+ }
+
+ if (auto value{args.GetArg("-assumevalid")}) opts.assumed_valid_block = uint256S(*value);
+
+ if (auto value{args.GetIntArg("-maxtipage")}) opts.max_tip_age = std::chrono::seconds{*value};
+
+ return std::nullopt;
+}
+} // namespace node
diff --git a/src/node/chainstatemanager_args.h b/src/node/chainstatemanager_args.h
new file mode 100644
index 0000000000..6c46b998f2
--- /dev/null
+++ b/src/node/chainstatemanager_args.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2022 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_CHAINSTATEMANAGER_ARGS_H
+#define BITCOIN_NODE_CHAINSTATEMANAGER_ARGS_H
+
+#include <validation.h>
+
+#include <optional>
+
+class ArgsManager;
+struct bilingual_str;
+
+namespace node {
+std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, ChainstateManager::Options& opts);
+} // namespace node
+
+#endif // BITCOIN_NODE_CHAINSTATEMANAGER_ARGS_H
diff --git a/src/node/interface_ui.cpp b/src/node/interface_ui.cpp
index 370cde84f8..fa90d6fda7 100644
--- a/src/node/interface_ui.cpp
+++ b/src/node/interface_ui.cpp
@@ -53,7 +53,7 @@ void CClientUIInterface::NotifyNetworkActiveChanged(bool networkActive) { return
void CClientUIInterface::NotifyAlertChanged() { return g_ui_signals.NotifyAlertChanged(); }
void CClientUIInterface::ShowProgress(const std::string& title, int nProgress, bool resume_possible) { return g_ui_signals.ShowProgress(title, nProgress, resume_possible); }
void CClientUIInterface::NotifyBlockTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyBlockTip(s, i); }
-void CClientUIInterface::NotifyHeaderTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyHeaderTip(s, i); }
+void CClientUIInterface::NotifyHeaderTip(SynchronizationState s, int64_t height, int64_t timestamp, bool presync) { return g_ui_signals.NotifyHeaderTip(s, height, timestamp, presync); }
void CClientUIInterface::BannedListChanged() { return g_ui_signals.BannedListChanged(); }
bool InitError(const bilingual_str& str)
diff --git a/src/node/interface_ui.h b/src/node/interface_ui.h
index 37c0f6392b..316d75167e 100644
--- a/src/node/interface_ui.h
+++ b/src/node/interface_ui.h
@@ -105,7 +105,7 @@ public:
ADD_SIGNALS_DECL_WRAPPER(NotifyBlockTip, void, SynchronizationState, const CBlockIndex*);
/** Best header has changed */
- ADD_SIGNALS_DECL_WRAPPER(NotifyHeaderTip, void, SynchronizationState, const CBlockIndex*);
+ ADD_SIGNALS_DECL_WRAPPER(NotifyHeaderTip, void, SynchronizationState, int64_t height, int64_t timestamp, bool presync);
/** Banlist did change. */
ADD_SIGNALS_DECL_WRAPPER(BannedListChanged, void, void);
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index 699dca0a73..979c625463 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -4,10 +4,12 @@
#include <addrdb.h>
#include <banman.h>
+#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
#include <deploymentstatus.h>
#include <external_signer.h>
+#include <index/blockfilterindex.h>
#include <init.h>
#include <interfaces/chain.h>
#include <interfaces/handler.h>
@@ -301,7 +303,11 @@ public:
}
}
bool getNetworkActive() override { return m_context->connman && m_context->connman->GetNetworkActive(); }
- CFeeRate getDustRelayFee() override { return ::dustRelayFee; }
+ CFeeRate getDustRelayFee() override
+ {
+ if (!m_context->mempool) return CFeeRate{DUST_RELAY_TX_FEE};
+ return m_context->mempool->m_dust_relay_feerate;
+ }
UniValue executeRpc(const std::string& command, const UniValue& params, const std::string& uri) override
{
JSONRPCRequest req;
@@ -373,9 +379,8 @@ public:
std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) override
{
return MakeHandler(
- ::uiInterface.NotifyHeaderTip_connect([fn](SynchronizationState sync_state, const CBlockIndex* block) {
- fn(sync_state, BlockTip{block->nHeight, block->GetBlockTime(), block->GetBlockHash()},
- /* verification progress is unused when a header was received */ 0);
+ ::uiInterface.NotifyHeaderTip_connect([fn](SynchronizationState sync_state, int64_t height, int64_t timestamp, bool presync) {
+ fn(sync_state, BlockTip{(int)height, timestamp, uint256{}}, presync);
}));
}
NodeContext* context() override { return m_context; }
@@ -396,7 +401,7 @@ bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<Rec
if (block.m_max_time) *block.m_max_time = index->GetBlockTimeMax();
if (block.m_mtp_time) *block.m_mtp_time = index->GetMedianTimePast();
if (block.m_in_active_chain) *block.m_in_active_chain = active[index->nHeight] == index;
- if (block.m_locator) { *block.m_locator = active.GetLocator(index); }
+ if (block.m_locator) { *block.m_locator = GetLocator(index); }
if (block.m_next_block) FillBlock(active[index->nHeight] == index ? active[index->nHeight + 1] : nullptr, *block.m_next_block, lock, active);
if (block.m_data) {
REVERSE_LOCK(lock);
@@ -523,8 +528,7 @@ public:
{
LOCK(::cs_main);
const CBlockIndex* index = chainman().m_blockman.LookupBlockIndex(block_hash);
- if (!index) return {};
- return chainman().ActiveChain().GetLocator(index);
+ return GetLocator(index);
}
std::optional<int> findLocatorFork(const CBlockLocator& locator) override
{
@@ -534,6 +538,20 @@ public:
}
return std::nullopt;
}
+ bool hasBlockFilterIndex(BlockFilterType filter_type) override
+ {
+ return GetBlockFilterIndex(filter_type) != nullptr;
+ }
+ std::optional<bool> blockFilterMatchesAny(BlockFilterType filter_type, const uint256& block_hash, const GCSFilter::ElementSet& filter_set) override
+ {
+ const BlockFilterIndex* block_filter_index{GetBlockFilterIndex(filter_type)};
+ if (!block_filter_index) return std::nullopt;
+
+ BlockFilter filter;
+ const CBlockIndex* index{WITH_LOCK(::cs_main, return chainman().m_blockman.LookupBlockIndex(block_hash))};
+ if (index == nullptr || !block_filter_index->LookupFilter(index, filter)) return std::nullopt;
+ return filter.GetFilter().MatchAny(filter_set);
+ }
bool findBlock(const uint256& hash, const FoundBlock& block) override
{
WAIT_LOCK(cs_main, lock);
@@ -658,8 +676,7 @@ public:
std::string unused_error_string;
LOCK(m_node.mempool->cs);
return m_node.mempool->CalculateMemPoolAncestors(
- entry, ancestors, limits.ancestor_count, limits.ancestor_size_vbytes,
- limits.descendant_count, limits.descendant_size_vbytes, unused_error_string);
+ entry, ancestors, limits, unused_error_string);
}
CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override
{
@@ -676,9 +693,21 @@ public:
if (!m_node.mempool) return {};
return m_node.mempool->GetMinFee();
}
- CFeeRate relayMinFee() override { return ::minRelayTxFee; }
- CFeeRate relayIncrementalFee() override { return ::incrementalRelayFee; }
- CFeeRate relayDustFee() override { return ::dustRelayFee; }
+ CFeeRate relayMinFee() override
+ {
+ if (!m_node.mempool) return CFeeRate{DEFAULT_MIN_RELAY_TX_FEE};
+ return m_node.mempool->m_min_relay_feerate;
+ }
+ CFeeRate relayIncrementalFee() override
+ {
+ if (!m_node.mempool) return CFeeRate{DEFAULT_INCREMENTAL_RELAY_FEE};
+ return m_node.mempool->m_incremental_relay_feerate;
+ }
+ CFeeRate relayDustFee() override
+ {
+ if (!m_node.mempool) return CFeeRate{DUST_RELAY_TX_FEE};
+ return m_node.mempool->m_dust_relay_feerate;
+ }
bool havePruned() override
{
LOCK(::cs_main);
diff --git a/src/node/mempool_args.cpp b/src/node/mempool_args.cpp
new file mode 100644
index 0000000000..8c929e5e0d
--- /dev/null
+++ b/src/node/mempool_args.cpp
@@ -0,0 +1,100 @@
+// Copyright (c) 2022 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/mempool_args.h>
+
+#include <kernel/mempool_limits.h>
+#include <kernel/mempool_options.h>
+
+#include <chainparams.h>
+#include <consensus/amount.h>
+#include <logging.h>
+#include <policy/feerate.h>
+#include <policy/policy.h>
+#include <script/standard.h>
+#include <tinyformat.h>
+#include <util/error.h>
+#include <util/moneystr.h>
+#include <util/system.h>
+#include <util/translation.h>
+
+#include <chrono>
+#include <memory>
+
+using kernel::MemPoolLimits;
+using kernel::MemPoolOptions;
+
+namespace {
+void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolLimits& mempool_limits)
+{
+ mempool_limits.ancestor_count = argsman.GetIntArg("-limitancestorcount", mempool_limits.ancestor_count);
+
+ if (auto vkb = argsman.GetIntArg("-limitancestorsize")) mempool_limits.ancestor_size_vbytes = *vkb * 1'000;
+
+ mempool_limits.descendant_count = argsman.GetIntArg("-limitdescendantcount", mempool_limits.descendant_count);
+
+ if (auto vkb = argsman.GetIntArg("-limitdescendantsize")) mempool_limits.descendant_size_vbytes = *vkb * 1'000;
+}
+}
+
+std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& argsman, const CChainParams& chainparams, MemPoolOptions& mempool_opts)
+{
+ mempool_opts.check_ratio = argsman.GetIntArg("-checkmempool", mempool_opts.check_ratio);
+
+ if (auto mb = argsman.GetIntArg("-maxmempool")) mempool_opts.max_size_bytes = *mb * 1'000'000;
+
+ if (auto hours = argsman.GetIntArg("-mempoolexpiry")) mempool_opts.expiry = std::chrono::hours{*hours};
+
+ // incremental relay fee sets the minimum feerate increase necessary for replacement in the mempool
+ // and the amount the mempool min fee increases above the feerate of txs evicted due to mempool limiting.
+ if (argsman.IsArgSet("-incrementalrelayfee")) {
+ if (std::optional<CAmount> inc_relay_fee = ParseMoney(argsman.GetArg("-incrementalrelayfee", ""))) {
+ mempool_opts.incremental_relay_feerate = CFeeRate{inc_relay_fee.value()};
+ } else {
+ return AmountErrMsg("incrementalrelayfee", argsman.GetArg("-incrementalrelayfee", ""));
+ }
+ }
+
+ if (argsman.IsArgSet("-minrelaytxfee")) {
+ if (std::optional<CAmount> min_relay_feerate = ParseMoney(argsman.GetArg("-minrelaytxfee", ""))) {
+ // High fee check is done afterward in CWallet::Create()
+ mempool_opts.min_relay_feerate = CFeeRate{min_relay_feerate.value()};
+ } else {
+ return AmountErrMsg("minrelaytxfee", argsman.GetArg("-minrelaytxfee", ""));
+ }
+ } else if (mempool_opts.incremental_relay_feerate > mempool_opts.min_relay_feerate) {
+ // Allow only setting incremental fee to control both
+ mempool_opts.min_relay_feerate = mempool_opts.incremental_relay_feerate;
+ LogPrintf("Increasing minrelaytxfee to %s to match incrementalrelayfee\n", mempool_opts.min_relay_feerate.ToString());
+ }
+
+ // Feerate used to define dust. Shouldn't be changed lightly as old
+ // implementations may inadvertently create non-standard transactions
+ if (argsman.IsArgSet("-dustrelayfee")) {
+ if (std::optional<CAmount> parsed = ParseMoney(argsman.GetArg("-dustrelayfee", ""))) {
+ mempool_opts.dust_relay_feerate = CFeeRate{parsed.value()};
+ } else {
+ return AmountErrMsg("dustrelayfee", argsman.GetArg("-dustrelayfee", ""));
+ }
+ }
+
+ mempool_opts.permit_bare_multisig = argsman.GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG);
+
+ if (argsman.GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER)) {
+ mempool_opts.max_datacarrier_bytes = argsman.GetIntArg("-datacarriersize", MAX_OP_RETURN_RELAY);
+ } else {
+ mempool_opts.max_datacarrier_bytes = std::nullopt;
+ }
+
+ mempool_opts.require_standard = !argsman.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());
+ if (!chainparams.IsTestChain() && !mempool_opts.require_standard) {
+ return strprintf(Untranslated("acceptnonstdtxn is not currently supported for %s chain"), chainparams.NetworkIDString());
+ }
+
+ mempool_opts.full_rbf = argsman.GetBoolArg("-mempoolfullrbf", mempool_opts.full_rbf);
+
+ ApplyArgsManOptions(argsman, mempool_opts.limits);
+
+ return std::nullopt;
+}
diff --git a/src/mempool_args.h b/src/node/mempool_args.h
index 9a4abe6618..52d8b4f265 100644
--- a/src/mempool_args.h
+++ b/src/node/mempool_args.h
@@ -2,21 +2,26 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#ifndef BITCOIN_MEMPOOL_ARGS_H
-#define BITCOIN_MEMPOOL_ARGS_H
+#ifndef BITCOIN_NODE_MEMPOOL_ARGS_H
+#define BITCOIN_NODE_MEMPOOL_ARGS_H
+
+#include <optional>
class ArgsManager;
+class CChainParams;
+struct bilingual_str;
namespace kernel {
struct MemPoolOptions;
};
/**
* Overlay the options set in \p argsman on top of corresponding members in \p mempool_opts.
+ * Returns an error if one was encountered.
*
* @param[in] argsman The ArgsManager in which to check set options.
* @param[in,out] mempool_opts The MemPoolOptions to modify according to \p argsman.
*/
-void ApplyArgsManOptions(const ArgsManager& argsman, kernel::MemPoolOptions& mempool_opts);
+[[nodiscard]] std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& argsman, const CChainParams& chainparams, kernel::MemPoolOptions& mempool_opts);
-#endif // BITCOIN_MEMPOOL_ARGS_H
+#endif // BITCOIN_NODE_MEMPOOL_ARGS_H
diff --git a/src/node/miner.cpp b/src/node/miner.cpp
index 9db10feae4..e11ec5b0f1 100644
--- a/src/node/miner.cpp
+++ b/src/node/miner.cpp
@@ -30,7 +30,7 @@ namespace node {
int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev)
{
int64_t nOldTime = pblock->nTime;
- int64_t nNewTime = std::max(pindexPrev->GetMedianTimePast() + 1, GetAdjustedTime());
+ int64_t nNewTime{std::max<int64_t>(pindexPrev->GetMedianTimePast() + 1, TicksSinceEpoch<std::chrono::seconds>(GetAdjustedTime()))};
if (nOldTime < nNewTime) {
pblock->nTime = nNewTime;
@@ -62,7 +62,7 @@ BlockAssembler::Options::Options()
nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT;
}
-BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool* mempool, const Options& options)
+BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options)
: chainparams{chainstate.m_chainman.GetParams()},
m_mempool(mempool),
m_chainstate(chainstate)
@@ -87,7 +87,7 @@ static BlockAssembler::Options DefaultOptions()
return options;
}
-BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool* mempool)
+BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool)
: BlockAssembler(chainstate, mempool, DefaultOptions()) {}
void BlockAssembler::resetBlock()
@@ -105,7 +105,7 @@ void BlockAssembler::resetBlock()
std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn)
{
- int64_t nTimeStart = GetTimeMicros();
+ const auto time_start{SteadyClock::now()};
resetBlock();
@@ -133,7 +133,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
pblock->nVersion = gArgs.GetIntArg("-blockversion", pblock->nVersion);
}
- pblock->nTime = GetAdjustedTime();
+ pblock->nTime = TicksSinceEpoch<std::chrono::seconds>(GetAdjustedTime());
m_lock_time_cutoff = pindexPrev->GetMedianTimePast();
int nPackagesSelected = 0;
@@ -143,7 +143,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
addPackageTxs(*m_mempool, nPackagesSelected, nDescendantsUpdated);
}
- int64_t nTime1 = GetTimeMicros();
+ const auto time_1{SteadyClock::now()};
m_last_block_num_txs = nBlockTx;
m_last_block_weight = nBlockWeight;
@@ -173,9 +173,12 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
if (!TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev, GetAdjustedTime, false, false)) {
throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString()));
}
- int64_t nTime2 = GetTimeMicros();
+ const auto time_2{SteadyClock::now()};
- LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n", 0.001 * (nTime1 - nTimeStart), nPackagesSelected, nDescendantsUpdated, 0.001 * (nTime2 - nTime1), 0.001 * (nTime2 - nTimeStart));
+ LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n",
+ Ticks<MillisecondsDouble>(time_1 - time_start), nPackagesSelected, nDescendantsUpdated,
+ Ticks<MillisecondsDouble>(time_2 - time_1),
+ Ticks<MillisecondsDouble>(time_2 - time_start));
return std::move(pblocktemplate);
}
@@ -257,13 +260,9 @@ static int UpdatePackagesForAdded(const CTxMemPool& mempool,
modtxiter mit = mapModifiedTx.find(desc);
if (mit == mapModifiedTx.end()) {
CTxMemPoolModifiedEntry modEntry(desc);
- modEntry.nSizeWithAncestors -= it->GetTxSize();
- modEntry.nModFeesWithAncestors -= it->GetModifiedFee();
- modEntry.nSigOpCostWithAncestors -= it->GetSigOpCost();
- mapModifiedTx.insert(modEntry);
- } else {
- mapModifiedTx.modify(mit, update_for_parent_inclusion(it));
+ mit = mapModifiedTx.insert(modEntry).first;
}
+ mapModifiedTx.modify(mit, update_for_parent_inclusion(it));
}
}
return nDescendantsUpdated;
@@ -396,9 +395,8 @@ void BlockAssembler::addPackageTxs(const CTxMemPool& mempool, int& nPackagesSele
}
CTxMemPool::setEntries ancestors;
- uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
std::string dummy;
- mempool.CalculateMemPoolAncestors(*iter, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false);
+ mempool.CalculateMemPoolAncestors(*iter, ancestors, CTxMemPool::Limits::NoLimits(), dummy, false);
onlyUnconfirmed(ancestors);
ancestors.insert(iter);
diff --git a/src/node/miner.h b/src/node/miner.h
index 26454df3df..7269ce1186 100644
--- a/src/node/miner.h
+++ b/src/node/miner.h
@@ -148,7 +148,7 @@ private:
const CChainParams& chainparams;
const CTxMemPool* const m_mempool;
- CChainState& m_chainstate;
+ Chainstate& m_chainstate;
public:
struct Options {
@@ -157,8 +157,8 @@ public:
CFeeRate blockMinFeeRate;
};
- explicit BlockAssembler(CChainState& chainstate, const CTxMemPool* mempool);
- explicit BlockAssembler(CChainState& chainstate, const CTxMemPool* mempool, const Options& options);
+ explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool);
+ explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options);
/** Construct a new block template with coinbase to scriptPubKeyIn */
std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn);
diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp
index 5a932f435d..ca3fc0955d 100644
--- a/src/node/psbt.cpp
+++ b/src/node/psbt.cpp
@@ -59,7 +59,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
}
// Check if it is final
- if (!utxo.IsNull() && !PSBTInputSigned(input)) {
+ if (!PSBTInputSignedAndVerified(psbtx, i, &txdata)) {
input_analysis.is_final = false;
// Figure out what is missing
@@ -137,7 +137,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
if (success) {
CTransaction ctx = CTransaction(mtx);
- size_t size = GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS));
+ size_t size(GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS), ::nBytesPerSigOp));
result.estimated_vsize = size;
// Estimate fee rate
CFeeRate feerate(fee, size);
diff --git a/src/node/txreconciliation.cpp b/src/node/txreconciliation.cpp
new file mode 100644
index 0000000000..974358fcda
--- /dev/null
+++ b/src/node/txreconciliation.cpp
@@ -0,0 +1,184 @@
+// Copyright (c) 2022 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/txreconciliation.h>
+
+#include <util/check.h>
+#include <util/system.h>
+
+#include <unordered_map>
+#include <variant>
+
+
+namespace {
+
+/** Static salt component used to compute short txids for sketch construction, see BIP-330. */
+const std::string RECON_STATIC_SALT = "Tx Relay Salting";
+const HashWriter RECON_SALT_HASHER = TaggedHash(RECON_STATIC_SALT);
+
+/**
+ * Salt (specified by BIP-330) constructed from contributions from both peers. It is used
+ * to compute transaction short IDs, which are then used to construct a sketch representing a set
+ * of transactions we want to announce to the peer.
+ */
+uint256 ComputeSalt(uint64_t salt1, uint64_t salt2)
+{
+ // According to BIP-330, salts should be combined in ascending order.
+ return (HashWriter(RECON_SALT_HASHER) << std::min(salt1, salt2) << std::max(salt1, salt2)).GetSHA256();
+}
+
+/**
+ * Keeps track of txreconciliation-related per-peer state.
+ */
+class TxReconciliationState
+{
+public:
+ /**
+ * TODO: This field is public to ignore -Wunused-private-field. Make private once used in
+ * the following commits.
+ *
+ * Reconciliation protocol assumes using one role consistently: either a reconciliation
+ * initiator (requesting sketches), or responder (sending sketches). This defines our role.
+ *
+ */
+ bool m_we_initiate;
+
+ /**
+ * TODO: These fields are public to ignore -Wunused-private-field. Make private once used in
+ * the following commits.
+ *
+ * These values are used to salt short IDs, which is necessary for transaction reconciliations.
+ */
+ uint64_t m_k0, m_k1;
+
+ TxReconciliationState(bool we_initiate, uint64_t k0, uint64_t k1) : m_we_initiate(we_initiate), m_k0(k0), m_k1(k1) {}
+};
+
+} // namespace
+
+/** Actual implementation for TxReconciliationTracker's data structure. */
+class TxReconciliationTracker::Impl
+{
+private:
+ mutable Mutex m_txreconciliation_mutex;
+
+ // Local protocol version
+ uint32_t m_recon_version;
+
+ /**
+ * Keeps track of txreconciliation states of eligible peers.
+ * For pre-registered peers, the locally generated salt is stored.
+ * For registered peers, the locally generated salt is forgotten, and the state (including
+ * "full" salt) is stored instead.
+ */
+ std::unordered_map<NodeId, std::variant<uint64_t, TxReconciliationState>> m_states GUARDED_BY(m_txreconciliation_mutex);
+
+public:
+ explicit Impl(uint32_t recon_version) : m_recon_version(recon_version) {}
+
+ uint64_t PreRegisterPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
+ {
+ AssertLockNotHeld(m_txreconciliation_mutex);
+ LOCK(m_txreconciliation_mutex);
+ // We do not support txreconciliation salt/version updates.
+ assert(m_states.find(peer_id) == m_states.end());
+
+ LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Pre-register peer=%d\n", peer_id);
+ const uint64_t local_salt{GetRand(UINT64_MAX)};
+
+ // We do this exactly once per peer (which are unique by NodeId, see GetNewNodeId) so it's
+ // safe to assume we don't have this record yet.
+ Assert(m_states.emplace(peer_id, local_salt).second);
+ return local_salt;
+ }
+
+ ReconciliationRegisterResult RegisterPeer(NodeId peer_id, bool is_peer_inbound, bool is_peer_recon_initiator,
+ bool is_peer_recon_responder, uint32_t peer_recon_version,
+ uint64_t remote_salt) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
+ {
+ AssertLockNotHeld(m_txreconciliation_mutex);
+ LOCK(m_txreconciliation_mutex);
+ auto recon_state = m_states.find(peer_id);
+
+ // A peer should be in the pre-registered state to proceed here.
+ if (recon_state == m_states.end()) return NOT_FOUND;
+ uint64_t* local_salt = std::get_if<uint64_t>(&recon_state->second);
+ // A peer is already registered. This should be checked by the caller.
+ Assume(local_salt);
+
+ // If the peer supports the version which is lower than ours, we downgrade to the version
+ // it supports. For now, this only guarantees that nodes with future reconciliation
+ // versions have the choice of reconciling with this current version. However, they also
+ // have the choice to refuse supporting reconciliations if the common version is not
+ // satisfactory (e.g. too low).
+ const uint32_t recon_version{std::min(peer_recon_version, m_recon_version)};
+ // v1 is the lowest version, so suggesting something below must be a protocol violation.
+ if (recon_version < 1) return PROTOCOL_VIOLATION;
+
+ // Must match SENDTXRCNCL logic.
+ const bool they_initiate = is_peer_recon_initiator && is_peer_inbound;
+ const bool we_initiate = !is_peer_inbound && is_peer_recon_responder;
+
+ // If we ever announce support for both requesting and responding, this will need
+ // tie-breaking. For now, this is mutually exclusive because both are based on the
+ // inbound flag.
+ assert(!(they_initiate && we_initiate));
+
+ // The peer set both flags to false, we treat it as a protocol violation.
+ if (!(they_initiate || we_initiate)) return PROTOCOL_VIOLATION;
+
+ LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Register peer=%d with the following params: " /* Continued */
+ "we_initiate=%i, they_initiate=%i.\n",
+ peer_id, we_initiate, they_initiate);
+
+ const uint256 full_salt{ComputeSalt(*local_salt, remote_salt)};
+ recon_state->second = TxReconciliationState(we_initiate, full_salt.GetUint64(0), full_salt.GetUint64(1));
+ return SUCCESS;
+ }
+
+ void ForgetPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
+ {
+ AssertLockNotHeld(m_txreconciliation_mutex);
+ LOCK(m_txreconciliation_mutex);
+ if (m_states.erase(peer_id)) {
+ LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Forget txreconciliation state of peer=%d\n", peer_id);
+ }
+ }
+
+ bool IsPeerRegistered(NodeId peer_id) const EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
+ {
+ AssertLockNotHeld(m_txreconciliation_mutex);
+ LOCK(m_txreconciliation_mutex);
+ auto recon_state = m_states.find(peer_id);
+ return (recon_state != m_states.end() &&
+ std::holds_alternative<TxReconciliationState>(recon_state->second));
+ }
+};
+
+TxReconciliationTracker::TxReconciliationTracker(uint32_t recon_version) : m_impl{std::make_unique<TxReconciliationTracker::Impl>(recon_version)} {}
+
+TxReconciliationTracker::~TxReconciliationTracker() = default;
+
+uint64_t TxReconciliationTracker::PreRegisterPeer(NodeId peer_id)
+{
+ return m_impl->PreRegisterPeer(peer_id);
+}
+
+ReconciliationRegisterResult TxReconciliationTracker::RegisterPeer(NodeId peer_id, bool is_peer_inbound,
+ bool is_peer_recon_initiator, bool is_peer_recon_responder,
+ uint32_t peer_recon_version, uint64_t remote_salt)
+{
+ return m_impl->RegisterPeer(peer_id, is_peer_inbound, is_peer_recon_initiator, is_peer_recon_responder,
+ peer_recon_version, remote_salt);
+}
+
+void TxReconciliationTracker::ForgetPeer(NodeId peer_id)
+{
+ m_impl->ForgetPeer(peer_id);
+}
+
+bool TxReconciliationTracker::IsPeerRegistered(NodeId peer_id) const
+{
+ return m_impl->IsPeerRegistered(peer_id);
+}
diff --git a/src/node/txreconciliation.h b/src/node/txreconciliation.h
new file mode 100644
index 0000000000..a4f0870914
--- /dev/null
+++ b/src/node/txreconciliation.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2022 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_TXRECONCILIATION_H
+#define BITCOIN_NODE_TXRECONCILIATION_H
+
+#include <net.h>
+#include <sync.h>
+
+#include <memory>
+#include <tuple>
+
+/** Whether transaction reconciliation protocol should be enabled by default. */
+static constexpr bool DEFAULT_TXRECONCILIATION_ENABLE{false};
+/** Supported transaction reconciliation protocol version */
+static constexpr uint32_t TXRECONCILIATION_VERSION{1};
+
+enum ReconciliationRegisterResult {
+ NOT_FOUND = 0,
+ SUCCESS = 1,
+ PROTOCOL_VIOLATION = 2,
+};
+
+/**
+ * Transaction reconciliation is a way for nodes to efficiently announce transactions.
+ * This object keeps track of all txreconciliation-related communications with the peers.
+ * The high-level protocol is:
+ * 0. Txreconciliation protocol handshake.
+ * 1. Once we receive a new transaction, add it to the set instead of announcing immediately.
+ * 2. At regular intervals, a txreconciliation initiator requests a sketch from a peer, where a
+ * sketch is a compressed representation of short form IDs of the transactions in their set.
+ * 3. Once the initiator received a sketch from the peer, the initiator computes a local sketch,
+ * and combines the two sketches to attempt finding the difference in *sets*.
+ * 4a. If the difference was not larger than estimated, see SUCCESS below.
+ * 4b. If the difference was larger than estimated, initial txreconciliation fails. The initiator
+ * requests a larger sketch via an extension round (allowed only once).
+ * - If extension succeeds (a larger sketch is sufficient), see SUCCESS below.
+ * - If extension fails (a larger sketch is insufficient), see FAILURE below.
+ *
+ * SUCCESS. The initiator knows full symmetrical difference and can request what the initiator is
+ * missing and announce to the peer what the peer is missing.
+ *
+ * FAILURE. The initiator notifies the peer about the failure and announces all transactions from
+ * the corresponding set. Once the peer received the failure notification, the peer
+ * announces all transactions from their set.
+
+ * This is a modification of the Erlay protocol (https://arxiv.org/abs/1905.10518) with two
+ * changes (sketch extensions instead of bisections, and an extra INV exchange round), both
+ * are motivated in BIP-330.
+ */
+class TxReconciliationTracker
+{
+private:
+ class Impl;
+ const std::unique_ptr<Impl> m_impl;
+
+public:
+ explicit TxReconciliationTracker(uint32_t recon_version);
+ ~TxReconciliationTracker();
+
+ /**
+ * Step 0. Generates initial part of the state (salt) required to reconcile txs with the peer.
+ * The salt is used for short ID computation required for txreconciliation.
+ * The function returns the salt.
+ * A peer can't participate in future txreconciliations without this call.
+ * This function must be called only once per peer.
+ */
+ uint64_t PreRegisterPeer(NodeId peer_id);
+
+ /**
+ * Step 0. Once the peer agreed to reconcile txs with us, generate the state required to track
+ * ongoing reconciliations. Must be called only after pre-registering the peer and only once.
+ */
+ ReconciliationRegisterResult RegisterPeer(NodeId peer_id, bool is_peer_inbound, bool is_peer_recon_initiator,
+ bool is_peer_recon_responder, uint32_t peer_recon_version, uint64_t remote_salt);
+
+ /**
+ * Attempts to forget txreconciliation-related state of the peer (if we previously stored any).
+ * After this, we won't be able to reconcile transactions with the peer.
+ */
+ void ForgetPeer(NodeId peer_id);
+
+ /**
+ * Check if a peer is registered to reconcile transactions with us.
+ */
+ bool IsPeerRegistered(NodeId peer_id) const;
+};
+
+#endif // BITCOIN_NODE_TXRECONCILIATION_H
diff --git a/src/node/utxo_snapshot.cpp b/src/node/utxo_snapshot.cpp
new file mode 100644
index 0000000000..bab1b75211
--- /dev/null
+++ b/src/node/utxo_snapshot.cpp
@@ -0,0 +1,91 @@
+// Copyright (c) 2022 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/utxo_snapshot.h>
+
+#include <fs.h>
+#include <logging.h>
+#include <streams.h>
+#include <uint256.h>
+#include <util/system.h>
+#include <validation.h>
+
+#include <cstdio>
+#include <optional>
+
+namespace node {
+
+bool WriteSnapshotBaseBlockhash(Chainstate& snapshot_chainstate)
+{
+ AssertLockHeld(::cs_main);
+ assert(snapshot_chainstate.m_from_snapshot_blockhash);
+
+ const std::optional<fs::path> chaindir = snapshot_chainstate.CoinsDB().StoragePath();
+ assert(chaindir); // Sanity check that chainstate isn't in-memory.
+ const fs::path write_to = *chaindir / node::SNAPSHOT_BLOCKHASH_FILENAME;
+
+ FILE* file{fsbridge::fopen(write_to, "wb")};
+ AutoFile afile{file};
+ if (afile.IsNull()) {
+ LogPrintf("[snapshot] failed to open base blockhash file for writing: %s\n",
+ fs::PathToString(write_to));
+ return false;
+ }
+ afile << *snapshot_chainstate.m_from_snapshot_blockhash;
+
+ if (afile.fclose() != 0) {
+ LogPrintf("[snapshot] failed to close base blockhash file %s after writing\n",
+ fs::PathToString(write_to));
+ return false;
+ }
+ return true;
+}
+
+std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir)
+{
+ if (!fs::exists(chaindir)) {
+ LogPrintf("[snapshot] cannot read base blockhash: no chainstate dir " /* Continued */
+ "exists at path %s\n", fs::PathToString(chaindir));
+ return std::nullopt;
+ }
+ const fs::path read_from = chaindir / node::SNAPSHOT_BLOCKHASH_FILENAME;
+ const std::string read_from_str = fs::PathToString(read_from);
+
+ if (!fs::exists(read_from)) {
+ LogPrintf("[snapshot] snapshot chainstate dir is malformed! no base blockhash file " /* Continued */
+ "exists at path %s. Try deleting %s and calling loadtxoutset again?\n",
+ fs::PathToString(chaindir), read_from_str);
+ return std::nullopt;
+ }
+
+ uint256 base_blockhash;
+ FILE* file{fsbridge::fopen(read_from, "rb")};
+ AutoFile afile{file};
+ if (afile.IsNull()) {
+ LogPrintf("[snapshot] failed to open base blockhash file for reading: %s\n",
+ read_from_str);
+ return std::nullopt;
+ }
+ afile >> base_blockhash;
+
+ if (std::fgetc(afile.Get()) != EOF) {
+ LogPrintf("[snapshot] warning: unexpected trailing data in %s\n", read_from_str);
+ } else if (std::ferror(afile.Get())) {
+ LogPrintf("[snapshot] warning: i/o error reading %s\n", read_from_str);
+ }
+ return base_blockhash;
+}
+
+std::optional<fs::path> FindSnapshotChainstateDir()
+{
+ fs::path possible_dir =
+ gArgs.GetDataDirNet() / fs::u8path(strprintf("chainstate%s", SNAPSHOT_CHAINSTATE_SUFFIX));
+
+ if (fs::exists(possible_dir)) {
+ return possible_dir;
+ }
+ return std::nullopt;
+}
+
+} // namespace node
diff --git a/src/node/utxo_snapshot.h b/src/node/utxo_snapshot.h
index 401d4baaeb..c94521792f 100644
--- a/src/node/utxo_snapshot.h
+++ b/src/node/utxo_snapshot.h
@@ -6,12 +6,18 @@
#ifndef BITCOIN_NODE_UTXO_SNAPSHOT_H
#define BITCOIN_NODE_UTXO_SNAPSHOT_H
+#include <fs.h>
#include <uint256.h>
#include <serialize.h>
+#include <validation.h>
+
+#include <optional>
+
+extern RecursiveMutex cs_main;
namespace node {
//! Metadata describing a serialized version of a UTXO set from which an
-//! assumeutxo CChainState can be constructed.
+//! assumeutxo Chainstate can be constructed.
class SnapshotMetadata
{
public:
@@ -33,6 +39,33 @@ public:
SERIALIZE_METHODS(SnapshotMetadata, obj) { READWRITE(obj.m_base_blockhash, obj.m_coins_count); }
};
+
+//! The file in the snapshot chainstate dir which stores the base blockhash. This is
+//! needed to reconstruct snapshot chainstates on init.
+//!
+//! Because we only allow loading a single snapshot at a time, there will only be one
+//! chainstate directory with this filename present within it.
+const fs::path SNAPSHOT_BLOCKHASH_FILENAME{"base_blockhash"};
+
+//! Write out the blockhash of the snapshot base block that was used to construct
+//! this chainstate. This value is read in during subsequent initializations and
+//! used to reconstruct snapshot-based chainstates.
+bool WriteSnapshotBaseBlockhash(Chainstate& snapshot_chainstate)
+ EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
+//! Read the blockhash of the snapshot base block that was used to construct the
+//! chainstate.
+std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir)
+ EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
+//! Suffix appended to the chainstate (leveldb) dir when created based upon
+//! a snapshot.
+constexpr std::string_view SNAPSHOT_CHAINSTATE_SUFFIX = "_snapshot";
+
+
+//! Return a path to the snapshot-based chainstate dir, if one exists.
+std::optional<fs::path> FindSnapshotChainstateDir();
+
} // namespace node
#endif // BITCOIN_NODE_UTXO_SNAPSHOT_H
diff --git a/src/node/validation_cache_args.cpp b/src/node/validation_cache_args.cpp
new file mode 100644
index 0000000000..5ea0a8ca0a
--- /dev/null
+++ b/src/node/validation_cache_args.cpp
@@ -0,0 +1,34 @@
+// Copyright (c) 2022 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/validation_cache_args.h>
+
+#include <kernel/validation_cache_sizes.h>
+
+#include <util/system.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+
+using kernel::ValidationCacheSizes;
+
+namespace node {
+void ApplyArgsManOptions(const ArgsManager& argsman, ValidationCacheSizes& cache_sizes)
+{
+ if (auto max_size = argsman.GetIntArg("-maxsigcachesize")) {
+ // 1. When supplied with a max_size of 0, both InitSignatureCache and
+ // InitScriptExecutionCache create the minimum possible cache (2
+ // elements). Therefore, we can use 0 as a floor here.
+ // 2. Multiply first, divide after to avoid integer truncation.
+ size_t clamped_size_each = std::max<int64_t>(*max_size, 0) * (1 << 20) / 2;
+ cache_sizes = {
+ .signature_cache_bytes = clamped_size_each,
+ .script_execution_cache_bytes = clamped_size_each,
+ };
+ }
+}
+} // namespace node
diff --git a/src/node/validation_cache_args.h b/src/node/validation_cache_args.h
new file mode 100644
index 0000000000..f447c13b49
--- /dev/null
+++ b/src/node/validation_cache_args.h
@@ -0,0 +1,17 @@
+// Copyright (c) 2022 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_VALIDATION_CACHE_ARGS_H
+#define BITCOIN_NODE_VALIDATION_CACHE_ARGS_H
+
+class ArgsManager;
+namespace kernel {
+struct ValidationCacheSizes;
+};
+
+namespace node {
+void ApplyArgsManOptions(const ArgsManager& argsman, kernel::ValidationCacheSizes& cache_sizes);
+} // namespace node
+
+#endif // BITCOIN_NODE_VALIDATION_CACHE_ARGS_H
diff --git a/src/outputtype.cpp b/src/outputtype.cpp
index 19366295e6..9ab2902256 100644
--- a/src/outputtype.cpp
+++ b/src/outputtype.cpp
@@ -20,6 +20,7 @@ static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy";
static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit";
static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32";
static const std::string OUTPUT_TYPE_STRING_BECH32M = "bech32m";
+static const std::string OUTPUT_TYPE_STRING_UNKNOWN = "unknown";
std::optional<OutputType> ParseOutputType(const std::string& type)
{
@@ -31,6 +32,8 @@ std::optional<OutputType> ParseOutputType(const std::string& type)
return OutputType::BECH32;
} else if (type == OUTPUT_TYPE_STRING_BECH32M) {
return OutputType::BECH32M;
+ } else if (type == OUTPUT_TYPE_STRING_UNKNOWN) {
+ return OutputType::UNKNOWN;
}
return std::nullopt;
}
@@ -42,6 +45,7 @@ const std::string& FormatOutputType(OutputType type)
case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT;
case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32;
case OutputType::BECH32M: return OUTPUT_TYPE_STRING_BECH32M;
+ case OutputType::UNKNOWN: return OUTPUT_TYPE_STRING_UNKNOWN;
} // no default case, so the compiler can warn about missing cases
assert(false);
}
@@ -61,7 +65,8 @@ CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type)
return witdest;
}
}
- case OutputType::BECH32M: {} // This function should never be used with BECH32M, so let it assert
+ case OutputType::BECH32M:
+ case OutputType::UNKNOWN: {} // This function should never be used with BECH32M or UNKNOWN, so let it assert
} // no default case, so the compiler can warn about missing cases
assert(false);
}
@@ -91,8 +96,6 @@ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore,
case OutputType::BECH32: {
CTxDestination witdest = WitnessV0ScriptHash(script);
CScript witprog = GetScriptForDestination(witdest);
- // Check if the resulting program is solvable (i.e. doesn't use an uncompressed key)
- if (!IsSolvable(keystore, witprog)) return ScriptHash(script);
// Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours.
keystore.AddCScript(witprog);
if (type == OutputType::BECH32) {
@@ -101,7 +104,8 @@ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore,
return ScriptHash(witprog);
}
}
- case OutputType::BECH32M: {} // This function should not be used for BECH32M, so let it assert
+ case OutputType::BECH32M:
+ case OutputType::UNKNOWN: {} // This function should not be used for BECH32M or UNKNOWN, so let it assert
} // no default case, so the compiler can warn about missing cases
assert(false);
}
diff --git a/src/outputtype.h b/src/outputtype.h
index 6b4e695760..c59262591b 100644
--- a/src/outputtype.h
+++ b/src/outputtype.h
@@ -19,6 +19,7 @@ enum class OutputType {
P2SH_SEGWIT,
BECH32,
BECH32M,
+ UNKNOWN,
};
static constexpr auto OUTPUT_TYPES = std::array{
diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp
index 2b940be07e..ab5599a1b4 100644
--- a/src/policy/fees.cpp
+++ b/src/policy/fees.cpp
@@ -997,8 +997,9 @@ bool CBlockPolicyEstimator::Read(AutoFile& filein)
return true;
}
-void CBlockPolicyEstimator::FlushUnconfirmed() {
- int64_t startclear = GetTimeMicros();
+void CBlockPolicyEstimator::FlushUnconfirmed()
+{
+ const auto startclear{SteadyClock::now()};
LOCK(m_cs_fee_estimator);
size_t num_entries = mapMemPoolTxs.size();
// Remove every entry in mapMemPoolTxs
@@ -1006,24 +1007,41 @@ void CBlockPolicyEstimator::FlushUnconfirmed() {
auto mi = mapMemPoolTxs.begin();
_removeTx(mi->first, false); // this calls erase() on mapMemPoolTxs
}
- int64_t endclear = GetTimeMicros();
- LogPrint(BCLog::ESTIMATEFEE, "Recorded %u unconfirmed txs from mempool in %gs\n", num_entries, (endclear - startclear)*0.000001);
+ const auto endclear{SteadyClock::now()};
+ LogPrint(BCLog::ESTIMATEFEE, "Recorded %u unconfirmed txs from mempool in %gs\n", num_entries, Ticks<SecondsDouble>(endclear - startclear));
}
-FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee)
+static std::set<double> MakeFeeSet(const CFeeRate& min_incremental_fee,
+ double max_filter_fee_rate,
+ double fee_filter_spacing)
{
- CAmount minFeeLimit = std::max(CAmount(1), minIncrementalFee.GetFeePerK() / 2);
- feeset.insert(0);
- for (double bucketBoundary = minFeeLimit; bucketBoundary <= MAX_FILTER_FEERATE; bucketBoundary *= FEE_FILTER_SPACING) {
- feeset.insert(bucketBoundary);
+ std::set<double> fee_set;
+
+ const CAmount min_fee_limit{std::max(CAmount(1), min_incremental_fee.GetFeePerK() / 2)};
+ fee_set.insert(0);
+ for (double bucket_boundary = min_fee_limit;
+ bucket_boundary <= max_filter_fee_rate;
+ bucket_boundary *= fee_filter_spacing) {
+
+ fee_set.insert(bucket_boundary);
}
+
+ return fee_set;
+}
+
+FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee)
+ : m_fee_set{MakeFeeSet(minIncrementalFee, MAX_FILTER_FEERATE, FEE_FILTER_SPACING)}
+{
}
CAmount FeeFilterRounder::round(CAmount currentMinFee)
{
- std::set<double>::iterator it = feeset.lower_bound(currentMinFee);
- if ((it != feeset.begin() && insecure_rand.rand32() % 3 != 0) || it == feeset.end()) {
- it--;
+ AssertLockNotHeld(m_insecure_rand_mutex);
+ std::set<double>::iterator it = m_fee_set.lower_bound(currentMinFee);
+ if (it == m_fee_set.end() ||
+ (it != m_fee_set.begin() &&
+ WITH_LOCK(m_insecure_rand_mutex, return insecure_rand.rand32()) % 3 != 0)) {
+ --it;
}
return static_cast<CAmount>(*it);
}
diff --git a/src/policy/fees.h b/src/policy/fees.h
index e4628bf853..204c4f2118 100644
--- a/src/policy/fees.h
+++ b/src/policy/fees.h
@@ -299,14 +299,15 @@ private:
public:
/** Create new FeeFilterRounder */
- explicit FeeFilterRounder(const CFeeRate& minIncrementalFee);
+ explicit FeeFilterRounder(const CFeeRate& min_incremental_fee);
- /** Quantize a minimum fee for privacy purpose before broadcast. Not thread-safe due to use of FastRandomContext */
- CAmount round(CAmount currentMinFee);
+ /** Quantize a minimum fee for privacy purpose before broadcast. */
+ CAmount round(CAmount currentMinFee) EXCLUSIVE_LOCKS_REQUIRED(!m_insecure_rand_mutex);
private:
- std::set<double> feeset;
- FastRandomContext insecure_rand;
+ const std::set<double> m_fee_set;
+ Mutex m_insecure_rand_mutex;
+ FastRandomContext insecure_rand GUARDED_BY(m_insecure_rand_mutex);
};
#endif // BITCOIN_POLICY_FEES_H
diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp
index f6452266b7..5086542865 100644
--- a/src/policy/policy.cpp
+++ b/src/policy/policy.cpp
@@ -67,7 +67,7 @@ bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
return (txout.nValue < GetDustThreshold(txout, dustRelayFeeIn));
}
-bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType)
+bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_datacarrier_bytes, TxoutType& whichType)
{
std::vector<std::vector<unsigned char> > vSolutions;
whichType = Solver(scriptPubKey, vSolutions);
@@ -82,15 +82,16 @@ bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType)
return false;
if (m < 1 || m > n)
return false;
- } else if (whichType == TxoutType::NULL_DATA &&
- (!fAcceptDatacarrier || scriptPubKey.size() > nMaxDatacarrierBytes)) {
- return false;
+ } else if (whichType == TxoutType::NULL_DATA) {
+ if (!max_datacarrier_bytes || scriptPubKey.size() > *max_datacarrier_bytes) {
+ return false;
+ }
}
return true;
}
-bool IsStandardTx(const CTransaction& tx, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason)
+bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_datacarrier_bytes, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason)
{
if (tx.nVersion > TX_MAX_STANDARD_VERSION || tx.nVersion < 1) {
reason = "version";
@@ -130,7 +131,7 @@ bool IsStandardTx(const CTransaction& tx, bool permit_bare_multisig, const CFeeR
unsigned int nDataOut = 0;
TxoutType whichType;
for (const CTxOut& txout : tx.vout) {
- if (!::IsStandard(txout.scriptPubKey, whichType)) {
+ if (!::IsStandard(txout.scriptPubKey, max_datacarrier_bytes, whichType)) {
reason = "scriptpubkey";
return false;
}
diff --git a/src/policy/policy.h b/src/policy/policy.h
index cd98a601a3..29764ea2d9 100644
--- a/src/policy/policy.h
+++ b/src/policy/policy.h
@@ -31,7 +31,7 @@ static constexpr unsigned int MIN_STANDARD_TX_NONWITNESS_SIZE{82};
static constexpr unsigned int MAX_P2SH_SIGOPS{15};
/** The maximum number of sigops we're willing to relay/mine in a single tx */
static constexpr unsigned int MAX_STANDARD_TX_SIGOPS_COST{MAX_BLOCK_SIGOPS_COST/5};
-/** Default for -incrementalrelayfee, which sets the minimum feerate increase for mempool limiting or BIP 125 replacement **/
+/** Default for -incrementalrelayfee, which sets the minimum feerate increase for mempool limiting or replacement **/
static constexpr unsigned int DEFAULT_INCREMENTAL_RELAY_FEE{1000};
/** Default for -bytespersigop */
static constexpr unsigned int DEFAULT_BYTES_PER_SIGOP{20};
@@ -47,8 +47,8 @@ static constexpr unsigned int MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE{80};
static constexpr unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE{3600};
/** The maximum size of a standard ScriptSig */
static constexpr unsigned int MAX_STANDARD_SCRIPTSIG_SIZE{1650};
-/** Min feerate for defining dust. Historically this has been based on the
- * minRelayTxFee, however changing the dust limit changes which transactions are
+/** Min feerate for defining dust.
+ * Changing the dust limit changes which transactions are
* standard and should be done with care and ideally rarely. It makes sense to
* only increase the dust limit after prior releases were already not creating
* outputs below the new threshold */
@@ -105,7 +105,7 @@ CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee);
bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee);
-bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType);
+bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_datacarrier_bytes, TxoutType& whichType);
// Changing the default transaction version requires a two step process: first
@@ -117,7 +117,7 @@ static constexpr decltype(CTransaction::nVersion) TX_MAX_STANDARD_VERSION{2};
* Check for standard transaction types
* @return True if all outputs (scriptPubKeys) use only standard transaction forms
*/
-bool IsStandardTx(const CTransaction& tx, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason);
+bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_datacarrier_bytes, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason);
/**
* Check for standard transaction types
* @param[in] mapInputs Map of previous transactions that have outputs we're spending
diff --git a/src/policy/rbf.cpp b/src/policy/rbf.cpp
index e25f5c7c5b..55f47f485b 100644
--- a/src/policy/rbf.cpp
+++ b/src/policy/rbf.cpp
@@ -36,10 +36,9 @@ RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool)
// If all the inputs have nSequence >= maxint-1, it still might be
// signaled for RBF if any unconfirmed parents have signaled.
- uint64_t noLimit = std::numeric_limits<uint64_t>::max();
std::string dummy;
CTxMemPoolEntry entry = *pool.mapTx.find(tx.GetHash());
- pool.CalculateMemPoolAncestors(entry, ancestors, noLimit, noLimit, noLimit, noLimit, dummy, false);
+ pool.CalculateMemPoolAncestors(entry, ancestors, CTxMemPool::Limits::NoLimits(), dummy, false);
for (CTxMemPool::txiter it : ancestors) {
if (SignalsOptInRBF(it->GetTx())) {
@@ -65,15 +64,15 @@ std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx,
uint64_t nConflictingCount = 0;
for (const auto& mi : iters_conflicting) {
nConflictingCount += mi->GetCountWithDescendants();
- // BIP125 Rule #5: don't consider replacing more than MAX_BIP125_REPLACEMENT_CANDIDATES
+ // Rule #5: don't consider replacing more than MAX_REPLACEMENT_CANDIDATES
// entries from the mempool. This potentially overestimates the number of actual
// descendants (i.e. if multiple conflicts share a descendant, it will be counted multiple
// times), but we just want to be conservative to avoid doing too much work.
- if (nConflictingCount > MAX_BIP125_REPLACEMENT_CANDIDATES) {
+ if (nConflictingCount > MAX_REPLACEMENT_CANDIDATES) {
return strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
txid.ToString(),
nConflictingCount,
- MAX_BIP125_REPLACEMENT_CANDIDATES);
+ MAX_REPLACEMENT_CANDIDATES);
}
}
// Calculate the set of all transactions that would have to be evicted.
@@ -96,7 +95,7 @@ std::optional<std::string> HasNoNewUnconfirmed(const CTransaction& tx,
}
for (unsigned int j = 0; j < tx.vin.size(); j++) {
- // BIP125 Rule #2: We don't want to accept replacements that require low feerate junk to be
+ // Rule #2: We don't want to accept replacements that require low 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.
//
@@ -162,7 +161,7 @@ std::optional<std::string> PaysForRBF(CAmount original_fees,
CFeeRate relay_fee,
const uint256& txid)
{
- // BIP125 Rule #3: The replacement fees must be greater than or equal to fees of the
+ // Rule #3: The replacement fees must be greater than or equal to fees of the
// transactions it replaces, otherwise the bandwidth used by those conflicting transactions
// would not be paid for.
if (replacement_fees < original_fees) {
@@ -170,7 +169,7 @@ std::optional<std::string> PaysForRBF(CAmount original_fees,
txid.ToString(), FormatMoney(replacement_fees), FormatMoney(original_fees));
}
- // BIP125 Rule #4: The new transaction must pay for its own bandwidth. Otherwise, we have a DoS
+ // Rule #4: The new transaction must pay for its own bandwidth. Otherwise, we have a DoS
// vector where attackers can cause a transaction to be replaced (and relayed) repeatedly by
// increasing the fee by tiny amounts.
CAmount additional_fees = replacement_fees - original_fees;
diff --git a/src/policy/rbf.h b/src/policy/rbf.h
index 07f68c8fd4..28c4e4bf9b 100644
--- a/src/policy/rbf.h
+++ b/src/policy/rbf.h
@@ -19,9 +19,9 @@
class CFeeRate;
class uint256;
-/** Maximum number of transactions that can be replaced by BIP125 RBF (Rule #5). This includes all
+/** Maximum number of transactions that can be replaced by RBF (Rule #5). This includes all
* mempool conflicts and their descendants. */
-static constexpr uint32_t MAX_BIP125_REPLACEMENT_CANDIDATES{100};
+static constexpr uint32_t MAX_REPLACEMENT_CANDIDATES{100};
/** The rbf state of unconfirmed transactions */
enum class RBFTransactionState {
@@ -47,24 +47,25 @@ enum class RBFTransactionState {
RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(pool.cs);
RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx);
-/** Get all descendants of iters_conflicting. Also enforce BIP125 Rule #5, "The number of original
- * transactions to be replaced and their descendant transactions which will be evicted from the
- * mempool must not exceed a total of 100 transactions." Quit as early as possible. There cannot be
- * more than MAX_BIP125_REPLACEMENT_CANDIDATES potential entries.
+/** Get all descendants of iters_conflicting. Checks that there are no more than
+ * MAX_REPLACEMENT_CANDIDATES potential entries. May overestimate if the entries in
+ * iters_conflicting have overlapping descendants.
* @param[in] iters_conflicting The set of iterators to mempool entries.
* @param[out] all_conflicts Populated with all the mempool entries that would be replaced,
- * which includes descendants of iters_conflicting. Not cleared at
- * the start; any existing mempool entries will remain in the set.
- * @returns an error message if Rule #5 is broken, otherwise a std::nullopt.
+ * which includes iters_conflicting and all entries' descendants.
+ * Not cleared at the start; any existing mempool entries will
+ * remain in the set.
+ * @returns an error message if MAX_REPLACEMENT_CANDIDATES may be exceeded, otherwise a std::nullopt.
*/
std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx, CTxMemPool& pool,
const CTxMemPool::setEntries& iters_conflicting,
CTxMemPool::setEntries& all_conflicts)
EXCLUSIVE_LOCKS_REQUIRED(pool.cs);
-/** BIP125 Rule #2: "The replacement transaction may only include an unconfirmed input if that input
- * was included in one of the original transactions."
- * @returns error message if Rule #2 is broken, otherwise std::nullopt. */
+/** The replacement transaction may only include an unconfirmed input if that input was included in
+ * one of the original transactions.
+ * @returns error message if tx spends unconfirmed inputs not also spent by iters_conflicting,
+ * otherwise std::nullopt. */
std::optional<std::string> HasNoNewUnconfirmed(const CTransaction& tx, const CTxMemPool& pool,
const CTxMemPool::setEntries& iters_conflicting)
EXCLUSIVE_LOCKS_REQUIRED(pool.cs);
@@ -90,9 +91,8 @@ std::optional<std::string> EntriesAndTxidsDisjoint(const CTxMemPool::setEntries&
std::optional<std::string> PaysMoreThanConflicts(const CTxMemPool::setEntries& iters_conflicting,
CFeeRate replacement_feerate, const uint256& txid);
-/** Enforce BIP125 Rule #3 "The replacement transaction pays an absolute fee of at least the sum
- * paid by the original transactions." Enforce BIP125 Rule #4 "The replacement transaction must also
- * pay for its own bandwidth at or above the rate set by the node's minimum relay fee setting."
+/** The replacement transaction must pay more fees than the original transactions. The additional
+ * fees must pay for the replacement's bandwidth at or above the incremental relay feerate.
* @param[in] original_fees Total modified fees of original transaction(s).
* @param[in] replacement_fees Total modified fees of replacement transaction(s).
* @param[in] replacement_vsize Total virtual size of replacement transaction(s).
diff --git a/src/policy/settings.cpp b/src/policy/settings.cpp
index 0b67d274ce..39e00f1111 100644
--- a/src/policy/settings.cpp
+++ b/src/policy/settings.cpp
@@ -5,11 +5,6 @@
#include <policy/settings.h>
-#include <policy/feerate.h>
#include <policy/policy.h>
-bool fIsBareMultisigStd = DEFAULT_PERMIT_BAREMULTISIG;
-CFeeRate incrementalRelayFee = CFeeRate(DEFAULT_INCREMENTAL_RELAY_FEE);
-CFeeRate dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE);
-CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
unsigned int nBytesPerSigOp = DEFAULT_BYTES_PER_SIGOP;
diff --git a/src/policy/settings.h b/src/policy/settings.h
index 2311d01fe8..f0d6f779ae 100644
--- a/src/policy/settings.h
+++ b/src/policy/settings.h
@@ -6,35 +6,6 @@
#ifndef BITCOIN_POLICY_SETTINGS_H
#define BITCOIN_POLICY_SETTINGS_H
-#include <policy/feerate.h>
-#include <policy/policy.h>
-
-#include <cstdint>
-#include <string>
-
-class CTransaction;
-
-// Policy settings which are configurable at runtime.
-extern CFeeRate incrementalRelayFee;
-extern CFeeRate dustRelayFee;
-/** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */
-extern CFeeRate minRelayTxFee;
extern unsigned int nBytesPerSigOp;
-extern bool fIsBareMultisigStd;
-
-static inline bool IsStandardTx(const CTransaction& tx, std::string& reason)
-{
- return IsStandardTx(tx, ::fIsBareMultisigStd, ::dustRelayFee, reason);
-}
-
-static inline int64_t GetVirtualTransactionSize(int64_t weight, int64_t sigop_cost)
-{
- return GetVirtualTransactionSize(weight, sigop_cost, ::nBytesPerSigOp);
-}
-
-static inline int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t sigop_cost)
-{
- return GetVirtualTransactionSize(tx, sigop_cost, ::nBytesPerSigOp);
-}
#endif // BITCOIN_POLICY_SETTINGS_H
diff --git a/src/pow.cpp b/src/pow.cpp
index 1414d37564..c0449cac74 100644
--- a/src/pow.cpp
+++ b/src/pow.cpp
@@ -71,6 +71,57 @@ unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nF
return bnNew.GetCompact();
}
+// Check that on difficulty adjustments, the new difficulty does not increase
+// or decrease beyond the permitted limits.
+bool PermittedDifficultyTransition(const Consensus::Params& params, int64_t height, uint32_t old_nbits, uint32_t new_nbits)
+{
+ if (params.fPowAllowMinDifficultyBlocks) return true;
+
+ if (height % params.DifficultyAdjustmentInterval() == 0) {
+ int64_t smallest_timespan = params.nPowTargetTimespan/4;
+ int64_t largest_timespan = params.nPowTargetTimespan*4;
+
+ const arith_uint256 pow_limit = UintToArith256(params.powLimit);
+ arith_uint256 observed_new_target;
+ observed_new_target.SetCompact(new_nbits);
+
+ // Calculate the largest difficulty value possible:
+ arith_uint256 largest_difficulty_target;
+ largest_difficulty_target.SetCompact(old_nbits);
+ largest_difficulty_target *= largest_timespan;
+ largest_difficulty_target /= params.nPowTargetTimespan;
+
+ if (largest_difficulty_target > pow_limit) {
+ largest_difficulty_target = pow_limit;
+ }
+
+ // Round and then compare this new calculated value to what is
+ // observed.
+ arith_uint256 maximum_new_target;
+ maximum_new_target.SetCompact(largest_difficulty_target.GetCompact());
+ if (maximum_new_target < observed_new_target) return false;
+
+ // Calculate the smallest difficulty value possible:
+ arith_uint256 smallest_difficulty_target;
+ smallest_difficulty_target.SetCompact(old_nbits);
+ smallest_difficulty_target *= smallest_timespan;
+ smallest_difficulty_target /= params.nPowTargetTimespan;
+
+ if (smallest_difficulty_target > pow_limit) {
+ smallest_difficulty_target = pow_limit;
+ }
+
+ // Round and then compare this new calculated value to what is
+ // observed.
+ arith_uint256 minimum_new_target;
+ minimum_new_target.SetCompact(smallest_difficulty_target.GetCompact());
+ if (minimum_new_target > observed_new_target) return false;
+ } else if (old_nbits != new_nbits) {
+ return false;
+ }
+ return true;
+}
+
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)
{
bool fNegative;
diff --git a/src/pow.h b/src/pow.h
index 1d802cd01e..44b9d673ef 100644
--- a/src/pow.h
+++ b/src/pow.h
@@ -20,4 +20,18 @@ unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nF
/** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params&);
+/**
+ * Return false if the proof-of-work requirement specified by new_nbits at a
+ * given height is not possible, given the proof-of-work on the prior block as
+ * specified by old_nbits.
+ *
+ * This function only checks that the new value is within a factor of 4 of the
+ * old value for blocks at the difficulty adjustment interval, and otherwise
+ * requires the values to be the same.
+ *
+ * Always returns true on networks where min difficulty blocks are allowed,
+ * such as regtest/testnet.
+ */
+bool PermittedDifficultyTransition(const Consensus::Params& params, int64_t height, uint32_t old_nbits, uint32_t new_nbits);
+
#endif // BITCOIN_POW_H
diff --git a/src/prevector.h b/src/prevector.h
index a52510930a..7df5a067a2 100644
--- a/src/prevector.h
+++ b/src/prevector.h
@@ -6,7 +6,7 @@
#define BITCOIN_PREVECTOR_H
#include <assert.h>
-#include <stdlib.h>
+#include <cstdlib>
#include <stdint.h>
#include <string.h>
diff --git a/src/primitives/block.h b/src/primitives/block.h
index 2d10853607..2e26e6c426 100644
--- a/src/primitives/block.h
+++ b/src/primitives/block.h
@@ -9,6 +9,7 @@
#include <primitives/transaction.h>
#include <serialize.h>
#include <uint256.h>
+#include <util/time.h>
/** Nodes collect new transactions into a block, hash them into a hash tree,
* and scan through nonce values to make the block's hash satisfy proof-of-work
@@ -52,6 +53,11 @@ public:
uint256 GetHash() const;
+ NodeSeconds Time() const
+ {
+ return NodeSeconds{std::chrono::seconds{nTime}};
+ }
+
int64_t GetBlockTime() const
{
return (int64_t)nTime;
@@ -117,7 +123,7 @@ struct CBlockLocator
CBlockLocator() {}
- explicit CBlockLocator(const std::vector<uint256>& vHaveIn) : vHave(vHaveIn) {}
+ explicit CBlockLocator(std::vector<uint256>&& have) : vHave(std::move(have)) {}
SERIALIZE_METHODS(CBlockLocator, obj)
{
diff --git a/src/protocol.cpp b/src/protocol.cpp
index 139405170b..23c68b335b 100644
--- a/src/protocol.cpp
+++ b/src/protocol.cpp
@@ -44,6 +44,7 @@ const char *CFHEADERS="cfheaders";
const char *GETCFCHECKPT="getcfcheckpt";
const char *CFCHECKPT="cfcheckpt";
const char *WTXIDRELAY="wtxidrelay";
+const char *SENDTXRCNCL="sendtxrcncl";
} // namespace NetMsgType
/** All known message types. Keep this in the same order as the list of
@@ -84,6 +85,7 @@ const static std::string allNetMessageTypes[] = {
NetMsgType::GETCFCHECKPT,
NetMsgType::CFCHECKPT,
NetMsgType::WTXIDRELAY,
+ NetMsgType::SENDTXRCNCL,
};
const static std::vector<std::string> allNetMessageTypesVec(std::begin(allNetMessageTypes), std::end(allNetMessageTypes));
@@ -199,12 +201,7 @@ static std::string serviceFlagToStr(size_t bit)
// Not using default, so we get warned when a case is missing
}
- std::ostringstream stream;
- stream.imbue(std::locale::classic());
- stream << "UNKNOWN[";
- stream << "2^" << bit;
- stream << "]";
- return stream.str();
+ return strprintf("UNKNOWN[2^%u]", bit);
}
std::vector<std::string> serviceFlagsToStr(uint64_t flags)
diff --git a/src/protocol.h b/src/protocol.h
index b85dc0d820..17a363b1d3 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -258,6 +258,14 @@ extern const char* CFCHECKPT;
* @since protocol version 70016 as described by BIP 339.
*/
extern const char* WTXIDRELAY;
+/**
+ * Contains 2 1-byte bools, a 4-byte version number and an 8-byte salt.
+ * The 2 booleans indicate that a node is willing to participate in transaction
+ * reconciliation, respectively as an initiator or as a receiver.
+ * The salt is used to compute short txids needed for efficient
+ * txreconciliation, as described by BIP 330.
+ */
+extern const char* SENDTXRCNCL;
}; // namespace NetMsgType
/* Get a vector of all valid message types (see above) */
diff --git a/src/psbt.cpp b/src/psbt.cpp
index 36fec74bc9..461987c503 100644
--- a/src/psbt.cpp
+++ b/src/psbt.cpp
@@ -4,6 +4,7 @@
#include <psbt.h>
+#include <policy/policy.h>
#include <util/check.h>
#include <util/strencodings.h>
@@ -218,8 +219,14 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
for (const auto& key_pair : hd_keypaths) {
sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair);
}
- if (m_tap_tree.has_value() && m_tap_internal_key.IsFullyValid()) {
- TaprootSpendData spenddata = m_tap_tree->GetSpendData();
+ if (!m_tap_tree.empty() && m_tap_internal_key.IsFullyValid()) {
+ TaprootBuilder builder;
+ for (const auto& [depth, leaf_ver, script] : m_tap_tree) {
+ builder.Add((int)depth, script, (int)leaf_ver, /*track=*/true);
+ }
+ assert(builder.IsComplete());
+ builder.Finalize(m_tap_internal_key);
+ TaprootSpendData spenddata = builder.GetSpendData();
sigdata.tr_spenddata.internal_key = m_tap_internal_key;
sigdata.tr_spenddata.Merge(spenddata);
@@ -243,8 +250,8 @@ void PSBTOutput::FromSignatureData(const SignatureData& sigdata)
if (!sigdata.tr_spenddata.internal_key.IsNull()) {
m_tap_internal_key = sigdata.tr_spenddata.internal_key;
}
- if (sigdata.tr_builder.has_value()) {
- m_tap_tree = sigdata.tr_builder;
+ if (sigdata.tr_builder.has_value() && sigdata.tr_builder->HasScripts()) {
+ m_tap_tree = sigdata.tr_builder->GetTreeTuples();
}
for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) {
m_tap_bip32_paths.emplace(pubkey, leaf_origin);
@@ -265,13 +272,43 @@ void PSBTOutput::Merge(const PSBTOutput& output)
if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script;
if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script;
if (m_tap_internal_key.IsNull() && !output.m_tap_internal_key.IsNull()) m_tap_internal_key = output.m_tap_internal_key;
- if (m_tap_tree.has_value() && !output.m_tap_tree.has_value()) m_tap_tree = output.m_tap_tree;
+ if (m_tap_tree.empty() && !output.m_tap_tree.empty()) m_tap_tree = output.m_tap_tree;
}
+
bool PSBTInputSigned(const PSBTInput& input)
{
return !input.final_script_sig.empty() || !input.final_script_witness.IsNull();
}
+bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned int input_index, const PrecomputedTransactionData* txdata)
+{
+ CTxOut utxo;
+ assert(psbt.inputs.size() >= input_index);
+ const PSBTInput& input = psbt.inputs[input_index];
+
+ if (input.non_witness_utxo) {
+ // If we're taking our information from a non-witness UTXO, verify that it matches the prevout.
+ COutPoint prevout = psbt.tx->vin[input_index].prevout;
+ if (prevout.n >= input.non_witness_utxo->vout.size()) {
+ return false;
+ }
+ if (input.non_witness_utxo->GetHash() != prevout.hash) {
+ return false;
+ }
+ utxo = input.non_witness_utxo->vout[prevout.n];
+ } else if (!input.witness_utxo.IsNull()) {
+ utxo = input.witness_utxo;
+ } else {
+ return false;
+ }
+
+ if (txdata) {
+ return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&(*psbt.tx), input_index, utxo.nValue, *txdata, MissingDataBehavior::FAIL});
+ } else {
+ return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&(*psbt.tx), input_index, utxo.nValue, MissingDataBehavior::FAIL});
+ }
+}
+
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt) {
size_t count = 0;
for (const auto& input : psbt.inputs) {
@@ -325,7 +362,7 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
PSBTInput& input = psbt.inputs.at(index);
const CMutableTransaction& tx = *psbt.tx;
- if (PSBTInputSigned(input)) {
+ if (PSBTInputSignedAndVerified(psbt, index, txdata)) {
return true;
}
diff --git a/src/psbt.h b/src/psbt.h
index eef7d7dd3b..37bf142366 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -641,6 +641,9 @@ struct PSBTInput
s >> leaf_hashes;
size_t after_hashes = s.size();
size_t hashes_len = before_hashes - after_hashes;
+ if (hashes_len > value_len) {
+ throw std::ios_base::failure("Input Taproot BIP32 keypath has an invalid length");
+ }
size_t origin_len = value_len - hashes_len;
m_tap_bip32_paths.emplace(xonly, std::make_pair(leaf_hashes, DeserializeKeyOrigin(s, origin_len)));
break;
@@ -710,7 +713,7 @@ struct PSBTOutput
CScript witness_script;
std::map<CPubKey, KeyOriginInfo> hd_keypaths;
XOnlyPubKey m_tap_internal_key;
- std::optional<TaprootBuilder> m_tap_tree;
+ std::vector<std::tuple<uint8_t, uint8_t, CScript>> m_tap_tree;
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
std::set<PSBTProprietary> m_proprietary;
@@ -751,15 +754,11 @@ struct PSBTOutput
}
// Write taproot tree
- if (m_tap_tree.has_value()) {
+ if (!m_tap_tree.empty()) {
SerializeToVector(s, PSBT_OUT_TAP_TREE);
std::vector<unsigned char> value;
CVectorWriter s_value(s.GetType(), s.GetVersion(), value, 0);
- const auto& tuples = m_tap_tree->GetTreeTuples();
- for (const auto& tuple : tuples) {
- uint8_t depth = std::get<0>(tuple);
- uint8_t leaf_ver = std::get<1>(tuple);
- CScript script = std::get<2>(tuple);
+ for (const auto& [depth, leaf_ver, script] : m_tap_tree) {
s_value << depth;
s_value << leaf_ver;
s_value << script;
@@ -855,10 +854,13 @@ struct PSBTOutput
} else if (key.size() != 1) {
throw std::ios_base::failure("Output Taproot tree key is more than one byte type");
}
- m_tap_tree.emplace();
std::vector<unsigned char> tree_v;
s >> tree_v;
SpanReader s_tree(s.GetType(), s.GetVersion(), tree_v);
+ if (s_tree.empty()) {
+ throw std::ios_base::failure("Output Taproot tree must not be empty");
+ }
+ TaprootBuilder builder;
while (!s_tree.empty()) {
uint8_t depth;
uint8_t leaf_ver;
@@ -872,9 +874,10 @@ struct PSBTOutput
if ((leaf_ver & ~TAPROOT_LEAF_MASK) != 0) {
throw std::ios_base::failure("Output Taproot tree has a leaf with an invalid leaf version");
}
- m_tap_tree->Add((int)depth, script, (int)leaf_ver, true /* track */);
+ m_tap_tree.push_back(std::make_tuple(depth, leaf_ver, script));
+ builder.Add((int)depth, script, (int)leaf_ver, true /* track */);
}
- if (!m_tap_tree->IsComplete()) {
+ if (!builder.IsComplete()) {
throw std::ios_base::failure("Output Taproot tree is malformed");
}
break;
@@ -928,11 +931,6 @@ struct PSBTOutput
}
}
- // Finalize m_tap_tree so that all of the computed things are computed
- if (m_tap_tree.has_value() && m_tap_tree->IsComplete() && m_tap_internal_key.IsFullyValid()) {
- m_tap_tree->Finalize(m_tap_internal_key);
- }
-
if (!found_sep) {
throw std::ios_base::failure("Separator is missing at the end of an output map");
}
@@ -1220,9 +1218,12 @@ std::string PSBTRoleName(PSBTRole role);
/** Compute a PrecomputedTransactionData object from a psbt. */
PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt);
-/** Checks whether a PSBTInput is already signed. */
+/** Checks whether a PSBTInput is already signed by checking for non-null finalized fields. */
bool PSBTInputSigned(const PSBTInput& input);
+/** Checks whether a PSBTInput is already signed by doing script verification using final fields. */
+bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned int input_index, const PrecomputedTransactionData* txdata);
+
/** Signs a PSBTInput, verifying that all provided data matches what is being signed.
*
* txdata should be the output of PrecomputePSBTData (which can be shared across
diff --git a/src/pubkey.cpp b/src/pubkey.cpp
index a4a1be6388..2e37e16690 100644
--- a/src/pubkey.cpp
+++ b/src/pubkey.cpp
@@ -365,6 +365,7 @@ void CExtPubKey::DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VE
}
bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild) const {
+ if (nDepth == std::numeric_limits<unsigned char>::max()) return false;
out.nDepth = nDepth + 1;
CKeyID id = pubkey.GetID();
memcpy(out.vchFingerprint, &id, 4);
diff --git a/src/pubkey.h b/src/pubkey.h
index 463efe1b00..0485a38f72 100644
--- a/src/pubkey.h
+++ b/src/pubkey.h
@@ -218,7 +218,7 @@ public:
bool Decompress();
//! Derive BIP32 child pubkey.
- bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
+ [[nodiscard]] bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
};
class XOnlyPubKey
@@ -327,7 +327,7 @@ struct CExtPubKey {
void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
void EncodeWithVersion(unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]) const;
void DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]);
- bool Derive(CExtPubKey& out, unsigned int nChild) const;
+ [[nodiscard]] bool Derive(CExtPubKey& out, unsigned int nChild) const;
};
/** Users of this module must hold an ECCVerifyHandle. The constructor and
diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp
index bb50b5384a..8b5da7f9f0 100644
--- a/src/qt/addresstablemodel.cpp
+++ b/src/qt/addresstablemodel.cpp
@@ -384,7 +384,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
return QString();
}
}
- strAddress = EncodeDestination(op_dest.GetObj());
+ strAddress = EncodeDestination(*op_dest);
}
else
{
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index 33c60deafb..cc01e4d54a 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -75,6 +75,7 @@ Q_IMPORT_PLUGIN(QAndroidPlatformIntegrationPlugin)
Q_DECLARE_METATYPE(bool*)
Q_DECLARE_METATYPE(CAmount)
Q_DECLARE_METATYPE(SynchronizationState)
+Q_DECLARE_METATYPE(SyncType)
Q_DECLARE_METATYPE(uint256)
static void RegisterMetaTypes()
@@ -82,6 +83,7 @@ static void RegisterMetaTypes()
// Register meta types used for QMetaObject::invokeMethod and Qt::QueuedConnection
qRegisterMetaType<bool*>();
qRegisterMetaType<SynchronizationState>();
+ qRegisterMetaType<SyncType>();
#ifdef ENABLE_WALLET
qRegisterMetaType<WalletModel*>();
#endif
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index 90f228803c..f522d78be4 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -410,7 +410,7 @@ void BitcoinGUI::createActions()
connect(action, &QAction::triggered, [this, path] {
auto activity = new OpenWalletActivity(m_wallet_controller, this);
- connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet);
+ connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet, Qt::QueuedConnection);
activity->open(path);
});
}
@@ -431,7 +431,7 @@ void BitcoinGUI::createActions()
bool wallet_name_ok;
/*: Title of pop-up window shown when the user is attempting to
-+ restore a wallet. */
+ restore a wallet. */
QString title = tr("Restore Wallet");
//: Label of the input field where the name of the wallet is entered.
QString label = tr("Wallet Name");
@@ -615,8 +615,8 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndH
connect(_clientModel, &ClientModel::numConnectionsChanged, this, &BitcoinGUI::setNumConnections);
connect(_clientModel, &ClientModel::networkActiveChanged, this, &BitcoinGUI::setNetworkActive);
- modalOverlay->setKnownBestHeight(tip_info->header_height, QDateTime::fromSecsSinceEpoch(tip_info->header_time));
- setNumBlocks(tip_info->block_height, QDateTime::fromSecsSinceEpoch(tip_info->block_time), tip_info->verification_progress, false, SynchronizationState::INIT_DOWNLOAD);
+ modalOverlay->setKnownBestHeight(tip_info->header_height, QDateTime::fromSecsSinceEpoch(tip_info->header_time), /*presync=*/false);
+ setNumBlocks(tip_info->block_height, QDateTime::fromSecsSinceEpoch(tip_info->block_time), tip_info->verification_progress, SyncType::BLOCK_SYNC, SynchronizationState::INIT_DOWNLOAD);
connect(_clientModel, &ClientModel::numBlocksChanged, this, &BitcoinGUI::setNumBlocks);
// Receive and report messages from client model
@@ -1026,6 +1026,13 @@ void BitcoinGUI::updateHeadersSyncProgressLabel()
progressBarLabel->setText(tr("Syncing Headers (%1%)…").arg(QString::number(100.0 / (headersTipHeight+estHeadersLeft)*headersTipHeight, 'f', 1)));
}
+void BitcoinGUI::updateHeadersPresyncProgressLabel(int64_t height, const QDateTime& blockDate)
+{
+ int estHeadersLeft = blockDate.secsTo(QDateTime::currentDateTime()) / Params().GetConsensus().nPowTargetSpacing;
+ if (estHeadersLeft > HEADER_HEIGHT_DELTA_SYNC)
+ progressBarLabel->setText(tr("Pre-syncing Headers (%1%)…").arg(QString::number(100.0 / (height+estHeadersLeft)*height, 'f', 1)));
+}
+
void BitcoinGUI::openOptionsDialogWithTab(OptionsDialog::Tab tab)
{
if (!clientModel || !clientModel->getOptionsModel())
@@ -1039,7 +1046,7 @@ void BitcoinGUI::openOptionsDialogWithTab(OptionsDialog::Tab tab)
GUIUtil::ShowModalDialogAsynchronously(dlg);
}
-void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state)
+void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state)
{
// Disabling macOS App Nap on initial sync, disk and reindex operations.
#ifdef Q_OS_MACOS
@@ -1052,8 +1059,8 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer
if (modalOverlay)
{
- if (header)
- modalOverlay->setKnownBestHeight(count, blockDate);
+ if (synctype != SyncType::BLOCK_SYNC)
+ modalOverlay->setKnownBestHeight(count, blockDate, synctype == SyncType::HEADER_PRESYNC);
else
modalOverlay->tipUpdate(count, blockDate, nVerificationProgress);
}
@@ -1067,7 +1074,10 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer
enum BlockSource blockSource = clientModel->getBlockSource();
switch (blockSource) {
case BlockSource::NETWORK:
- if (header) {
+ if (synctype == SyncType::HEADER_PRESYNC) {
+ updateHeadersPresyncProgressLabel(count, blockDate);
+ return;
+ } else if (synctype == SyncType::HEADER_SYNC) {
updateHeadersSyncProgressLabel();
return;
}
@@ -1075,7 +1085,7 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer
updateHeadersSyncProgressLabel();
break;
case BlockSource::DISK:
- if (header) {
+ if (synctype != SyncType::BLOCK_SYNC) {
progressBarLabel->setText(tr("Indexing blocks on disk…"));
} else {
progressBarLabel->setText(tr("Processing blocks on disk…"));
@@ -1085,7 +1095,7 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer
progressBarLabel->setText(tr("Reindexing blocks on disk…"));
break;
case BlockSource::NONE:
- if (header) {
+ if (synctype != SyncType::BLOCK_SYNC) {
return;
}
progressBarLabel->setText(tr("Connecting to peers…"));
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index b2e13245e1..912e9b95aa 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -10,6 +10,7 @@
#endif
#include <qt/bitcoinunits.h>
+#include <qt/clientmodel.h>
#include <qt/guiutil.h>
#include <qt/optionsdialog.h>
@@ -28,7 +29,6 @@
#include <memory>
-class ClientModel;
class NetworkStyle;
class Notificator;
class OptionsModel;
@@ -208,6 +208,7 @@ private:
void updateNetworkState();
void updateHeadersSyncProgressLabel();
+ void updateHeadersPresyncProgressLabel(int64_t height, const QDateTime& blockDate);
/** Open the OptionsDialog on the specified tab index */
void openOptionsDialogWithTab(OptionsDialog::Tab tab);
@@ -226,7 +227,7 @@ public Q_SLOTS:
/** Set network state shown in the UI */
void setNetworkActive(bool network_active);
/** Set number of blocks and last block date shown in the UI */
- void setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state);
+ void setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state);
/** Notify the user of an event from the core network or transaction handling code.
@param[in] title the message box / notification title
diff --git a/src/qt/bitcoinstrings.cpp b/src/qt/bitcoinstrings.cpp
index ba804d6955..3df4d4d921 100644
--- a/src/qt/bitcoinstrings.cpp
+++ b/src/qt/bitcoinstrings.cpp
@@ -14,9 +14,28 @@ QT_TRANSLATE_NOOP("bitcoin-core", ""
"%s corrupt. Try using the wallet tool bitcoin-wallet to salvage or restoring "
"a backup."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
+"%s request to listen on port %u. This port is considered \"bad\" and thus it "
+"is unlikely that any Bitcoin Core peers connect to it. See doc/p2p-bad-ports."
+"md for details and a full list."),
+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", ""
+"-reindex-chainstate option is not compatible with -blockfilterindex. Please "
+"temporarily disable blockfilterindex while using -reindex-chainstate, or "
+"replace -reindex-chainstate with -reindex to fully rebuild all indexes."),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
+"-reindex-chainstate option is not compatible with -coinstatsindex. Please "
+"temporarily disable coinstatsindex while using -reindex-chainstate, or "
+"replace -reindex-chainstate with -reindex to fully rebuild all indexes."),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
+"-reindex-chainstate option is not compatible with -txindex. Please "
+"temporarily disable txindex while using -reindex-chainstate, or replace -"
+"reindex-chainstate with -reindex to fully rebuild all indexes."),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
+"Assumed-valid: last wallet synchronisation goes beyond available block data. "
+"You need to wait for the background validation chain to download more blocks."),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
"Cannot downgrade wallet from version %i to version %i. Wallet version "
"unchanged."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
@@ -41,6 +60,9 @@ QT_TRANSLATE_NOOP("bitcoin-core", ""
"Error reading %s! Transaction data may be missing or incorrect. Rescanning "
"wallet."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
+"Error: Address book data in wallet cannot be identified to belong to "
+"migrated wallets"),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
"Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"Error: Dumpfile identifier record is incorrect. Got \"%s\", expected \"%s\"."),
@@ -48,10 +70,20 @@ QT_TRANSLATE_NOOP("bitcoin-core", ""
"Error: Dumpfile version is not supported. This version of bitcoin-wallet "
"only supports version 1 dumpfiles. Got dumpfile with version %s"),
QT_TRANSLATE_NOOP("bitcoin-core", ""
+"Error: Duplicate descriptors created during migration. Your wallet may be "
+"corrupted."),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
"Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and "
"\"bech32\" address types"),
QT_TRANSLATE_NOOP("bitcoin-core", ""
-"Error: Listening for incoming connections failed (listen returned error %s)"),
+"Error: Transaction %s in wallet cannot be identified to belong to migrated "
+"wallets"),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
+"Error: Unable to produce descriptors for this legacy wallet. Make sure the "
+"wallet is unlocked first"),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
+"Failed to rename invalid peers.dat file. Please move or delete it and try "
+"again."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
"Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -"
"fallbackfee."),
@@ -59,6 +91,9 @@ QT_TRANSLATE_NOOP("bitcoin-core", ""
"File %s already exists. If you are sure this is what you want, move it out "
"of the way first."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
+"Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet "
+"forbids connections to IPv4/IPv6"),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
"Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay "
"fee of %s to prevent stuck transactions)"),
QT_TRANSLATE_NOOP("bitcoin-core", ""
@@ -77,6 +112,13 @@ QT_TRANSLATE_NOOP("bitcoin-core", ""
"No wallet file format provided. To use createfromdump, -format=<format> must "
"be provided."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
+"Outbound connections restricted to Tor (-onlynet=onion) but the proxy for "
+"reaching the Tor network is explicitly forbidden: -onion=0"),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
+"Outbound connections restricted to Tor (-onlynet=onion) but the proxy for "
+"reaching the Tor network is not provided: none of -proxy, -onion or -"
+"listenonion is given"),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
"Please check that your computer's date and time are correct! If your clock "
"is wrong, %s will not work properly."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
@@ -85,6 +127,9 @@ QT_TRANSLATE_NOOP("bitcoin-core", ""
QT_TRANSLATE_NOOP("bitcoin-core", ""
"Prune configured below the minimum of %d MiB. Please use a higher number."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
+"Prune mode is incompatible with -reindex-chainstate. Use full -reindex "
+"instead."),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
"Prune: last wallet synchronisation goes beyond pruned data. You need to -"
"reindex (download the whole blockchain again in case of pruned node)"),
QT_TRANSLATE_NOOP("bitcoin-core", ""
@@ -129,6 +174,21 @@ QT_TRANSLATE_NOOP("bitcoin-core", ""
"Unknown wallet file format \"%s\" provided. Please provide one of \"bdb\" or "
"\"sqlite\"."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
+"Unrecognized descriptor found. Loading wallet %s\n"
+"\n"
+"The wallet might had been created on a newer version.\n"
+"Please try running the latest software version.\n"),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
+"Unsupported category-specific logging level -loglevel=%s. Expected -"
+"loglevel=<category>:<loglevel>. Valid categories: %s. Valid loglevels: %s."),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
+"Unsupported chainstate database format found. Please restart with -reindex-"
+"chainstate. This will rebuild the chainstate database."),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
+"Wallet created successfully. The legacy wallet type is being deprecated and "
+"support for creating and opening legacy wallets will be removed in the "
+"future."),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
"Warning: Dumpfile wallet format \"%s\" does not match command line specified "
"format \"%s\"."),
QT_TRANSLATE_NOOP("bitcoin-core", ""
@@ -142,6 +202,12 @@ QT_TRANSLATE_NOOP("bitcoin-core", ""
QT_TRANSLATE_NOOP("bitcoin-core", ""
"You need to rebuild the database using -reindex to go back to unpruned "
"mode. This will redownload the entire blockchain"),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
+"\n"
+"Unable to cleanup failed migration"),
+QT_TRANSLATE_NOOP("bitcoin-core", ""
+"\n"
+"Unable to restore backup of wallet."),
QT_TRANSLATE_NOOP("bitcoin-core", "%s is set very high!"),
QT_TRANSLATE_NOOP("bitcoin-core", "-maxmempool must be at least %d MB"),
QT_TRANSLATE_NOOP("bitcoin-core", "A fatal internal error occurred, see debug.log for details"),
@@ -169,16 +235,25 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Error loading block database"),
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 reading next record from wallet database"),
-QT_TRANSLATE_NOOP("bitcoin-core", "Error upgrading chainstate database"),
+QT_TRANSLATE_NOOP("bitcoin-core", "Error: Could not add watchonly tx to watchonly wallet"),
+QT_TRANSLATE_NOOP("bitcoin-core", "Error: Could not delete watchonly transactions"),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: Couldn't create cursor into database"),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: Disk space is low for %s"),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: Dumpfile checksum does not match. Computed %s, expected %s"),
+QT_TRANSLATE_NOOP("bitcoin-core", "Error: Failed to create new watchonly wallet"),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: Got key that was not hex: %s"),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: Got value that was not hex: %s"),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: Keypool ran out, please call keypoolrefill first"),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: Missing checksum"),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: No %s addresses available."),
+QT_TRANSLATE_NOOP("bitcoin-core", "Error: Not all watchonly txs could be deleted"),
+QT_TRANSLATE_NOOP("bitcoin-core", "Error: This wallet already uses SQLite"),
+QT_TRANSLATE_NOOP("bitcoin-core", "Error: This wallet is already a descriptor wallet"),
+QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to begin reading all records in the database"),
+QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to make a backup of your wallet"),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to parse version %u as a uint32_t"),
+QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to read all records in the database"),
+QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to remove watchonly address book data"),
QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to write record to new wallet"),
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"),
@@ -199,6 +274,7 @@ 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", "Listening for incoming connections failed (listen returned error %s)"),
QT_TRANSLATE_NOOP("bitcoin-core", "Loading P2P addresses…"),
QT_TRANSLATE_NOOP("bitcoin-core", "Loading banlist…"),
QT_TRANSLATE_NOOP("bitcoin-core", "Loading block index…"),
@@ -207,10 +283,8 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Missing amount"),
QT_TRANSLATE_NOOP("bitcoin-core", "Missing solving data for estimating transaction size"),
QT_TRANSLATE_NOOP("bitcoin-core", "Need to specify a port with -whitebind: '%s'"),
QT_TRANSLATE_NOOP("bitcoin-core", "No addresses available"),
-QT_TRANSLATE_NOOP("bitcoin-core", "No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>."),
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 -coinstatsindex."),
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."),
@@ -241,21 +315,24 @@ 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 needs a change address, but we can't generate it."),
QT_TRANSLATE_NOOP("bitcoin-core", "Transaction too large"),
+QT_TRANSLATE_NOOP("bitcoin-core", "Unable to allocate memory for -maxsigcachesize: '%s' MiB"),
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 find UTXO for external input"),
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 open %s for writing"),
QT_TRANSLATE_NOOP("bitcoin-core", "Unable to parse -maxuploadtarget: '%s'"),
QT_TRANSLATE_NOOP("bitcoin-core", "Unable to start HTTP server. See debug log for details."),
+QT_TRANSLATE_NOOP("bitcoin-core", "Unable to unload the wallet before migrating"),
QT_TRANSLATE_NOOP("bitcoin-core", "Unknown -blockfilterindex value %s."),
QT_TRANSLATE_NOOP("bitcoin-core", "Unknown address type '%s'"),
QT_TRANSLATE_NOOP("bitcoin-core", "Unknown change type '%s'"),
QT_TRANSLATE_NOOP("bitcoin-core", "Unknown network specified in -onlynet: '%s'"),
QT_TRANSLATE_NOOP("bitcoin-core", "Unknown new rules activated (versionbit %i)"),
+QT_TRANSLATE_NOOP("bitcoin-core", "Unsupported global logging level -loglevel=%s. Valid values: %s."),
QT_TRANSLATE_NOOP("bitcoin-core", "Unsupported logging category %s=%s."),
-QT_TRANSLATE_NOOP("bitcoin-core", "Upgrading UTXO 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)…"),
diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp
index f41da519df..092ffe7e5b 100644
--- a/src/qt/clientmodel.cpp
+++ b/src/qt/clientmodel.cpp
@@ -215,26 +215,26 @@ QString ClientModel::blocksDir() const
return GUIUtil::PathToQString(gArgs.GetBlocksDirPath());
}
-void ClientModel::TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, bool header)
+void ClientModel::TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, SyncType synctype)
{
- if (header) {
+ if (synctype == SyncType::HEADER_SYNC) {
// cache best headers time and height to reduce future cs_main locks
cachedBestHeaderHeight = tip.block_height;
cachedBestHeaderTime = tip.block_time;
- } else {
+ } else if (synctype == SyncType::BLOCK_SYNC) {
m_cached_num_blocks = tip.block_height;
WITH_LOCK(m_cached_tip_mutex, m_cached_tip_blocks = tip.block_hash;);
}
// Throttle GUI notifications about (a) blocks during initial sync, and (b) both blocks and headers during reindex.
- const bool throttle = (sync_state != SynchronizationState::POST_INIT && !header) || sync_state == SynchronizationState::INIT_REINDEX;
+ const bool throttle = (sync_state != SynchronizationState::POST_INIT && synctype == SyncType::BLOCK_SYNC) || sync_state == SynchronizationState::INIT_REINDEX;
const int64_t now = throttle ? GetTimeMillis() : 0;
- int64_t& nLastUpdateNotification = header ? nLastHeaderTipUpdateNotification : nLastBlockTipUpdateNotification;
+ int64_t& nLastUpdateNotification = synctype != SyncType::BLOCK_SYNC ? nLastHeaderTipUpdateNotification : nLastBlockTipUpdateNotification;
if (throttle && now < nLastUpdateNotification + count_milliseconds(MODEL_UPDATE_DELAY)) {
return;
}
- Q_EMIT numBlocksChanged(tip.block_height, QDateTime::fromSecsSinceEpoch(tip.block_time), verification_progress, header, sync_state);
+ Q_EMIT numBlocksChanged(tip.block_height, QDateTime::fromSecsSinceEpoch(tip.block_time), verification_progress, synctype, sync_state);
nLastUpdateNotification = now;
}
@@ -264,11 +264,11 @@ void ClientModel::subscribeToCoreSignals()
});
m_handler_notify_block_tip = m_node.handleNotifyBlockTip(
[this](SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress) {
- TipChanged(sync_state, tip, verification_progress, /*header=*/false);
+ TipChanged(sync_state, tip, verification_progress, SyncType::BLOCK_SYNC);
});
m_handler_notify_header_tip = m_node.handleNotifyHeaderTip(
- [this](SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress) {
- TipChanged(sync_state, tip, verification_progress, /*header=*/true);
+ [this](SynchronizationState sync_state, interfaces::BlockTip tip, bool presync) {
+ TipChanged(sync_state, tip, /*verification_progress=*/0.0, presync ? SyncType::HEADER_PRESYNC : SyncType::HEADER_SYNC);
});
}
diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h
index 81f03a58ec..4a6abd6a76 100644
--- a/src/qt/clientmodel.h
+++ b/src/qt/clientmodel.h
@@ -37,6 +37,12 @@ enum class BlockSource {
NETWORK
};
+enum class SyncType {
+ HEADER_PRESYNC,
+ HEADER_SYNC,
+ BLOCK_SYNC
+};
+
enum NumConnections {
CONNECTIONS_NONE = 0,
CONNECTIONS_IN = (1U << 0),
@@ -105,13 +111,13 @@ private:
//! A thread to interact with m_node asynchronously
QThread* const m_thread;
- void TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, bool header) EXCLUSIVE_LOCKS_REQUIRED(!m_cached_tip_mutex);
+ void TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, SyncType synctype) EXCLUSIVE_LOCKS_REQUIRED(!m_cached_tip_mutex);
void subscribeToCoreSignals();
void unsubscribeFromCoreSignals();
Q_SIGNALS:
void numConnectionsChanged(int count);
- void numBlocksChanged(int count, const QDateTime& blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state);
+ void numBlocksChanged(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType header, SynchronizationState sync_state);
void mempoolSizeChanged(long count, size_t mempoolSizeInBytes);
void networkActiveChanged(bool networkActive);
void alertsChanged(const QString &warnings);
diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui
index ead977296a..33308cd68c 100644
--- a/src/qt/forms/debugwindow.ui
+++ b/src/qt/forms/debugwindow.ui
@@ -1185,10 +1185,10 @@
<item row="6" column="0">
<widget class="QLabel" name="peerRelayTxesLabel">
<property name="toolTip">
- <string>Whether the peer requested us to relay transactions.</string>
+ <string>Whether we relay transactions to this peer (not available while the peer connection is being set up).</string>
</property>
<property name="text">
- <string>Wants Tx Relay</string>
+ <string>Transaction Relay</string>
</property>
</widget>
</item>
diff --git a/src/qt/forms/intro.ui b/src/qt/forms/intro.ui
index a1e94f99e6..9ab91f6aa9 100644
--- a/src/qt/forms/intro.ui
+++ b/src/qt/forms/intro.ui
@@ -203,7 +203,7 @@
<item>
<widget class="QLabel" name="lblExplanation1">
<property name="text">
- <string>When you click OK, %1 will begin to download and process the full %4 block chain (%2GB) starting with the earliest transactions in %3 when %4 initially launched.</string>
+ <string>When you click OK, %1 will begin to download and process the full %4 block chain (%2 GB) starting with the earliest transactions in %3 when %4 initially launched.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui
index 582f02132a..6be47e94fe 100644
--- a/src/qt/forms/optionsdialog.ui
+++ b/src/qt/forms/optionsdialog.ui
@@ -286,7 +286,7 @@
<item>
<widget class="QLineEdit" name="externalSignerPath">
<property name="toolTip">
- <string>Full path to a Bitcoin Core compatible script (e.g. C:\Downloads\hwi.exe or /Users/you/Downloads/hwi.py). Beware: malware can steal your coins!</string>
+ <string>Full path to a %1 compatible script (e.g. C:\Downloads\hwi.exe or /Users/you/Downloads/hwi.py). Beware: malware can steal your coins!</string>
</property>
</widget>
</item>
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 2551be0af3..6e88b57e08 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -428,7 +428,7 @@ void openDebugLogfile()
bool openBitcoinConf()
{
- fs::path pathConfig = GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME));
+ fs::path pathConfig = GetConfigFile(gArgs.GetPathArg("-conf", BITCOIN_CONF_FILENAME));
/* Create the file */
std::ofstream configFile{pathConfig, std::ios_base::app};
@@ -615,9 +615,10 @@ bool SetStartOnSystemStartup(bool fAutoStart)
else
{
char pszExePath[MAX_PATH+1];
- ssize_t r = readlink("/proc/self/exe", pszExePath, sizeof(pszExePath) - 1);
- if (r == -1)
+ ssize_t r = readlink("/proc/self/exe", pszExePath, sizeof(pszExePath));
+ if (r == -1 || r > MAX_PATH) {
return false;
+ }
pszExePath[r] = '\0';
fs::create_directories(GetAutostartDir());
@@ -982,7 +983,7 @@ void PrintSlotException(
std::string description = sender->metaObject()->className();
description += "->";
description += receiver->metaObject()->className();
- PrintExceptionContinue(exception, description.c_str());
+ PrintExceptionContinue(exception, description);
}
void ShowModalDialogAsynchronously(QDialog* dialog)
diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts
index 35d8201877..586240445e 100644
--- a/src/qt/locale/bitcoin_en.ts
+++ b/src/qt/locale/bitcoin_en.ts
@@ -55,7 +55,7 @@
</message>
<message>
<location line="-30"/>
- <location filename="../addressbookpage.cpp" line="+122"/>
+ <location filename="../addressbookpage.cpp" line="+128"/>
<source>&amp;Delete</source>
<translation>&amp;Delete</translation>
</message>
@@ -313,7 +313,12 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<context>
<name>BitcoinApplication</name>
<message>
- <location filename="../bitcoin.cpp" line="+433"/>
+ <location filename="../bitcoin.cpp" line="+288"/>
+ <source>Settings file %1 might be corrupt or invalid.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+178"/>
<source>Runaway exception</source>
<translation type="unfinished"></translation>
</message>
@@ -336,7 +341,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<context>
<name>BitcoinGUI</name>
<message>
- <location filename="../bitcoingui.cpp" line="+250"/>
+ <location filename="../bitcoingui.cpp" line="+253"/>
<source>&amp;Overview</source>
<translation>&amp;Overview</translation>
</message>
@@ -396,7 +401,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+127"/>
+ <location line="+160"/>
<source>&amp;Minimize</source>
<translation type="unfinished"></translation>
</message>
@@ -406,18 +411,18 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+392"/>
+ <location line="+393"/>
<source>Network activity disabled.</source>
<extracomment>A substring of the tooltip.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+422"/>
+ <location line="+439"/>
<source>Proxy is &lt;b&gt;enabled&lt;/b&gt;: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-1109"/>
+ <location line="-1160"/>
<source>Send coins to a Bitcoin address</source>
<translation>Send coins to a Bitcoin address</translation>
</message>
@@ -507,17 +512,17 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+4"/>
+ <location line="+10"/>
<source>Close All Wallets…</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+94"/>
+ <location line="+119"/>
<source>&amp;File</source>
<translation>&amp;File</translation>
</message>
<message>
- <location line="+18"/>
+ <location line="+20"/>
<source>&amp;Settings</source>
<translation>&amp;Settings</translation>
</message>
@@ -532,12 +537,12 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation>Tabs toolbar</translation>
</message>
<message>
- <location line="+456"/>
+ <location line="+457"/>
<source>Syncing Headers (%1%)…</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+47"/>
+ <location line="+58"/>
<source>Synchronizing with network…</source>
<translation type="unfinished"></translation>
</message>
@@ -562,7 +567,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-788"/>
+ <location line="-833"/>
<source>Request payments (generates QR codes and bitcoin: URIs)</source>
<translation type="unfinished"></translation>
</message>
@@ -577,12 +582,12 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+20"/>
+ <location line="+26"/>
<source>&amp;Command-line options</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
- <location line="+710"/>
+ <location line="+749"/>
<source>Processed %n block(s) of transaction history.</source>
<translation>
<numerusform>Processed %n block of transaction history.</numerusform>
@@ -630,7 +635,12 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation>Up to date</translation>
</message>
<message>
- <location line="-747"/>
+ <location line="-818"/>
+ <source>Ctrl+Q</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+26"/>
<source>Load Partially Signed Bitcoin Transaction</source>
<translation type="unfinished"></translation>
</message>
@@ -686,6 +696,18 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</message>
<message>
<location line="+7"/>
+ <source>Restore Wallet…</source>
+ <extracomment>Name of the menu item that restores wallet from a backup file.</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Restore a wallet from a backup file</source>
+ <extracomment>Status tip for Restore Wallet menu item</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
<source>Close all wallets</source>
<translation type="unfinished"></translation>
</message>
@@ -715,12 +737,41 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+63"/>
+ <location line="+6"/>
+ <source>Wallet Data</source>
+ <extracomment>Name of the wallet data file format.</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Load Wallet Backup</source>
+ <extracomment>The title for Restore Wallet File Windows</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Restore Wallet</source>
+ <extracomment>Title of pop-up window shown when the user is attempting to restore a wallet.</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Wallet Name</source>
+ <extracomment>Label of the input field where the name of the wallet is entered.</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+71"/>
<source>&amp;Window</source>
<translation type="unfinished">&amp;Window</translation>
</message>
<message>
- <location line="+12"/>
+ <location line="+3"/>
+ <source>Ctrl+M</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+9"/>
<source>Zoom</source>
<translation type="unfinished"></translation>
</message>
@@ -730,7 +781,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+257"/>
+ <location line="+258"/>
<source>%1 client</source>
<translation type="unfinished"></translation>
</message>
@@ -778,7 +829,12 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+158"/>
+ <location line="+17"/>
+ <source>Pre-syncing Headers (%1%)…</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+152"/>
<source>Error: %1</source>
<translation type="unfinished"></translation>
</message>
@@ -849,7 +905,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+17"/>
+ <location line="+23"/>
<source>Wallet is &lt;b&gt;encrypted&lt;/b&gt; and currently &lt;b&gt;unlocked&lt;/b&gt;</source>
<translation>Wallet is &lt;b&gt;encrypted&lt;/b&gt; and currently &lt;b&gt;unlocked&lt;/b&gt;</translation>
</message>
@@ -1022,7 +1078,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+155"/>
+ <location line="+154"/>
<source>yes</source>
<translation type="unfinished"></translation>
</message>
@@ -1061,7 +1117,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<context>
<name>CreateWalletActivity</name>
<message>
- <location filename="../walletcontroller.cpp" line="+243"/>
+ <location filename="../walletcontroller.cpp" line="+244"/>
<source>Create Wallet</source>
<extracomment>Title of window indicating the progress of creation of a new wallet.</extracomment>
<translation type="unfinished"></translation>
@@ -1073,7 +1129,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+29"/>
+ <location line="+33"/>
<source>Create wallet failed</source>
<translation type="unfinished"></translation>
</message>
@@ -1087,6 +1143,11 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<source>Can&apos;t list signers</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <location line="+3"/>
+ <source>Too many external signers found</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>CreateWalletDialog</name>
@@ -1276,7 +1337,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<context>
<name>HelpMessageDialog</name>
<message>
- <location filename="../utilitydialog.cpp" line="+37"/>
+ <location filename="../utilitydialog.cpp" line="+38"/>
<source>version</source>
<translation type="unfinished">version</translation>
</message>
@@ -1286,7 +1347,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+19"/>
+ <location line="+18"/>
<source>Command-line options</source>
<translation type="unfinished"></translation>
</message>
@@ -1309,12 +1370,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+157"/>
- <source>When you click OK, %1 will begin to download and process the full %4 block chain (%2GB) starting with the earliest transactions in %3 when %4 initially launched.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+32"/>
+ <location line="+189"/>
<source>Limit block chain storage to</source>
<translation type="unfinished"></translation>
</message>
@@ -1334,7 +1390,12 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+10"/>
+ <location line="-10"/>
+ <source>When you click OK, %1 will begin to download and process the full %4 block chain (%2 GB) starting with the earliest transactions in %3 when %4 initially launched.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+20"/>
<source>If you have chosen to limit block chain storage (pruning), the historical data must still be downloaded and processed, but will be deleted afterward to keep your disk usage low.</source>
<translation type="unfinished"></translation>
</message>
@@ -1353,20 +1414,29 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<source>Bitcoin</source>
<translation type="unfinished">Bitcoin</translation>
</message>
- <message>
+ <message numerus="yes">
<location line="+162"/>
- <source>%1 GB of space available</source>
- <translation type="unfinished"></translation>
+ <source>%n GB of space available</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ <numerusform></numerusform>
+ </translation>
</message>
- <message>
+ <message numerus="yes">
<location line="+2"/>
- <source>(of %1 GB needed)</source>
- <translation type="unfinished"></translation>
+ <source>(of %n GB needed)</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ <numerusform></numerusform>
+ </translation>
</message>
- <message>
+ <message numerus="yes">
<location line="+3"/>
- <source>(%1 GB needed for full chain)</source>
- <translation type="unfinished"></translation>
+ <source>(%n GB needed for full chain)</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ <numerusform></numerusform>
+ </translation>
</message>
<message>
<location line="+72"/>
@@ -1411,7 +1481,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<context>
<name>LoadWalletsActivity</name>
<message>
- <location filename="../walletcontroller.cpp" line="+69"/>
+ <location filename="../walletcontroller.cpp" line="+74"/>
<source>Load Wallets</source>
<extracomment>Title of progress window which is displayed when wallets are being loaded.</extracomment>
<translation type="unfinished"></translation>
@@ -1448,7 +1518,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<message>
<location line="+7"/>
<location line="+26"/>
- <location filename="../modaloverlay.cpp" line="+152"/>
+ <location filename="../modaloverlay.cpp" line="+155"/>
<source>Unknown…</source>
<translation type="unfinished"></translation>
</message>
@@ -1489,15 +1559,20 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../modaloverlay.cpp" line="-118"/>
+ <location filename="../modaloverlay.cpp" line="-121"/>
<source>%1 is currently syncing. It will download headers and blocks from peers and validate them until reaching the tip of the block chain.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+124"/>
+ <location line="+127"/>
<source>Unknown. Syncing Headers (%1, %2%)…</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <location line="+5"/>
+ <source>Unknown. Pre-syncing Headers (%1, %2%)…</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>OpenURIDialog</name>
@@ -1521,7 +1596,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<context>
<name>OpenWalletActivity</name>
<message>
- <location filename="../walletcontroller.cpp" line="-42"/>
+ <location filename="../walletcontroller.cpp" line="-46"/>
<source>Open wallet failed</source>
<translation type="unfinished"></translation>
</message>
@@ -1604,7 +1679,12 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+272"/>
+ <location line="+227"/>
+ <source>Options set in this dialog are overridden by the command line:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+45"/>
<source>Open the %1 configuration file from the working directory.</source>
<translation type="unfinished"></translation>
</message>
@@ -1916,12 +1996,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+65"/>
- <source>Options set in this dialog are overridden by the command line or in the configuration file:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+141"/>
+ <location line="+206"/>
<source>&amp;OK</source>
<translation>&amp;OK</translation>
</message>
@@ -1931,7 +2006,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation>&amp;Cancel</translation>
</message>
<message>
- <location filename="../optionsdialog.cpp" line="+99"/>
+ <location filename="../optionsdialog.cpp" line="+96"/>
<source>Compiled without external signing support (required for external signing)</source>
<extracomment>&quot;External signing&quot; means using devices such as hardware wallets.</extracomment>
<translation type="unfinished"></translation>
@@ -1942,28 +2017,37 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation>default</translation>
</message>
<message>
- <location line="+81"/>
+ <location line="+86"/>
<source>none</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+97"/>
+ <location line="+107"/>
<source>Confirm options reset</source>
+ <extracomment>Window title text of pop-up window shown when the user has chosen to reset options.</extracomment>
<translation>Confirm options reset</translation>
</message>
<message>
- <location line="+1"/>
- <location line="+70"/>
+ <location line="-9"/>
+ <location line="+79"/>
<source>Client restart required to activate changes.</source>
+ <extracomment>Text explaining that the settings changed will not come into effect until the client is restarted.</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-75"/>
+ <source>Current settings will be backed up at &quot;%1&quot;.</source>
+ <extracomment>Text explaining to the user that the client&apos;s current settings will be backed up at a specific location. %1 is a stand-in argument for the backup location&apos;s path.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-70"/>
+ <location line="+3"/>
<source>Client will be shut down. Do you want to proceed?</source>
+ <extracomment>Text asking the user to confirm if they would like to proceed with a client shutdown.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+18"/>
+ <location line="+20"/>
<source>Configuration options</source>
<extracomment>Window title text of pop-up box that allows opening up of configuration file.</extracomment>
<translation type="unfinished"></translation>
@@ -2006,6 +2090,14 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</message>
</context>
<context>
+ <name>OptionsModel</name>
+ <message>
+ <location filename="../optionsmodel.cpp" line="+204"/>
+ <source>Could not read setting &quot;%1&quot;, %2.</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>OverviewPage</name>
<message>
<location filename="../forms/overviewpage.ui" line="+14"/>
@@ -2099,7 +2191,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../overviewpage.cpp" line="+187"/>
+ <location filename="../overviewpage.cpp" line="+185"/>
<source>Privacy mode activated for the Overview tab. To unmask the values, uncheck Settings-&gt;Mask values.</source>
<translation type="unfinished"></translation>
</message>
@@ -2271,7 +2363,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<context>
<name>PaymentServer</name>
<message>
- <location filename="../paymentserver.cpp" line="+173"/>
+ <location filename="../paymentserver.cpp" line="+152"/>
<source>Payment request error</source>
<translation type="unfinished"></translation>
</message>
@@ -2281,7 +2373,7 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+50"/>
+ <location line="+48"/>
<location line="+16"/>
<location line="+6"/>
<location line="+7"/>
@@ -2315,7 +2407,7 @@ If you are receiving this error you should request the merchant provide a BIP21
<context>
<name>PeerTableModel</name>
<message>
- <location filename="../peertablemodel.h" line="+108"/>
+ <location filename="../peertablemodel.h" line="+112"/>
<source>User Agent</source>
<extracomment>Title of Peers Table column which contains the peer&apos;s User Agent string.</extracomment>
<translation type="unfinished"></translation>
@@ -2327,12 +2419,18 @@ If you are receiving this error you should request the merchant provide a BIP21
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-15"/>
+ <location line="-18"/>
<source>Peer</source>
<extracomment>Title of Peers Table column which contains a unique number used to identify a connection.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
+ <location line="+3"/>
+ <source>Age</source>
+ <extracomment>Title of Peers Table column which indicates the duration (length of time) since the peer connection started.</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
<location line="+6"/>
<source>Direction</source>
<extracomment>Title of Peers Table column which indicates the direction the peer connection was initiated from.</extracomment>
@@ -2369,7 +2467,7 @@ If you are receiving this error you should request the merchant provide a BIP21
<translation type="unfinished">Network</translation>
</message>
<message>
- <location filename="../peertablemodel.cpp" line="+79"/>
+ <location filename="../peertablemodel.cpp" line="+78"/>
<source>Inbound</source>
<extracomment>An Inbound Connection from a Peer.</extracomment>
<translation type="unfinished"></translation>
@@ -2384,17 +2482,22 @@ If you are receiving this error you should request the merchant provide a BIP21
<context>
<name>QObject</name>
<message>
- <location filename="../bitcoinunits.cpp" line="+215"/>
+ <location filename="../bitcoinunits.cpp" line="+197"/>
<source>Amount</source>
<translation type="unfinished">Amount</translation>
</message>
<message>
- <location filename="../guiutil.cpp" line="+127"/>
+ <location filename="../guiutil.cpp" line="+129"/>
<source>Enter a Bitcoin address (e.g. %1)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+546"/>
+ <location line="+288"/>
+ <source>Ctrl+W</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+256"/>
<source>Unroutable</source>
<translation type="unfinished"></translation>
</message>
@@ -2446,23 +2549,27 @@ If you are receiving this error you should request the merchant provide a BIP21
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+15"/>
+ <location line="+13"/>
+ <location line="+12"/>
<source>%1 d</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+2"/>
+ <location line="-11"/>
+ <location line="+12"/>
<source>%1 h</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+2"/>
+ <location line="-11"/>
+ <location line="+12"/>
<source>%1 m</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+2"/>
- <location line="+28"/>
+ <location line="-10"/>
+ <location line="+11"/>
+ <location line="+26"/>
<source>%1 s</source>
<translation type="unfinished"></translation>
</message>
@@ -2556,7 +2663,7 @@ If you are receiving this error you should request the merchant provide a BIP21
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../bitcoin.cpp" line="-276"/>
+ <location filename="../bitcoin.cpp" line="-294"/>
<source>Do you want to reset settings to default values, or to abort without making changes?</source>
<extracomment>Explanatory text shown on startup when the settings file cannot be read. Prompts user to make a choice between resetting or aborting.</extracomment>
<translation type="unfinished"></translation>
@@ -2568,7 +2675,7 @@ If you are receiving this error you should request the merchant provide a BIP21
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+373"/>
+ <location line="+393"/>
<source>Error: Specified data directory &quot;%1&quot; does not exist.</source>
<translation type="unfinished"></translation>
</message>
@@ -2583,12 +2690,12 @@ If you are receiving this error you should request the merchant provide a BIP21
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+71"/>
+ <location line="+74"/>
<source>%1 didn&apos;t yet exit safely…</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../modaloverlay.cpp" line="-35"/>
+ <location filename="../modaloverlay.cpp" line="-40"/>
<source>unknown</source>
<translation type="unfinished"></translation>
</message>
@@ -2672,7 +2779,7 @@ If you are receiving this error you should request the merchant provide a BIP21
<location line="+26"/>
<location line="+26"/>
<location line="+26"/>
- <location filename="../rpcconsole.h" line="+139"/>
+ <location filename="../rpcconsole.h" line="+143"/>
<source>N/A</source>
<translation>N/A</translation>
</message>
@@ -2791,7 +2898,7 @@ If you are receiving this error you should request the merchant provide a BIP21
</message>
<message>
<location line="+68"/>
- <location filename="../rpcconsole.cpp" line="+1160"/>
+ <location filename="../rpcconsole.cpp" line="+1155"/>
<source>Select a peer to view detailed information.</source>
<translation type="unfinished"></translation>
</message>
@@ -2833,34 +2940,37 @@ If you are receiving this error you should request the merchant provide a BIP21
<message>
<location line="+23"/>
<source>Whether we relay addresses to this peer.</source>
- <extracomment>Tooltip text for the Address Relay field in the peer details area.</extracomment>
+ <extracomment>Tooltip text for the Address Relay field in the peer details area, which displays whether we relay addresses to this peer (Yes/No).</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location line="+3"/>
<source>Address Relay</source>
+ <extracomment>Text title for the Address Relay field in the peer details area, which displays whether we relay addresses to this peer (Yes/No).</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location line="+23"/>
- <source>Total number of addresses processed, excluding those dropped due to rate-limiting.</source>
- <extracomment>Tooltip text for the Addresses Processed field in the peer details area.</extracomment>
+ <source>The total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</source>
+ <extracomment>Tooltip text for the Addresses Processed field in the peer details area, which displays the total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+3"/>
- <source>Addresses Processed</source>
+ <location line="+26"/>
+ <source>The total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</source>
+ <extracomment>Tooltip text for the Addresses Rate-Limited field in the peer details area, which displays the total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+23"/>
- <source>Total number of addresses dropped due to rate-limiting.</source>
- <extracomment>Tooltip text for the Addresses Rate-Limited field in the peer details area.</extracomment>
+ <location line="-23"/>
+ <source>Addresses Processed</source>
+ <extracomment>Text title for the Addresses Processed field in the peer details area, which displays the total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+3"/>
+ <location line="+26"/>
<source>Addresses Rate-Limited</source>
+ <extracomment>Text title for the Addresses Rate-Limited field in the peer details area, which displays the total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
@@ -3021,7 +3131,7 @@ If you are receiving this error you should request the merchant provide a BIP21
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../rpcconsole.cpp" line="-201"/>
+ <location filename="../rpcconsole.cpp" line="-202"/>
<source>In:</source>
<translation type="unfinished"></translation>
</message>
@@ -3066,12 +3176,12 @@ If you are receiving this error you should request the merchant provide a BIP21
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+41"/>
+ <location line="+42"/>
<source>Never</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../rpcconsole.cpp" line="-457"/>
+ <location filename="../rpcconsole.cpp" line="-458"/>
<source>Inbound: initiated by peer</source>
<extracomment>Explanatory text for an inbound peer connection.</extracomment>
<translation type="unfinished"></translation>
@@ -3177,7 +3287,7 @@ If you are receiving this error you should request the merchant provide a BIP21
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+25"/>
+ <location line="+26"/>
<source>&amp;Copy IP/Netmask</source>
<extracomment>Context menu action to copy the IP/Netmask of a banned peer. IP/Netmask is the combination of a peer&apos;s IP address and its Netmask. For IP address, see: https://en.wikipedia.org/wiki/IP_address.</extracomment>
<translation type="unfinished"></translation>
@@ -3193,17 +3303,37 @@ If you are receiving this error you should request the merchant provide a BIP21
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+77"/>
+ <location line="+78"/>
<source>Executing command without any wallet</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-2"/>
+ <location line="+316"/>
+ <source>Ctrl+I</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Ctrl+T</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Ctrl+N</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Ctrl+P</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-321"/>
<source>Executing command using &quot;%1&quot; wallet</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-145"/>
+ <location line="-146"/>
<source>Welcome to the %1 RPC console.
Use up and down arrows to navigate history, and %2 to clear screen.
Use %3 and %4 to increase or decrease the font size.
@@ -3215,7 +3345,7 @@ For more information on using this console, type %6.
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+155"/>
+ <location line="+156"/>
<source>Executing…</source>
<extracomment>A console message indicating an entered command is currently being executed.</extracomment>
<translation type="unfinished"></translation>
@@ -3231,7 +3361,7 @@ For more information on using this console, type %6.
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../rpcconsole.h" line="-41"/>
+ <location filename="../rpcconsole.h" line="-42"/>
<source>Unknown</source>
<translation type="unfinished"></translation>
</message>
@@ -3446,7 +3576,7 @@ For more information on using this console, type %6.
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+41"/>
+ <location line="+38"/>
<source>(no label)</source>
<translation type="unfinished"></translation>
</message>
@@ -3467,10 +3597,43 @@ For more information on using this console, type %6.
</message>
</context>
<context>
+ <name>RestoreWalletActivity</name>
+ <message>
+ <location filename="../walletcontroller.cpp" line="+49"/>
+ <source>Restore Wallet</source>
+ <extracomment>Title of progress window which is displayed when wallets are being restored.</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Restoring Wallet &lt;b&gt;%1&lt;/b&gt;…</source>
+ <extracomment>Descriptive text of the restore wallets progress window which indicates to the user that wallets are currently being restored.</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+19"/>
+ <source>Restore wallet failed</source>
+ <extracomment>Title of message box which is displayed when the wallet could not be restored.</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Restore wallet warning</source>
+ <extracomment>Title of message box which is displayed when the wallet is restored with some warning.</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Restore wallet message</source>
+ <extracomment>Title of message box which is displayed when the wallet is successfully restored.</extracomment>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>SendCoinsDialog</name>
<message>
<location filename="../forms/sendcoinsdialog.ui" line="+14"/>
- <location filename="../sendcoinsdialog.cpp" line="+752"/>
+ <location filename="../sendcoinsdialog.cpp" line="+757"/>
<source>Send Coins</source>
<translation>Send Coins</translation>
</message>
@@ -3657,7 +3820,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
<translation>S&amp;end</translation>
</message>
<message>
- <location filename="../sendcoinsdialog.cpp" line="-653"/>
+ <location filename="../sendcoinsdialog.cpp" line="-658"/>
<source>Copy quantity</source>
<translation type="unfinished"></translation>
</message>
@@ -3692,7 +3855,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+76"/>
+ <location line="+74"/>
<source>%1 (%2 blocks)</source>
<translation type="unfinished"></translation>
</message>
@@ -3739,29 +3902,29 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+67"/>
+ <location line="+66"/>
<source>To review recipient list click &quot;Show Details…&quot;</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+44"/>
+ <location line="+60"/>
<source>Sign failed</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+6"/>
+ <location line="+5"/>
<source>External signer not found</source>
<extracomment>&quot;External signer&quot; means using devices such as hardware wallets.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+6"/>
+ <location line="+5"/>
<source>External signer failure</source>
<extracomment>&quot;External signer&quot; means using devices such as hardware wallets.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+58"/>
+ <location line="-34"/>
<source>Save Transaction Data</source>
<translation type="unfinished"></translation>
</message>
@@ -3777,17 +3940,17 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+176"/>
+ <location line="+266"/>
<source>External balance:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-303"/>
+ <location line="-315"/>
<source>or</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-19"/>
+ <location line="-18"/>
<source>You can increase the fee later (signals Replace-By-Fee, BIP-125).</source>
<translation type="unfinished"></translation>
</message>
@@ -3826,17 +3989,17 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+14"/>
+ <location line="+13"/>
<source>Total Amount</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+25"/>
+ <location line="+99"/>
<source>Confirm send coins</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+284"/>
+ <location line="+222"/>
<source>Watch-only balance:</source>
<translation type="unfinished"></translation>
</message>
@@ -3875,13 +4038,8 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
<source>A fee higher than %1 is considered an absurdly high fee.</source>
<translation type="unfinished"></translation>
</message>
- <message>
- <location line="+3"/>
- <source>Payment request expired.</source>
- <translation type="unfinished"></translation>
- </message>
<message numerus="yes">
- <location line="+124"/>
+ <location line="+123"/>
<source>Estimated to begin confirmation within %n block(s).</source>
<translation>
<numerusform>Estimated to begin confirmation within %n block.</numerusform>
@@ -3917,14 +4075,12 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
<context>
<name>SendCoinsEntry</name>
<message>
- <location filename="../forms/sendcoinsentry.ui" line="+155"/>
- <location line="+550"/>
- <location line="+533"/>
+ <location filename="../forms/sendcoinsentry.ui" line="+151"/>
<source>A&amp;mount:</source>
<translation>A&amp;mount:</translation>
</message>
<message>
- <location line="-1199"/>
+ <location line="-116"/>
<source>Pay &amp;To:</source>
<translation>Pay &amp;To:</translation>
</message>
@@ -3960,13 +4116,11 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
</message>
<message>
<location line="+7"/>
- <location line="+562"/>
- <location line="+533"/>
<source>Remove this entry</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-1035"/>
+ <location line="+60"/>
<source>The amount to send in the selected unit</source>
<translation type="unfinished"></translation>
</message>
@@ -3991,17 +4145,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+443"/>
- <source>This is an unauthenticated payment request.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+529"/>
- <source>This is an authenticated payment request.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="-1023"/>
+ <location line="-51"/>
<location line="+3"/>
<source>Enter a label for this address to add it to the list of used addresses</source>
<translation type="unfinished"></translation>
@@ -4011,23 +4155,11 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
<source>A message that was attached to the bitcoin: URI which will be stored with the transaction for your reference. Note: This message will not be sent over the Bitcoin network.</source>
<translation type="unfinished"></translation>
</message>
- <message>
- <location line="+448"/>
- <location line="+529"/>
- <source>Pay To:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="-495"/>
- <location line="+533"/>
- <source>Memo:</source>
- <translation type="unfinished"></translation>
- </message>
</context>
<context>
<name>SendConfirmationDialog</name>
<message>
- <location filename="../sendcoinsdialog.h" line="+131"/>
+ <location filename="../sendcoinsdialog.h" line="+144"/>
<source>Send</source>
<translation type="unfinished"></translation>
</message>
@@ -4274,42 +4406,43 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
<context>
<name>TransactionDesc</name>
<message>
- <location filename="../transactiondesc.cpp" line="+40"/>
+ <location filename="../transactiondesc.cpp" line="+43"/>
<source>conflicted with a transaction with %1 confirmations</source>
+ <extracomment>Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an unconfirmed transaction that conflicts with a confirmed transaction.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+3"/>
- <source>0/unconfirmed, %1</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+0"/>
- <source>in memory pool</source>
+ <location line="+7"/>
+ <source>0/unconfirmed, in memory pool</source>
+ <extracomment>Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an unconfirmed transaction that is in the memory pool.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+0"/>
- <source>not in memory pool</source>
+ <location line="+5"/>
+ <source>0/unconfirmed, not in memory pool</source>
+ <extracomment>Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an unconfirmed transaction that is not in the memory pool.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-1"/>
+ <location line="+6"/>
<source>abandoned</source>
+ <extracomment>Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an abandoned transaction.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+3"/>
+ <location line="+8"/>
<source>%1/unconfirmed</source>
+ <extracomment>Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents a transaction confirmed in at least one block, but less than 6 blocks.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+2"/>
+ <location line="+5"/>
<source>%1 confirmations</source>
+ <extracomment>Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents a transaction confirmed in 6 or more blocks.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+51"/>
+ <location line="+50"/>
<source>Status</source>
<translation type="unfinished"></translation>
</message>
@@ -4507,7 +4640,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
<context>
<name>TransactionTableModel</name>
<message>
- <location filename="../transactiontablemodel.cpp" line="+260"/>
+ <location filename="../transactiontablemodel.cpp" line="+261"/>
<source>Date</source>
<translation type="unfinished">Date</translation>
</message>
@@ -4844,7 +4977,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
<context>
<name>WalletController</name>
<message>
- <location filename="../walletcontroller.cpp" line="-259"/>
+ <location filename="../walletcontroller.cpp" line="-344"/>
<source>Close wallet</source>
<translation type="unfinished"></translation>
</message>
@@ -4884,19 +5017,19 @@ Go to File &gt; Open Wallet to load a wallet.
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+154"/>
- <location line="+9"/>
+ <location line="+151"/>
<location line="+10"/>
+ <location line="+18"/>
<source>Error</source>
<translation type="unfinished">Error</translation>
</message>
<message>
- <location line="-19"/>
+ <location line="-28"/>
<source>Unable to decode PSBT from clipboard (invalid base64)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+5"/>
+ <location line="+6"/>
<source>Load Transaction Data</source>
<translation type="unfinished"></translation>
</message>
@@ -4911,7 +5044,7 @@ Go to File &gt; Open Wallet to load a wallet.
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+10"/>
+ <location line="+18"/>
<source>Unable to decode PSBT</source>
<translation type="unfinished"></translation>
</message>
@@ -4919,12 +5052,12 @@ Go to File &gt; Open Wallet to load a wallet.
<context>
<name>WalletModel</name>
<message>
- <location filename="../walletmodel.cpp" line="+221"/>
+ <location filename="../walletmodel.cpp" line="+232"/>
<source>Send Coins</source>
<translation type="unfinished">Send Coins</translation>
</message>
<message>
- <location line="+260"/>
+ <location line="+263"/>
<location line="+52"/>
<location line="+15"/>
<location line="+5"/>
@@ -5001,7 +5134,7 @@ Go to File &gt; Open Wallet to load a wallet.
<context>
<name>WalletView</name>
<message>
- <location filename="../walletview.cpp" line="+52"/>
+ <location filename="../walletview.cpp" line="+51"/>
<source>&amp;Export</source>
<translation type="unfinished">&amp;Export</translation>
</message>
@@ -5011,7 +5144,7 @@ Go to File &gt; Open Wallet to load a wallet.
<translation type="unfinished">Export the data in the current tab to a file</translation>
</message>
<message>
- <location line="+164"/>
+ <location line="+162"/>
<source>Backup Wallet</source>
<translation type="unfinished"></translation>
</message>
@@ -5060,12 +5193,12 @@ Go to File &gt; Open Wallet to load a wallet.
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+3"/>
+ <location line="+7"/>
<source>-maxtxfee is set very high! Fees this large could be paid on a single transaction.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+3"/>
+ <location line="+18"/>
<source>Cannot downgrade wallet from version %i to version %i. Wallet version unchanged.</source>
<translation type="unfinished"></translation>
</message>
@@ -5095,7 +5228,7 @@ Go to File &gt; Open Wallet to load a wallet.
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+3"/>
+ <location line="+6"/>
<source>Error: Dumpfile format record is incorrect. Got &quot;%s&quot;, expected &quot;format&quot;.</source>
<translation type="unfinished"></translation>
</message>
@@ -5110,17 +5243,12 @@ Go to File &gt; Open Wallet to load a wallet.
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+3"/>
+ <location line="+6"/>
<source>Error: Legacy wallets only support the &quot;legacy&quot;, &quot;p2sh-segwit&quot;, and &quot;bech32&quot; address types</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+3"/>
- <source>Error: Listening for incoming connections failed (listen returned error %s)</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+2"/>
+ <location line="+12"/>
<source>Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.</source>
<translation type="unfinished"></translation>
</message>
@@ -5130,7 +5258,7 @@ Go to File &gt; Open Wallet to load a wallet.
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+3"/>
+ <location line="+6"/>
<source>Invalid amount for -maxtxfee=&lt;amount&gt;: &apos;%s&apos; (must be at least the minrelay fee of %s to prevent stuck transactions)</source>
<translation type="unfinished"></translation>
</message>
@@ -5160,7 +5288,7 @@ Go to File &gt; Open Wallet to load a wallet.
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+3"/>
+ <location line="+10"/>
<source>Please check that your computer&apos;s date and time are correct! If your clock is wrong, %s will not work properly.</source>
<translation type="unfinished"></translation>
</message>
@@ -5176,6 +5304,11 @@ Go to File &gt; Open Wallet to load a wallet.
</message>
<message>
<location line="+2"/>
+ <source>Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
<source>Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)</source>
<translation type="unfinished"></translation>
</message>
@@ -5240,7 +5373,17 @@ Go to File &gt; Open Wallet to load a wallet.
<translation type="unfinished"></translation>
</message>
<message>
+ <location line="+11"/>
+ <source>Unsupported chainstate database format found. Please restart with -reindex-chainstate. This will rebuild the chainstate database.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
<location line="+3"/>
+ <source>Wallet created successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
<source>Warning: Dumpfile wallet format &quot;%s&quot; does not match command line specified format &quot;%s&quot;.</source>
<translation type="unfinished"></translation>
</message>
@@ -5265,7 +5408,7 @@ Go to File &gt; Open Wallet to load a wallet.
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+3"/>
+ <location line="+9"/>
<source>%s is set very high!</source>
<translation type="unfinished"></translation>
</message>
@@ -5300,12 +5443,37 @@ Go to File &gt; Open Wallet to load a wallet.
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-58"/>
+ <location line="-79"/>
<source>The -txindex upgrade started by a previous version cannot be completed. Restart with the previous version or run a full -reindex.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-69"/>
+ <location line="-122"/>
+ <source>%s request to listen on port %u. This port is considered &quot;bad&quot; and thus it is unlikely that any Bitcoin Core peers connect to it. See doc/p2p-bad-ports.md for details and a full list.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+7"/>
+ <source>-reindex-chainstate option is not compatible with -blockfilterindex. Please temporarily disable blockfilterindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>-reindex-chainstate option is not compatible with -coinstatsindex. Please temporarily disable coinstatsindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>-reindex-chainstate option is not compatible with -txindex. Please temporarily disable txindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
+ <source>Assumed-valid: last wallet synchronisation goes beyond available block data. You need to wait for the background validation chain to download more blocks.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
<source>Cannot provide specific connections and have addrman find outgoing connections at the same time.</source>
<translation type="unfinished"></translation>
</message>
@@ -5315,7 +5483,73 @@ Go to File &gt; Open Wallet to load a wallet.
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+118"/>
+ <location line="+9"/>
+ <source>Error: Address book data in wallet cannot be identified to belong to migrated wallets</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>Error: Duplicate descriptors created during migration. Your wallet may be corrupted.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+6"/>
+ <source>Error: Transaction %s in wallet cannot be identified to belong to migrated wallets</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Error: Unable to produce descriptors for this legacy wallet. Make sure the wallet is unlocked first</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Failed to rename invalid peers.dat file. Please move or delete it and try again.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+9"/>
+ <source>Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+21"/>
+ <source>Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is explicitly forbidden: -onion=0</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is not provided: none of -proxy, -onion or -listenonion is given</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+59"/>
+ <source>Unrecognized descriptor found. Loading wallet %s
+
+The wallet might had been created on a newer version.
+Please try running the latest software version.
+</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+5"/>
+ <source>Unsupported category-specific logging level -loglevel=%s. Expected -loglevel=&lt;category&gt;:&lt;loglevel&gt;. Valid categories: %s. Valid loglevels: %s.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+24"/>
+ <source>
+Unable to cleanup failed migration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>
+Unable to restore backup of wallet.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
<source>Config setting for %s only applied on %s network when in [%s] section.</source>
<translation type="unfinished"></translation>
</message>
@@ -5416,7 +5650,12 @@ Go to File &gt; Open Wallet to load a wallet.
</message>
<message>
<location line="+1"/>
- <source>Error upgrading chainstate database</source>
+ <source>Error: Could not add watchonly tx to watchonly wallet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Error: Could not delete watchonly transactions</source>
<translation type="unfinished"></translation>
</message>
<message>
@@ -5436,6 +5675,11 @@ Go to File &gt; Open Wallet to load a wallet.
</message>
<message>
<location line="+1"/>
+ <source>Error: Failed to create new watchonly wallet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
<source>Error: Got key that was not hex: %s</source>
<translation type="unfinished"></translation>
</message>
@@ -5461,11 +5705,46 @@ Go to File &gt; Open Wallet to load a wallet.
</message>
<message>
<location line="+1"/>
+ <source>Error: Not all watchonly txs could be deleted</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Error: This wallet already uses SQLite</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Error: This wallet is already a descriptor wallet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Error: Unable to begin reading all records in the database</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Error: Unable to make a backup of your wallet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
<source>Error: Unable to parse version %u as a uint32_t</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="+1"/>
+ <source>Error: Unable to read all records in the database</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Error: Unable to remove watchonly address book data</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
<source>Error: Unable to write record to new wallet</source>
<translation type="unfinished"></translation>
</message>
@@ -5566,6 +5845,11 @@ Go to File &gt; Open Wallet to load a wallet.
</message>
<message>
<location line="+1"/>
+ <source>Listening for incoming connections failed (listen returned error %s)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
<source>Loading P2P addresses…</source>
<translation type="unfinished"></translation>
</message>
@@ -5606,11 +5890,6 @@ Go to File &gt; Open Wallet to load a wallet.
</message>
<message>
<location line="+1"/>
- <source>No proxy server specified. Use -proxy=&lt;ip&gt; or -proxy=&lt;ip:port&gt;.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+1"/>
<source>Not enough file descriptors available.</source>
<translation type="unfinished"></translation>
</message>
@@ -5621,11 +5900,6 @@ Go to File &gt; Open Wallet to load a wallet.
</message>
<message>
<location line="+1"/>
- <source>Prune mode is incompatible with -coinstatsindex.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+1"/>
<source>Prune mode is incompatible with -txindex.</source>
<translation type="unfinished"></translation>
</message>
@@ -5776,6 +6050,11 @@ Go to File &gt; Open Wallet to load a wallet.
</message>
<message>
<location line="+1"/>
+ <source>Unable to allocate memory for -maxsigcachesize: &apos;%s&apos; MiB</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
<source>Unable to bind to %s on this computer (bind returned error %s)</source>
<translation type="unfinished"></translation>
</message>
@@ -5791,6 +6070,11 @@ Go to File &gt; Open Wallet to load a wallet.
</message>
<message>
<location line="+1"/>
+ <source>Unable to find UTXO for external input</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
<source>Unable to generate initial keys</source>
<translation type="unfinished"></translation>
</message>
@@ -5816,6 +6100,11 @@ Go to File &gt; Open Wallet to load a wallet.
</message>
<message>
<location line="+1"/>
+ <source>Unable to unload the wallet before migrating</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
<source>Unknown -blockfilterindex value %s.</source>
<translation type="unfinished"></translation>
</message>
@@ -5841,12 +6130,12 @@ Go to File &gt; Open Wallet to load a wallet.
</message>
<message>
<location line="+1"/>
- <source>Unsupported logging category %s=%s.</source>
+ <source>Unsupported global logging level -loglevel=%s. Valid values: %s.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="+1"/>
- <source>Upgrading UTXO database</source>
+ <source>Unsupported logging category %s=%s.</source>
<translation type="unfinished"></translation>
</message>
<message>
@@ -5870,7 +6159,7 @@ Go to File &gt; Open Wallet to load a wallet.
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../bitcoin.cpp" line="-496"/>
+ <location filename="../bitcoin.cpp" line="-519"/>
<source>Settings file could not be read</source>
<translation type="unfinished"></translation>
</message>
diff --git a/src/qt/locale/bitcoin_en.xlf b/src/qt/locale/bitcoin_en.xlf
index 30ee5e4de6..a25ccfca72 100644
--- a/src/qt/locale/bitcoin_en.xlf
+++ b/src/qt/locale/bitcoin_en.xlf
@@ -45,7 +45,7 @@
<trans-unit id="_msg11">
<source xml:space="preserve">&amp;Delete</source>
<context-group purpose="location"><context context-type="linenumber">101</context></context-group>
- <context-group purpose="location"><context context-type="sourcefile">../addressbookpage.cpp</context><context context-type="linenumber">122</context></context-group>
+ <context-group purpose="location"><context context-type="sourcefile">../addressbookpage.cpp</context><context context-type="linenumber">128</context></context-group>
</trans-unit>
</group>
</body></file>
@@ -53,62 +53,62 @@
<group restype="x-trolltech-linguist-context" resname="AddressBookPage">
<trans-unit id="_msg12">
<source xml:space="preserve">Choose the address to send coins to</source>
- <context-group purpose="location"><context context-type="linenumber">84</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">90</context></context-group>
</trans-unit>
<trans-unit id="_msg13">
<source xml:space="preserve">Choose the address to receive coins with</source>
- <context-group purpose="location"><context context-type="linenumber">85</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">91</context></context-group>
</trans-unit>
<trans-unit id="_msg14">
<source xml:space="preserve">C&amp;hoose</source>
- <context-group purpose="location"><context context-type="linenumber">90</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">96</context></context-group>
</trans-unit>
<trans-unit id="_msg15">
<source xml:space="preserve">Sending addresses</source>
- <context-group purpose="location"><context context-type="linenumber">96</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">102</context></context-group>
</trans-unit>
<trans-unit id="_msg16">
<source xml:space="preserve">Receiving addresses</source>
- <context-group purpose="location"><context context-type="linenumber">97</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">103</context></context-group>
</trans-unit>
<trans-unit id="_msg17">
<source xml:space="preserve">These are your Bitcoin addresses for sending payments. Always check the amount and the receiving address before sending coins.</source>
- <context-group purpose="location"><context context-type="linenumber">104</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">110</context></context-group>
</trans-unit>
<trans-unit id="_msg18">
<source xml:space="preserve">These are your Bitcoin addresses for receiving payments. Use the &apos;Create new receiving address&apos; button in the receive tab to create new addresses.
Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
- <context-group purpose="location"><context context-type="linenumber">109</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">115</context></context-group>
</trans-unit>
<trans-unit id="_msg19">
<source xml:space="preserve">&amp;Copy Address</source>
- <context-group purpose="location"><context context-type="linenumber">117</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">123</context></context-group>
</trans-unit>
<trans-unit id="_msg20">
<source xml:space="preserve">Copy &amp;Label</source>
- <context-group purpose="location"><context context-type="linenumber">118</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">124</context></context-group>
</trans-unit>
<trans-unit id="_msg21">
<source xml:space="preserve">&amp;Edit</source>
- <context-group purpose="location"><context context-type="linenumber">119</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">125</context></context-group>
</trans-unit>
<trans-unit id="_msg22">
<source xml:space="preserve">Export Address List</source>
- <context-group purpose="location"><context context-type="linenumber">283</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">289</context></context-group>
</trans-unit>
<trans-unit id="_msg23">
<source xml:space="preserve">Comma separated file</source>
- <context-group purpose="location"><context context-type="linenumber">286</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">292</context></context-group>
<note annotates="source" from="developer">Expanded name of the CSV file format. See: https://en.wikipedia.org/wiki/Comma-separated_values.</note>
</trans-unit>
<trans-unit id="_msg24">
<source xml:space="preserve">There was an error trying to save the address list to %1. Please try again.</source>
- <context-group purpose="location"><context context-type="linenumber">302</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">308</context></context-group>
<note annotates="source" from="developer">An error message. %1 is a stand-in argument for the name of the file we attempted to save to.</note>
</trans-unit>
<trans-unit id="_msg25">
<source xml:space="preserve">Exporting Failed</source>
- <context-group purpose="location"><context context-type="linenumber">299</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">305</context></context-group>
</trans-unit>
</group>
</body></file>
@@ -267,568 +267,614 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
<file original="../bitcoin.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="BitcoinApplication">
<trans-unit id="_msg58">
- <source xml:space="preserve">Runaway exception</source>
- <context-group purpose="location"><context context-type="linenumber">433</context></context-group>
+ <source xml:space="preserve">Settings file %1 might be corrupt or invalid.</source>
+ <context-group purpose="location"><context context-type="linenumber">288</context></context-group>
</trans-unit>
<trans-unit id="_msg59">
- <source xml:space="preserve">A fatal error occurred. %1 can no longer continue safely and will quit.</source>
- <context-group purpose="location"><context context-type="linenumber">434</context></context-group>
+ <source xml:space="preserve">Runaway exception</source>
+ <context-group purpose="location"><context context-type="linenumber">466</context></context-group>
</trans-unit>
<trans-unit id="_msg60">
- <source xml:space="preserve">Internal error</source>
- <context-group purpose="location"><context context-type="linenumber">443</context></context-group>
+ <source xml:space="preserve">A fatal error occurred. %1 can no longer continue safely and will quit.</source>
+ <context-group purpose="location"><context context-type="linenumber">467</context></context-group>
</trans-unit>
<trans-unit id="_msg61">
+ <source xml:space="preserve">Internal error</source>
+ <context-group purpose="location"><context context-type="linenumber">476</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg62">
<source xml:space="preserve">An internal error occurred. %1 will attempt to continue safely. This is an unexpected bug which can be reported as described below.</source>
- <context-group purpose="location"><context context-type="linenumber">444</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">477</context></context-group>
</trans-unit>
</group>
<group restype="x-trolltech-linguist-context" resname="QObject">
- <trans-unit id="_msg62">
+ <trans-unit id="_msg63">
<source xml:space="preserve">Do you want to reset settings to default values, or to abort without making changes?</source>
- <context-group purpose="location"><context context-type="linenumber">168</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">183</context></context-group>
<note annotates="source" from="developer">Explanatory text shown on startup when the settings file cannot be read. Prompts user to make a choice between resetting or aborting.</note>
</trans-unit>
- <trans-unit id="_msg63">
+ <trans-unit id="_msg64">
<source xml:space="preserve">A fatal error occurred. Check that settings file is writable, or try running with -nosettings.</source>
- <context-group purpose="location"><context context-type="linenumber">192</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">207</context></context-group>
<note annotates="source" from="developer">Explanatory text shown on startup when the settings file could not be written. Prompts user to check that we have the ability to write to the file. Explains that the user has the option of running without a settings file.</note>
</trans-unit>
- <trans-unit id="_msg64">
+ <trans-unit id="_msg65">
<source xml:space="preserve">Error: Specified data directory &quot;%1&quot; does not exist.</source>
- <context-group purpose="location"><context context-type="linenumber">565</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">600</context></context-group>
</trans-unit>
- <trans-unit id="_msg65">
+ <trans-unit id="_msg66">
<source xml:space="preserve">Error: Cannot parse configuration file: %1.</source>
- <context-group purpose="location"><context context-type="linenumber">571</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">606</context></context-group>
</trans-unit>
- <trans-unit id="_msg66">
+ <trans-unit id="_msg67">
<source xml:space="preserve">Error: %1</source>
- <context-group purpose="location"><context context-type="linenumber">586</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">621</context></context-group>
</trans-unit>
- <trans-unit id="_msg67">
+ <trans-unit id="_msg68">
<source xml:space="preserve">%1 didn&apos;t yet exit safely…</source>
- <context-group purpose="location"><context context-type="linenumber">657</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">695</context></context-group>
</trans-unit>
</group>
<group restype="x-trolltech-linguist-context" resname="bitcoin-core">
- <trans-unit id="_msg68">
+ <trans-unit id="_msg69">
<source xml:space="preserve">Settings file could not be read</source>
- <context-group purpose="location"><context context-type="linenumber">161</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">176</context></context-group>
</trans-unit>
- <trans-unit id="_msg69">
+ <trans-unit id="_msg70">
<source xml:space="preserve">Settings file could not be written</source>
- <context-group purpose="location"><context context-type="linenumber">184</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">199</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../bitcoingui.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="BitcoinGUI">
- <trans-unit id="_msg70">
- <source xml:space="preserve">&amp;Overview</source>
- <context-group purpose="location"><context context-type="linenumber">250</context></context-group>
- </trans-unit>
<trans-unit id="_msg71">
- <source xml:space="preserve">Show general overview of wallet</source>
- <context-group purpose="location"><context context-type="linenumber">251</context></context-group>
+ <source xml:space="preserve">&amp;Overview</source>
+ <context-group purpose="location"><context context-type="linenumber">253</context></context-group>
</trans-unit>
<trans-unit id="_msg72">
- <source xml:space="preserve">&amp;Transactions</source>
- <context-group purpose="location"><context context-type="linenumber">271</context></context-group>
+ <source xml:space="preserve">Show general overview of wallet</source>
+ <context-group purpose="location"><context context-type="linenumber">254</context></context-group>
</trans-unit>
<trans-unit id="_msg73">
- <source xml:space="preserve">Browse transaction history</source>
- <context-group purpose="location"><context context-type="linenumber">272</context></context-group>
+ <source xml:space="preserve">&amp;Transactions</source>
+ <context-group purpose="location"><context context-type="linenumber">274</context></context-group>
</trans-unit>
<trans-unit id="_msg74">
- <source xml:space="preserve">E&amp;xit</source>
- <context-group purpose="location"><context context-type="linenumber">291</context></context-group>
+ <source xml:space="preserve">Browse transaction history</source>
+ <context-group purpose="location"><context context-type="linenumber">275</context></context-group>
</trans-unit>
<trans-unit id="_msg75">
- <source xml:space="preserve">Quit application</source>
- <context-group purpose="location"><context context-type="linenumber">292</context></context-group>
+ <source xml:space="preserve">E&amp;xit</source>
+ <context-group purpose="location"><context context-type="linenumber">294</context></context-group>
</trans-unit>
<trans-unit id="_msg76">
- <source xml:space="preserve">&amp;About %1</source>
+ <source xml:space="preserve">Quit application</source>
<context-group purpose="location"><context context-type="linenumber">295</context></context-group>
</trans-unit>
<trans-unit id="_msg77">
- <source xml:space="preserve">Show information about %1</source>
- <context-group purpose="location"><context context-type="linenumber">296</context></context-group>
+ <source xml:space="preserve">&amp;About %1</source>
+ <context-group purpose="location"><context context-type="linenumber">298</context></context-group>
</trans-unit>
<trans-unit id="_msg78">
- <source xml:space="preserve">About &amp;Qt</source>
+ <source xml:space="preserve">Show information about %1</source>
<context-group purpose="location"><context context-type="linenumber">299</context></context-group>
</trans-unit>
<trans-unit id="_msg79">
- <source xml:space="preserve">Show information about Qt</source>
- <context-group purpose="location"><context context-type="linenumber">300</context></context-group>
+ <source xml:space="preserve">About &amp;Qt</source>
+ <context-group purpose="location"><context context-type="linenumber">302</context></context-group>
</trans-unit>
<trans-unit id="_msg80">
- <source xml:space="preserve">Modify configuration options for %1</source>
+ <source xml:space="preserve">Show information about Qt</source>
<context-group purpose="location"><context context-type="linenumber">303</context></context-group>
</trans-unit>
<trans-unit id="_msg81">
- <source xml:space="preserve">Create a new wallet</source>
- <context-group purpose="location"><context context-type="linenumber">347</context></context-group>
+ <source xml:space="preserve">Modify configuration options for %1</source>
+ <context-group purpose="location"><context context-type="linenumber">306</context></context-group>
</trans-unit>
<trans-unit id="_msg82">
- <source xml:space="preserve">&amp;Minimize</source>
- <context-group purpose="location"><context context-type="linenumber">474</context></context-group>
+ <source xml:space="preserve">Create a new wallet</source>
+ <context-group purpose="location"><context context-type="linenumber">350</context></context-group>
</trans-unit>
<trans-unit id="_msg83">
- <source xml:space="preserve">Wallet:</source>
- <context-group purpose="location"><context context-type="linenumber">553</context></context-group>
+ <source xml:space="preserve">&amp;Minimize</source>
+ <context-group purpose="location"><context context-type="linenumber">510</context></context-group>
</trans-unit>
<trans-unit id="_msg84">
- <source xml:space="preserve">Network activity disabled.</source>
- <context-group purpose="location"><context context-type="linenumber">945</context></context-group>
- <note annotates="source" from="developer">A substring of the tooltip.</note>
+ <source xml:space="preserve">Wallet:</source>
+ <context-group purpose="location"><context context-type="linenumber">589</context></context-group>
</trans-unit>
<trans-unit id="_msg85">
- <source xml:space="preserve">Proxy is &lt;b&gt;enabled&lt;/b&gt;: %1</source>
- <context-group purpose="location"><context context-type="linenumber">1367</context></context-group>
+ <source xml:space="preserve">Network activity disabled.</source>
+ <context-group purpose="location"><context context-type="linenumber">982</context></context-group>
+ <note annotates="source" from="developer">A substring of the tooltip.</note>
</trans-unit>
<trans-unit id="_msg86">
- <source xml:space="preserve">Send coins to a Bitcoin address</source>
- <context-group purpose="location"><context context-type="linenumber">258</context></context-group>
+ <source xml:space="preserve">Proxy is &lt;b&gt;enabled&lt;/b&gt;: %1</source>
+ <context-group purpose="location"><context context-type="linenumber">1421</context></context-group>
</trans-unit>
<trans-unit id="_msg87">
- <source xml:space="preserve">Backup wallet to another location</source>
- <context-group purpose="location"><context context-type="linenumber">311</context></context-group>
+ <source xml:space="preserve">Send coins to a Bitcoin address</source>
+ <context-group purpose="location"><context context-type="linenumber">261</context></context-group>
</trans-unit>
<trans-unit id="_msg88">
- <source xml:space="preserve">Change the passphrase used for wallet encryption</source>
- <context-group purpose="location"><context context-type="linenumber">313</context></context-group>
+ <source xml:space="preserve">Backup wallet to another location</source>
+ <context-group purpose="location"><context context-type="linenumber">314</context></context-group>
</trans-unit>
<trans-unit id="_msg89">
- <source xml:space="preserve">&amp;Send</source>
- <context-group purpose="location"><context context-type="linenumber">257</context></context-group>
+ <source xml:space="preserve">Change the passphrase used for wallet encryption</source>
+ <context-group purpose="location"><context context-type="linenumber">316</context></context-group>
</trans-unit>
<trans-unit id="_msg90">
- <source xml:space="preserve">&amp;Receive</source>
- <context-group purpose="location"><context context-type="linenumber">264</context></context-group>
+ <source xml:space="preserve">&amp;Send</source>
+ <context-group purpose="location"><context context-type="linenumber">260</context></context-group>
</trans-unit>
<trans-unit id="_msg91">
- <source xml:space="preserve">&amp;Options…</source>
- <context-group purpose="location"><context context-type="linenumber">302</context></context-group>
+ <source xml:space="preserve">&amp;Receive</source>
+ <context-group purpose="location"><context context-type="linenumber">267</context></context-group>
</trans-unit>
<trans-unit id="_msg92">
- <source xml:space="preserve">&amp;Encrypt Wallet…</source>
- <context-group purpose="location"><context context-type="linenumber">307</context></context-group>
+ <source xml:space="preserve">&amp;Options…</source>
+ <context-group purpose="location"><context context-type="linenumber">305</context></context-group>
</trans-unit>
<trans-unit id="_msg93">
- <source xml:space="preserve">Encrypt the private keys that belong to your wallet</source>
- <context-group purpose="location"><context context-type="linenumber">308</context></context-group>
+ <source xml:space="preserve">&amp;Encrypt Wallet…</source>
+ <context-group purpose="location"><context context-type="linenumber">310</context></context-group>
</trans-unit>
<trans-unit id="_msg94">
- <source xml:space="preserve">&amp;Backup Wallet…</source>
- <context-group purpose="location"><context context-type="linenumber">310</context></context-group>
+ <source xml:space="preserve">Encrypt the private keys that belong to your wallet</source>
+ <context-group purpose="location"><context context-type="linenumber">311</context></context-group>
</trans-unit>
<trans-unit id="_msg95">
- <source xml:space="preserve">&amp;Change Passphrase…</source>
- <context-group purpose="location"><context context-type="linenumber">312</context></context-group>
+ <source xml:space="preserve">&amp;Backup Wallet…</source>
+ <context-group purpose="location"><context context-type="linenumber">313</context></context-group>
</trans-unit>
<trans-unit id="_msg96">
- <source xml:space="preserve">Sign &amp;message…</source>
- <context-group purpose="location"><context context-type="linenumber">314</context></context-group>
+ <source xml:space="preserve">&amp;Change Passphrase…</source>
+ <context-group purpose="location"><context context-type="linenumber">315</context></context-group>
</trans-unit>
<trans-unit id="_msg97">
- <source xml:space="preserve">Sign messages with your Bitcoin addresses to prove you own them</source>
- <context-group purpose="location"><context context-type="linenumber">315</context></context-group>
+ <source xml:space="preserve">Sign &amp;message…</source>
+ <context-group purpose="location"><context context-type="linenumber">317</context></context-group>
</trans-unit>
<trans-unit id="_msg98">
- <source xml:space="preserve">&amp;Verify message…</source>
- <context-group purpose="location"><context context-type="linenumber">316</context></context-group>
+ <source xml:space="preserve">Sign messages with your Bitcoin addresses to prove you own them</source>
+ <context-group purpose="location"><context context-type="linenumber">318</context></context-group>
</trans-unit>
<trans-unit id="_msg99">
- <source xml:space="preserve">Verify messages to ensure they were signed with specified Bitcoin addresses</source>
- <context-group purpose="location"><context context-type="linenumber">317</context></context-group>
+ <source xml:space="preserve">&amp;Verify message…</source>
+ <context-group purpose="location"><context context-type="linenumber">319</context></context-group>
</trans-unit>
<trans-unit id="_msg100">
- <source xml:space="preserve">&amp;Load PSBT from file…</source>
- <context-group purpose="location"><context context-type="linenumber">318</context></context-group>
+ <source xml:space="preserve">Verify messages to ensure they were signed with specified Bitcoin addresses</source>
+ <context-group purpose="location"><context context-type="linenumber">320</context></context-group>
</trans-unit>
<trans-unit id="_msg101">
- <source xml:space="preserve">Open &amp;URI…</source>
- <context-group purpose="location"><context context-type="linenumber">334</context></context-group>
+ <source xml:space="preserve">&amp;Load PSBT from file…</source>
+ <context-group purpose="location"><context context-type="linenumber">321</context></context-group>
</trans-unit>
<trans-unit id="_msg102">
- <source xml:space="preserve">Close Wallet…</source>
- <context-group purpose="location"><context context-type="linenumber">342</context></context-group>
+ <source xml:space="preserve">Open &amp;URI…</source>
+ <context-group purpose="location"><context context-type="linenumber">337</context></context-group>
</trans-unit>
<trans-unit id="_msg103">
- <source xml:space="preserve">Create Wallet…</source>
+ <source xml:space="preserve">Close Wallet…</source>
<context-group purpose="location"><context context-type="linenumber">345</context></context-group>
</trans-unit>
<trans-unit id="_msg104">
- <source xml:space="preserve">Close All Wallets…</source>
- <context-group purpose="location"><context context-type="linenumber">349</context></context-group>
+ <source xml:space="preserve">Create Wallet…</source>
+ <context-group purpose="location"><context context-type="linenumber">348</context></context-group>
</trans-unit>
<trans-unit id="_msg105">
- <source xml:space="preserve">&amp;File</source>
- <context-group purpose="location"><context context-type="linenumber">443</context></context-group>
+ <source xml:space="preserve">Close All Wallets…</source>
+ <context-group purpose="location"><context context-type="linenumber">358</context></context-group>
</trans-unit>
<trans-unit id="_msg106">
- <source xml:space="preserve">&amp;Settings</source>
- <context-group purpose="location"><context context-type="linenumber">461</context></context-group>
+ <source xml:space="preserve">&amp;File</source>
+ <context-group purpose="location"><context context-type="linenumber">477</context></context-group>
</trans-unit>
<trans-unit id="_msg107">
- <source xml:space="preserve">&amp;Help</source>
- <context-group purpose="location"><context context-type="linenumber">522</context></context-group>
+ <source xml:space="preserve">&amp;Settings</source>
+ <context-group purpose="location"><context context-type="linenumber">497</context></context-group>
</trans-unit>
<trans-unit id="_msg108">
- <source xml:space="preserve">Tabs toolbar</source>
- <context-group purpose="location"><context context-type="linenumber">533</context></context-group>
+ <source xml:space="preserve">&amp;Help</source>
+ <context-group purpose="location"><context context-type="linenumber">558</context></context-group>
</trans-unit>
<trans-unit id="_msg109">
- <source xml:space="preserve">Syncing Headers (%1%)…</source>
- <context-group purpose="location"><context context-type="linenumber">989</context></context-group>
+ <source xml:space="preserve">Tabs toolbar</source>
+ <context-group purpose="location"><context context-type="linenumber">569</context></context-group>
</trans-unit>
<trans-unit id="_msg110">
- <source xml:space="preserve">Synchronizing with network…</source>
- <context-group purpose="location"><context context-type="linenumber">1036</context></context-group>
+ <source xml:space="preserve">Syncing Headers (%1%)…</source>
+ <context-group purpose="location"><context context-type="linenumber">1026</context></context-group>
</trans-unit>
<trans-unit id="_msg111">
- <source xml:space="preserve">Indexing blocks on disk…</source>
- <context-group purpose="location"><context context-type="linenumber">1041</context></context-group>
+ <source xml:space="preserve">Synchronizing with network…</source>
+ <context-group purpose="location"><context context-type="linenumber">1084</context></context-group>
</trans-unit>
<trans-unit id="_msg112">
- <source xml:space="preserve">Processing blocks on disk…</source>
- <context-group purpose="location"><context context-type="linenumber">1043</context></context-group>
+ <source xml:space="preserve">Indexing blocks on disk…</source>
+ <context-group purpose="location"><context context-type="linenumber">1089</context></context-group>
</trans-unit>
<trans-unit id="_msg113">
- <source xml:space="preserve">Reindexing blocks on disk…</source>
- <context-group purpose="location"><context context-type="linenumber">1047</context></context-group>
+ <source xml:space="preserve">Processing blocks on disk…</source>
+ <context-group purpose="location"><context context-type="linenumber">1091</context></context-group>
</trans-unit>
<trans-unit id="_msg114">
- <source xml:space="preserve">Connecting to peers…</source>
- <context-group purpose="location"><context context-type="linenumber">1053</context></context-group>
+ <source xml:space="preserve">Reindexing blocks on disk…</source>
+ <context-group purpose="location"><context context-type="linenumber">1095</context></context-group>
</trans-unit>
<trans-unit id="_msg115">
- <source xml:space="preserve">Request payments (generates QR codes and bitcoin: URIs)</source>
- <context-group purpose="location"><context context-type="linenumber">265</context></context-group>
+ <source xml:space="preserve">Connecting to peers…</source>
+ <context-group purpose="location"><context context-type="linenumber">1101</context></context-group>
</trans-unit>
<trans-unit id="_msg116">
- <source xml:space="preserve">Show the list of used sending addresses and labels</source>
- <context-group purpose="location"><context context-type="linenumber">330</context></context-group>
+ <source xml:space="preserve">Request payments (generates QR codes and bitcoin: URIs)</source>
+ <context-group purpose="location"><context context-type="linenumber">268</context></context-group>
</trans-unit>
<trans-unit id="_msg117">
- <source xml:space="preserve">Show the list of used receiving addresses and labels</source>
- <context-group purpose="location"><context context-type="linenumber">332</context></context-group>
+ <source xml:space="preserve">Show the list of used sending addresses and labels</source>
+ <context-group purpose="location"><context context-type="linenumber">333</context></context-group>
</trans-unit>
<trans-unit id="_msg118">
+ <source xml:space="preserve">Show the list of used receiving addresses and labels</source>
+ <context-group purpose="location"><context context-type="linenumber">335</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg119">
<source xml:space="preserve">&amp;Command-line options</source>
- <context-group purpose="location"><context context-type="linenumber">352</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">361</context></context-group>
</trans-unit>
<group restype="x-gettext-plurals">
- <context-group purpose="location"><context context-type="linenumber">1062</context></context-group>
- <trans-unit id="_msg119[0]">
+ <context-group purpose="location"><context context-type="linenumber">1110</context></context-group>
+ <trans-unit id="_msg120[0]">
<source xml:space="preserve">Processed %n block(s) of transaction history.</source>
</trans-unit>
- <trans-unit id="_msg119[1]">
+ <trans-unit id="_msg120[1]">
<source xml:space="preserve">Processed %n block(s) of transaction history.</source>
</trans-unit>
</group>
- <trans-unit id="_msg120">
- <source xml:space="preserve">%1 behind</source>
- <context-group purpose="location"><context context-type="linenumber">1085</context></context-group>
- </trans-unit>
<trans-unit id="_msg121">
- <source xml:space="preserve">Catching up…</source>
- <context-group purpose="location"><context context-type="linenumber">1090</context></context-group>
+ <source xml:space="preserve">%1 behind</source>
+ <context-group purpose="location"><context context-type="linenumber">1133</context></context-group>
</trans-unit>
<trans-unit id="_msg122">
- <source xml:space="preserve">Last received block was generated %1 ago.</source>
- <context-group purpose="location"><context context-type="linenumber">1109</context></context-group>
+ <source xml:space="preserve">Catching up…</source>
+ <context-group purpose="location"><context context-type="linenumber">1138</context></context-group>
</trans-unit>
<trans-unit id="_msg123">
- <source xml:space="preserve">Transactions after this will not yet be visible.</source>
- <context-group purpose="location"><context context-type="linenumber">1111</context></context-group>
+ <source xml:space="preserve">Last received block was generated %1 ago.</source>
+ <context-group purpose="location"><context context-type="linenumber">1157</context></context-group>
</trans-unit>
<trans-unit id="_msg124">
- <source xml:space="preserve">Error</source>
- <context-group purpose="location"><context context-type="linenumber">1136</context></context-group>
+ <source xml:space="preserve">Transactions after this will not yet be visible.</source>
+ <context-group purpose="location"><context context-type="linenumber">1159</context></context-group>
</trans-unit>
<trans-unit id="_msg125">
- <source xml:space="preserve">Warning</source>
- <context-group purpose="location"><context context-type="linenumber">1140</context></context-group>
+ <source xml:space="preserve">Error</source>
+ <context-group purpose="location"><context context-type="linenumber">1184</context></context-group>
</trans-unit>
<trans-unit id="_msg126">
- <source xml:space="preserve">Information</source>
- <context-group purpose="location"><context context-type="linenumber">1144</context></context-group>
+ <source xml:space="preserve">Warning</source>
+ <context-group purpose="location"><context context-type="linenumber">1188</context></context-group>
</trans-unit>
<trans-unit id="_msg127">
- <source xml:space="preserve">Up to date</source>
- <context-group purpose="location"><context context-type="linenumber">1066</context></context-group>
+ <source xml:space="preserve">Information</source>
+ <context-group purpose="location"><context context-type="linenumber">1192</context></context-group>
</trans-unit>
<trans-unit id="_msg128">
- <source xml:space="preserve">Load Partially Signed Bitcoin Transaction</source>
- <context-group purpose="location"><context context-type="linenumber">319</context></context-group>
+ <source xml:space="preserve">Up to date</source>
+ <context-group purpose="location"><context context-type="linenumber">1114</context></context-group>
</trans-unit>
<trans-unit id="_msg129">
- <source xml:space="preserve">Load PSBT from &amp;clipboard…</source>
- <context-group purpose="location"><context context-type="linenumber">320</context></context-group>
+ <source xml:space="preserve">Ctrl+Q</source>
+ <context-group purpose="location"><context context-type="linenumber">296</context></context-group>
</trans-unit>
<trans-unit id="_msg130">
- <source xml:space="preserve">Load Partially Signed Bitcoin Transaction from clipboard</source>
- <context-group purpose="location"><context context-type="linenumber">321</context></context-group>
+ <source xml:space="preserve">Load Partially Signed Bitcoin Transaction</source>
+ <context-group purpose="location"><context context-type="linenumber">322</context></context-group>
</trans-unit>
<trans-unit id="_msg131">
- <source xml:space="preserve">Node window</source>
+ <source xml:space="preserve">Load PSBT from &amp;clipboard…</source>
<context-group purpose="location"><context context-type="linenumber">323</context></context-group>
</trans-unit>
<trans-unit id="_msg132">
- <source xml:space="preserve">Open node debugging and diagnostic console</source>
+ <source xml:space="preserve">Load Partially Signed Bitcoin Transaction from clipboard</source>
<context-group purpose="location"><context context-type="linenumber">324</context></context-group>
</trans-unit>
<trans-unit id="_msg133">
- <source xml:space="preserve">&amp;Sending addresses</source>
- <context-group purpose="location"><context context-type="linenumber">329</context></context-group>
+ <source xml:space="preserve">Node window</source>
+ <context-group purpose="location"><context context-type="linenumber">326</context></context-group>
</trans-unit>
<trans-unit id="_msg134">
- <source xml:space="preserve">&amp;Receiving addresses</source>
- <context-group purpose="location"><context context-type="linenumber">331</context></context-group>
+ <source xml:space="preserve">Open node debugging and diagnostic console</source>
+ <context-group purpose="location"><context context-type="linenumber">327</context></context-group>
</trans-unit>
<trans-unit id="_msg135">
- <source xml:space="preserve">Open a bitcoin: URI</source>
- <context-group purpose="location"><context context-type="linenumber">335</context></context-group>
+ <source xml:space="preserve">&amp;Sending addresses</source>
+ <context-group purpose="location"><context context-type="linenumber">332</context></context-group>
</trans-unit>
<trans-unit id="_msg136">
- <source xml:space="preserve">Open Wallet</source>
- <context-group purpose="location"><context context-type="linenumber">337</context></context-group>
+ <source xml:space="preserve">&amp;Receiving addresses</source>
+ <context-group purpose="location"><context context-type="linenumber">334</context></context-group>
</trans-unit>
<trans-unit id="_msg137">
- <source xml:space="preserve">Open a wallet</source>
- <context-group purpose="location"><context context-type="linenumber">339</context></context-group>
+ <source xml:space="preserve">Open a bitcoin: URI</source>
+ <context-group purpose="location"><context context-type="linenumber">338</context></context-group>
</trans-unit>
<trans-unit id="_msg138">
- <source xml:space="preserve">Close wallet</source>
- <context-group purpose="location"><context context-type="linenumber">343</context></context-group>
+ <source xml:space="preserve">Open Wallet</source>
+ <context-group purpose="location"><context context-type="linenumber">340</context></context-group>
</trans-unit>
<trans-unit id="_msg139">
- <source xml:space="preserve">Close all wallets</source>
- <context-group purpose="location"><context context-type="linenumber">350</context></context-group>
+ <source xml:space="preserve">Open a wallet</source>
+ <context-group purpose="location"><context context-type="linenumber">342</context></context-group>
</trans-unit>
<trans-unit id="_msg140">
- <source xml:space="preserve">Show the %1 help message to get a list with possible Bitcoin command-line options</source>
- <context-group purpose="location"><context context-type="linenumber">354</context></context-group>
+ <source xml:space="preserve">Close wallet</source>
+ <context-group purpose="location"><context context-type="linenumber">346</context></context-group>
</trans-unit>
<trans-unit id="_msg141">
- <source xml:space="preserve">&amp;Mask values</source>
- <context-group purpose="location"><context context-type="linenumber">356</context></context-group>
+ <source xml:space="preserve">Restore Wallet…</source>
+ <context-group purpose="location"><context context-type="linenumber">353</context></context-group>
+ <note annotates="source" from="developer">Name of the menu item that restores wallet from a backup file.</note>
</trans-unit>
<trans-unit id="_msg142">
- <source xml:space="preserve">Mask the values in the Overview tab</source>
- <context-group purpose="location"><context context-type="linenumber">358</context></context-group>
+ <source xml:space="preserve">Restore a wallet from a backup file</source>
+ <context-group purpose="location"><context context-type="linenumber">356</context></context-group>
+ <note annotates="source" from="developer">Status tip for Restore Wallet menu item</note>
</trans-unit>
<trans-unit id="_msg143">
- <source xml:space="preserve">default wallet</source>
- <context-group purpose="location"><context context-type="linenumber">389</context></context-group>
+ <source xml:space="preserve">Close all wallets</source>
+ <context-group purpose="location"><context context-type="linenumber">359</context></context-group>
</trans-unit>
<trans-unit id="_msg144">
- <source xml:space="preserve">No wallets available</source>
- <context-group purpose="location"><context context-type="linenumber">409</context></context-group>
+ <source xml:space="preserve">Show the %1 help message to get a list with possible Bitcoin command-line options</source>
+ <context-group purpose="location"><context context-type="linenumber">363</context></context-group>
</trans-unit>
<trans-unit id="_msg145">
- <source xml:space="preserve">&amp;Window</source>
- <context-group purpose="location"><context context-type="linenumber">472</context></context-group>
+ <source xml:space="preserve">&amp;Mask values</source>
+ <context-group purpose="location"><context context-type="linenumber">365</context></context-group>
</trans-unit>
<trans-unit id="_msg146">
- <source xml:space="preserve">Zoom</source>
- <context-group purpose="location"><context context-type="linenumber">484</context></context-group>
+ <source xml:space="preserve">Mask the values in the Overview tab</source>
+ <context-group purpose="location"><context context-type="linenumber">367</context></context-group>
</trans-unit>
<trans-unit id="_msg147">
- <source xml:space="preserve">Main Window</source>
- <context-group purpose="location"><context context-type="linenumber">502</context></context-group>
+ <source xml:space="preserve">default wallet</source>
+ <context-group purpose="location"><context context-type="linenumber">398</context></context-group>
</trans-unit>
<trans-unit id="_msg148">
- <source xml:space="preserve">%1 client</source>
- <context-group purpose="location"><context context-type="linenumber">759</context></context-group>
+ <source xml:space="preserve">No wallets available</source>
+ <context-group purpose="location"><context context-type="linenumber">418</context></context-group>
</trans-unit>
<trans-unit id="_msg149">
- <source xml:space="preserve">&amp;Hide</source>
- <context-group purpose="location"><context context-type="linenumber">824</context></context-group>
+ <source xml:space="preserve">Wallet Data</source>
+ <context-group purpose="location"><context context-type="linenumber">424</context></context-group>
+ <note annotates="source" from="developer">Name of the wallet data file format.</note>
</trans-unit>
<trans-unit id="_msg150">
+ <source xml:space="preserve">Load Wallet Backup</source>
+ <context-group purpose="location"><context context-type="linenumber">427</context></context-group>
+ <note annotates="source" from="developer">The title for Restore Wallet File Windows</note>
+ </trans-unit>
+ <trans-unit id="_msg151">
+ <source xml:space="preserve">Restore Wallet</source>
+ <context-group purpose="location"><context context-type="linenumber">435</context></context-group>
+ <note annotates="source" from="developer">Title of pop-up window shown when the user is attempting to restore a wallet.</note>
+ </trans-unit>
+ <trans-unit id="_msg152">
+ <source xml:space="preserve">Wallet Name</source>
+ <context-group purpose="location"><context context-type="linenumber">437</context></context-group>
+ <note annotates="source" from="developer">Label of the input field where the name of the wallet is entered.</note>
+ </trans-unit>
+ <trans-unit id="_msg153">
+ <source xml:space="preserve">&amp;Window</source>
+ <context-group purpose="location"><context context-type="linenumber">508</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg154">
+ <source xml:space="preserve">Ctrl+M</source>
+ <context-group purpose="location"><context context-type="linenumber">511</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg155">
+ <source xml:space="preserve">Zoom</source>
+ <context-group purpose="location"><context context-type="linenumber">520</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg156">
+ <source xml:space="preserve">Main Window</source>
+ <context-group purpose="location"><context context-type="linenumber">538</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg157">
+ <source xml:space="preserve">%1 client</source>
+ <context-group purpose="location"><context context-type="linenumber">796</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg158">
+ <source xml:space="preserve">&amp;Hide</source>
+ <context-group purpose="location"><context context-type="linenumber">861</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg159">
<source xml:space="preserve">S&amp;how</source>
- <context-group purpose="location"><context context-type="linenumber">825</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">862</context></context-group>
</trans-unit>
<group restype="x-gettext-plurals">
- <context-group purpose="location"><context context-type="linenumber">942</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">979</context></context-group>
<note annotates="source" from="developer">A substring of the tooltip.</note>
- <trans-unit id="_msg151[0]">
+ <trans-unit id="_msg160[0]">
<source xml:space="preserve">%n active connection(s) to Bitcoin network.</source>
</trans-unit>
- <trans-unit id="_msg151[1]">
+ <trans-unit id="_msg160[1]">
<source xml:space="preserve">%n active connection(s) to Bitcoin network.</source>
</trans-unit>
</group>
- <trans-unit id="_msg152">
+ <trans-unit id="_msg161">
<source xml:space="preserve">Click for more actions.</source>
- <context-group purpose="location"><context context-type="linenumber">952</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">989</context></context-group>
<note annotates="source" from="developer">A substring of the tooltip. &quot;More actions&quot; are available via the context menu.</note>
</trans-unit>
- <trans-unit id="_msg153">
+ <trans-unit id="_msg162">
<source xml:space="preserve">Show Peers tab</source>
- <context-group purpose="location"><context context-type="linenumber">969</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1006</context></context-group>
<note annotates="source" from="developer">A context menu item. The &quot;Peers tab&quot; is an element of the &quot;Node window&quot;.</note>
</trans-unit>
- <trans-unit id="_msg154">
+ <trans-unit id="_msg163">
<source xml:space="preserve">Disable network activity</source>
- <context-group purpose="location"><context context-type="linenumber">977</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1014</context></context-group>
<note annotates="source" from="developer">A context menu item.</note>
</trans-unit>
- <trans-unit id="_msg155">
+ <trans-unit id="_msg164">
<source xml:space="preserve">Enable network activity</source>
- <context-group purpose="location"><context context-type="linenumber">979</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1016</context></context-group>
<note annotates="source" from="developer">A context menu item. The network activity was disabled previously.</note>
</trans-unit>
- <trans-unit id="_msg156">
+ <trans-unit id="_msg165">
+ <source xml:space="preserve">Pre-syncing Headers (%1%)…</source>
+ <context-group purpose="location"><context context-type="linenumber">1033</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg166">
<source xml:space="preserve">Error: %1</source>
- <context-group purpose="location"><context context-type="linenumber">1137</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1185</context></context-group>
</trans-unit>
- <trans-unit id="_msg157">
+ <trans-unit id="_msg167">
<source xml:space="preserve">Warning: %1</source>
- <context-group purpose="location"><context context-type="linenumber">1141</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1189</context></context-group>
</trans-unit>
- <trans-unit id="_msg158">
+ <trans-unit id="_msg168">
<source xml:space="preserve">Date: %1
</source>
- <context-group purpose="location"><context context-type="linenumber">1249</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1297</context></context-group>
</trans-unit>
- <trans-unit id="_msg159">
+ <trans-unit id="_msg169">
<source xml:space="preserve">Amount: %1
</source>
- <context-group purpose="location"><context context-type="linenumber">1250</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1298</context></context-group>
</trans-unit>
- <trans-unit id="_msg160">
+ <trans-unit id="_msg170">
<source xml:space="preserve">Wallet: %1
</source>
- <context-group purpose="location"><context context-type="linenumber">1252</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1300</context></context-group>
</trans-unit>
- <trans-unit id="_msg161">
+ <trans-unit id="_msg171">
<source xml:space="preserve">Type: %1
</source>
- <context-group purpose="location"><context context-type="linenumber">1254</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1302</context></context-group>
</trans-unit>
- <trans-unit id="_msg162">
+ <trans-unit id="_msg172">
<source xml:space="preserve">Label: %1
</source>
- <context-group purpose="location"><context context-type="linenumber">1256</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1304</context></context-group>
</trans-unit>
- <trans-unit id="_msg163">
+ <trans-unit id="_msg173">
<source xml:space="preserve">Address: %1
</source>
- <context-group purpose="location"><context context-type="linenumber">1258</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1306</context></context-group>
</trans-unit>
- <trans-unit id="_msg164">
+ <trans-unit id="_msg174">
<source xml:space="preserve">Sent transaction</source>
- <context-group purpose="location"><context context-type="linenumber">1259</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1307</context></context-group>
</trans-unit>
- <trans-unit id="_msg165">
+ <trans-unit id="_msg175">
<source xml:space="preserve">Incoming transaction</source>
- <context-group purpose="location"><context context-type="linenumber">1259</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1307</context></context-group>
</trans-unit>
- <trans-unit id="_msg166">
+ <trans-unit id="_msg176">
<source xml:space="preserve">HD key generation is &lt;b&gt;enabled&lt;/b&gt;</source>
- <context-group purpose="location"><context context-type="linenumber">1311</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1359</context></context-group>
</trans-unit>
- <trans-unit id="_msg167">
+ <trans-unit id="_msg177">
<source xml:space="preserve">HD key generation is &lt;b&gt;disabled&lt;/b&gt;</source>
- <context-group purpose="location"><context context-type="linenumber">1311</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1359</context></context-group>
</trans-unit>
- <trans-unit id="_msg168">
+ <trans-unit id="_msg178">
<source xml:space="preserve">Private key &lt;b&gt;disabled&lt;/b&gt;</source>
- <context-group purpose="location"><context context-type="linenumber">1311</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1359</context></context-group>
</trans-unit>
- <trans-unit id="_msg169">
+ <trans-unit id="_msg179">
<source xml:space="preserve">Wallet is &lt;b&gt;encrypted&lt;/b&gt; and currently &lt;b&gt;unlocked&lt;/b&gt;</source>
- <context-group purpose="location"><context context-type="linenumber">1328</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1382</context></context-group>
</trans-unit>
- <trans-unit id="_msg170">
+ <trans-unit id="_msg180">
<source xml:space="preserve">Wallet is &lt;b&gt;encrypted&lt;/b&gt; and currently &lt;b&gt;locked&lt;/b&gt;</source>
- <context-group purpose="location"><context context-type="linenumber">1336</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1390</context></context-group>
</trans-unit>
- <trans-unit id="_msg171">
+ <trans-unit id="_msg181">
<source xml:space="preserve">Original message:</source>
- <context-group purpose="location"><context context-type="linenumber">1455</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1509</context></context-group>
</trans-unit>
</group>
<group restype="x-trolltech-linguist-context" resname="UnitDisplayStatusBarControl">
- <trans-unit id="_msg172">
+ <trans-unit id="_msg182">
<source xml:space="preserve">Unit to show amounts in. Click to select another unit.</source>
- <context-group purpose="location"><context context-type="linenumber">1496</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1550</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../forms/coincontroldialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="CoinControlDialog">
- <trans-unit id="_msg173">
+ <trans-unit id="_msg183">
<source xml:space="preserve">Coin Selection</source>
<context-group purpose="location"><context context-type="linenumber">14</context></context-group>
</trans-unit>
- <trans-unit id="_msg174">
+ <trans-unit id="_msg184">
<source xml:space="preserve">Quantity:</source>
<context-group purpose="location"><context context-type="linenumber">48</context></context-group>
</trans-unit>
- <trans-unit id="_msg175">
+ <trans-unit id="_msg185">
<source xml:space="preserve">Bytes:</source>
<context-group purpose="location"><context context-type="linenumber">77</context></context-group>
</trans-unit>
- <trans-unit id="_msg176">
+ <trans-unit id="_msg186">
<source xml:space="preserve">Amount:</source>
<context-group purpose="location"><context context-type="linenumber">122</context></context-group>
</trans-unit>
- <trans-unit id="_msg177">
+ <trans-unit id="_msg187">
<source xml:space="preserve">Fee:</source>
<context-group purpose="location"><context context-type="linenumber">202</context></context-group>
</trans-unit>
- <trans-unit id="_msg178">
+ <trans-unit id="_msg188">
<source xml:space="preserve">Dust:</source>
<context-group purpose="location"><context context-type="linenumber">154</context></context-group>
</trans-unit>
- <trans-unit id="_msg179">
+ <trans-unit id="_msg189">
<source xml:space="preserve">After Fee:</source>
<context-group purpose="location"><context context-type="linenumber">247</context></context-group>
</trans-unit>
- <trans-unit id="_msg180">
+ <trans-unit id="_msg190">
<source xml:space="preserve">Change:</source>
<context-group purpose="location"><context context-type="linenumber">279</context></context-group>
</trans-unit>
- <trans-unit id="_msg181">
+ <trans-unit id="_msg191">
<source xml:space="preserve">(un)select all</source>
<context-group purpose="location"><context context-type="linenumber">335</context></context-group>
</trans-unit>
- <trans-unit id="_msg182">
+ <trans-unit id="_msg192">
<source xml:space="preserve">Tree mode</source>
<context-group purpose="location"><context context-type="linenumber">351</context></context-group>
</trans-unit>
- <trans-unit id="_msg183">
+ <trans-unit id="_msg193">
<source xml:space="preserve">List mode</source>
<context-group purpose="location"><context context-type="linenumber">364</context></context-group>
</trans-unit>
- <trans-unit id="_msg184">
+ <trans-unit id="_msg194">
<source xml:space="preserve">Amount</source>
<context-group purpose="location"><context context-type="linenumber">420</context></context-group>
</trans-unit>
- <trans-unit id="_msg185">
+ <trans-unit id="_msg195">
<source xml:space="preserve">Received with label</source>
<context-group purpose="location"><context context-type="linenumber">425</context></context-group>
</trans-unit>
- <trans-unit id="_msg186">
+ <trans-unit id="_msg196">
<source xml:space="preserve">Received with address</source>
<context-group purpose="location"><context context-type="linenumber">430</context></context-group>
</trans-unit>
- <trans-unit id="_msg187">
+ <trans-unit id="_msg197">
<source xml:space="preserve">Date</source>
<context-group purpose="location"><context context-type="linenumber">435</context></context-group>
</trans-unit>
- <trans-unit id="_msg188">
+ <trans-unit id="_msg198">
<source xml:space="preserve">Confirmations</source>
<context-group purpose="location"><context context-type="linenumber">440</context></context-group>
</trans-unit>
- <trans-unit id="_msg189">
+ <trans-unit id="_msg199">
<source xml:space="preserve">Confirmed</source>
<context-group purpose="location"><context context-type="linenumber">443</context></context-group>
</trans-unit>
@@ -836,232 +882,263 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../coincontroldialog.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="CoinControlDialog">
- <trans-unit id="_msg190">
+ <trans-unit id="_msg200">
<source xml:space="preserve">Copy amount</source>
<context-group purpose="location"><context context-type="linenumber">69</context></context-group>
</trans-unit>
- <trans-unit id="_msg191">
+ <trans-unit id="_msg201">
<source xml:space="preserve">&amp;Copy address</source>
<context-group purpose="location"><context context-type="linenumber">58</context></context-group>
</trans-unit>
- <trans-unit id="_msg192">
+ <trans-unit id="_msg202">
<source xml:space="preserve">Copy &amp;label</source>
<context-group purpose="location"><context context-type="linenumber">59</context></context-group>
</trans-unit>
- <trans-unit id="_msg193">
+ <trans-unit id="_msg203">
<source xml:space="preserve">Copy &amp;amount</source>
<context-group purpose="location"><context context-type="linenumber">60</context></context-group>
</trans-unit>
- <trans-unit id="_msg194">
+ <trans-unit id="_msg204">
<source xml:space="preserve">Copy transaction &amp;ID and output index</source>
<context-group purpose="location"><context context-type="linenumber">61</context></context-group>
</trans-unit>
- <trans-unit id="_msg195">
+ <trans-unit id="_msg205">
<source xml:space="preserve">L&amp;ock unspent</source>
<context-group purpose="location"><context context-type="linenumber">63</context></context-group>
</trans-unit>
- <trans-unit id="_msg196">
+ <trans-unit id="_msg206">
<source xml:space="preserve">&amp;Unlock unspent</source>
<context-group purpose="location"><context context-type="linenumber">64</context></context-group>
</trans-unit>
- <trans-unit id="_msg197">
+ <trans-unit id="_msg207">
<source xml:space="preserve">Copy quantity</source>
<context-group purpose="location"><context context-type="linenumber">68</context></context-group>
</trans-unit>
- <trans-unit id="_msg198">
+ <trans-unit id="_msg208">
<source xml:space="preserve">Copy fee</source>
<context-group purpose="location"><context context-type="linenumber">70</context></context-group>
</trans-unit>
- <trans-unit id="_msg199">
+ <trans-unit id="_msg209">
<source xml:space="preserve">Copy after fee</source>
<context-group purpose="location"><context context-type="linenumber">71</context></context-group>
</trans-unit>
- <trans-unit id="_msg200">
+ <trans-unit id="_msg210">
<source xml:space="preserve">Copy bytes</source>
<context-group purpose="location"><context context-type="linenumber">72</context></context-group>
</trans-unit>
- <trans-unit id="_msg201">
+ <trans-unit id="_msg211">
<source xml:space="preserve">Copy dust</source>
<context-group purpose="location"><context context-type="linenumber">73</context></context-group>
</trans-unit>
- <trans-unit id="_msg202">
+ <trans-unit id="_msg212">
<source xml:space="preserve">Copy change</source>
<context-group purpose="location"><context context-type="linenumber">74</context></context-group>
</trans-unit>
- <trans-unit id="_msg203">
+ <trans-unit id="_msg213">
<source xml:space="preserve">(%1 locked)</source>
<context-group purpose="location"><context context-type="linenumber">380</context></context-group>
</trans-unit>
- <trans-unit id="_msg204">
+ <trans-unit id="_msg214">
<source xml:space="preserve">yes</source>
- <context-group purpose="location"><context context-type="linenumber">535</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">534</context></context-group>
</trans-unit>
- <trans-unit id="_msg205">
+ <trans-unit id="_msg215">
<source xml:space="preserve">no</source>
- <context-group purpose="location"><context context-type="linenumber">535</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">534</context></context-group>
</trans-unit>
- <trans-unit id="_msg206">
+ <trans-unit id="_msg216">
<source xml:space="preserve">This label turns red if any recipient receives an amount smaller than the current dust threshold.</source>
- <context-group purpose="location"><context context-type="linenumber">549</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">548</context></context-group>
</trans-unit>
- <trans-unit id="_msg207">
+ <trans-unit id="_msg217">
<source xml:space="preserve">Can vary +/- %1 satoshi(s) per input.</source>
- <context-group purpose="location"><context context-type="linenumber">554</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">553</context></context-group>
</trans-unit>
- <trans-unit id="_msg208">
+ <trans-unit id="_msg218">
<source xml:space="preserve">(no label)</source>
- <context-group purpose="location"><context context-type="linenumber">601</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">655</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">600</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">654</context></context-group>
</trans-unit>
- <trans-unit id="_msg209">
+ <trans-unit id="_msg219">
<source xml:space="preserve">change from %1 (%2)</source>
- <context-group purpose="location"><context context-type="linenumber">648</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">647</context></context-group>
</trans-unit>
- <trans-unit id="_msg210">
+ <trans-unit id="_msg220">
<source xml:space="preserve">(change)</source>
- <context-group purpose="location"><context context-type="linenumber">649</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">648</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../walletcontroller.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="CreateWalletActivity">
- <trans-unit id="_msg211">
+ <trans-unit id="_msg221">
<source xml:space="preserve">Create Wallet</source>
- <context-group purpose="location"><context context-type="linenumber">243</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">244</context></context-group>
<note annotates="source" from="developer">Title of window indicating the progress of creation of a new wallet.</note>
</trans-unit>
- <trans-unit id="_msg212">
+ <trans-unit id="_msg222">
<source xml:space="preserve">Creating Wallet &lt;b&gt;%1&lt;/b&gt;…</source>
- <context-group purpose="location"><context context-type="linenumber">246</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">247</context></context-group>
<note annotates="source" from="developer">Descriptive text of the create wallet progress window which indicates to the user which wallet is currently being created.</note>
</trans-unit>
- <trans-unit id="_msg213">
+ <trans-unit id="_msg223">
<source xml:space="preserve">Create wallet failed</source>
- <context-group purpose="location"><context context-type="linenumber">275</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">280</context></context-group>
</trans-unit>
- <trans-unit id="_msg214">
+ <trans-unit id="_msg224">
<source xml:space="preserve">Create wallet warning</source>
- <context-group purpose="location"><context context-type="linenumber">277</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">282</context></context-group>
</trans-unit>
- <trans-unit id="_msg215">
+ <trans-unit id="_msg225">
<source xml:space="preserve">Can&apos;t list signers</source>
- <context-group purpose="location"><context context-type="linenumber">293</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">298</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg226">
+ <source xml:space="preserve">Too many external signers found</source>
+ <context-group purpose="location"><context context-type="linenumber">301</context></context-group>
</trans-unit>
</group>
<group restype="x-trolltech-linguist-context" resname="LoadWalletsActivity">
- <trans-unit id="_msg216">
+ <trans-unit id="_msg227">
<source xml:space="preserve">Load Wallets</source>
- <context-group purpose="location"><context context-type="linenumber">362</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">375</context></context-group>
<note annotates="source" from="developer">Title of progress window which is displayed when wallets are being loaded.</note>
</trans-unit>
- <trans-unit id="_msg217">
+ <trans-unit id="_msg228">
<source xml:space="preserve">Loading wallets…</source>
- <context-group purpose="location"><context context-type="linenumber">365</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">378</context></context-group>
<note annotates="source" from="developer">Descriptive text of the load wallets progress window which indicates to the user that wallets are currently being loaded.</note>
</trans-unit>
</group>
<group restype="x-trolltech-linguist-context" resname="OpenWalletActivity">
- <trans-unit id="_msg218">
+ <trans-unit id="_msg229">
<source xml:space="preserve">Open wallet failed</source>
- <context-group purpose="location"><context context-type="linenumber">323</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">332</context></context-group>
</trans-unit>
- <trans-unit id="_msg219">
+ <trans-unit id="_msg230">
<source xml:space="preserve">Open wallet warning</source>
- <context-group purpose="location"><context context-type="linenumber">325</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">334</context></context-group>
</trans-unit>
- <trans-unit id="_msg220">
+ <trans-unit id="_msg231">
<source xml:space="preserve">default wallet</source>
- <context-group purpose="location"><context context-type="linenumber">335</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">344</context></context-group>
</trans-unit>
- <trans-unit id="_msg221">
+ <trans-unit id="_msg232">
<source xml:space="preserve">Open Wallet</source>
- <context-group purpose="location"><context context-type="linenumber">339</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">348</context></context-group>
<note annotates="source" from="developer">Title of window indicating the progress of opening of a wallet.</note>
</trans-unit>
- <trans-unit id="_msg222">
+ <trans-unit id="_msg233">
<source xml:space="preserve">Opening Wallet &lt;b&gt;%1&lt;/b&gt;…</source>
- <context-group purpose="location"><context context-type="linenumber">342</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">351</context></context-group>
<note annotates="source" from="developer">Descriptive text of the open wallet progress window which indicates to the user which wallet is currently being opened.</note>
</trans-unit>
</group>
+ <group restype="x-trolltech-linguist-context" resname="RestoreWalletActivity">
+ <trans-unit id="_msg234">
+ <source xml:space="preserve">Restore Wallet</source>
+ <context-group purpose="location"><context context-type="linenumber">400</context></context-group>
+ <note annotates="source" from="developer">Title of progress window which is displayed when wallets are being restored.</note>
+ </trans-unit>
+ <trans-unit id="_msg235">
+ <source xml:space="preserve">Restoring Wallet &lt;b&gt;%1&lt;/b&gt;…</source>
+ <context-group purpose="location"><context context-type="linenumber">403</context></context-group>
+ <note annotates="source" from="developer">Descriptive text of the restore wallets progress window which indicates to the user that wallets are currently being restored.</note>
+ </trans-unit>
+ <trans-unit id="_msg236">
+ <source xml:space="preserve">Restore wallet failed</source>
+ <context-group purpose="location"><context context-type="linenumber">422</context></context-group>
+ <note annotates="source" from="developer">Title of message box which is displayed when the wallet could not be restored.</note>
+ </trans-unit>
+ <trans-unit id="_msg237">
+ <source xml:space="preserve">Restore wallet warning</source>
+ <context-group purpose="location"><context context-type="linenumber">425</context></context-group>
+ <note annotates="source" from="developer">Title of message box which is displayed when the wallet is restored with some warning.</note>
+ </trans-unit>
+ <trans-unit id="_msg238">
+ <source xml:space="preserve">Restore wallet message</source>
+ <context-group purpose="location"><context context-type="linenumber">428</context></context-group>
+ <note annotates="source" from="developer">Title of message box which is displayed when the wallet is successfully restored.</note>
+ </trans-unit>
+ </group>
<group restype="x-trolltech-linguist-context" resname="WalletController">
- <trans-unit id="_msg223">
+ <trans-unit id="_msg239">
<source xml:space="preserve">Close wallet</source>
- <context-group purpose="location"><context context-type="linenumber">83</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">84</context></context-group>
</trans-unit>
- <trans-unit id="_msg224">
+ <trans-unit id="_msg240">
<source xml:space="preserve">Are you sure you wish to close the wallet &lt;i&gt;%1&lt;/i&gt;?</source>
- <context-group purpose="location"><context context-type="linenumber">84</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">85</context></context-group>
</trans-unit>
- <trans-unit id="_msg225">
+ <trans-unit id="_msg241">
<source xml:space="preserve">Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled.</source>
- <context-group purpose="location"><context context-type="linenumber">85</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">86</context></context-group>
</trans-unit>
- <trans-unit id="_msg226">
+ <trans-unit id="_msg242">
<source xml:space="preserve">Close all wallets</source>
- <context-group purpose="location"><context context-type="linenumber">98</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">99</context></context-group>
</trans-unit>
- <trans-unit id="_msg227">
+ <trans-unit id="_msg243">
<source xml:space="preserve">Are you sure you wish to close all wallets?</source>
- <context-group purpose="location"><context context-type="linenumber">99</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">100</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../forms/createwalletdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="CreateWalletDialog">
- <trans-unit id="_msg228">
+ <trans-unit id="_msg244">
<source xml:space="preserve">Create Wallet</source>
<context-group purpose="location"><context context-type="linenumber">14</context></context-group>
</trans-unit>
- <trans-unit id="_msg229">
+ <trans-unit id="_msg245">
<source xml:space="preserve">Wallet Name</source>
<context-group purpose="location"><context context-type="linenumber">25</context></context-group>
</trans-unit>
- <trans-unit id="_msg230">
+ <trans-unit id="_msg246">
<source xml:space="preserve">Wallet</source>
<context-group purpose="location"><context context-type="linenumber">38</context></context-group>
</trans-unit>
- <trans-unit id="_msg231">
+ <trans-unit id="_msg247">
<source xml:space="preserve">Encrypt the wallet. The wallet will be encrypted with a passphrase of your choice.</source>
<context-group purpose="location"><context context-type="linenumber">47</context></context-group>
</trans-unit>
- <trans-unit id="_msg232">
+ <trans-unit id="_msg248">
<source xml:space="preserve">Encrypt Wallet</source>
<context-group purpose="location"><context context-type="linenumber">50</context></context-group>
</trans-unit>
- <trans-unit id="_msg233">
+ <trans-unit id="_msg249">
<source xml:space="preserve">Advanced Options</source>
<context-group purpose="location"><context context-type="linenumber">76</context></context-group>
</trans-unit>
- <trans-unit id="_msg234">
+ <trans-unit id="_msg250">
<source xml:space="preserve">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.</source>
<context-group purpose="location"><context context-type="linenumber">85</context></context-group>
</trans-unit>
- <trans-unit id="_msg235">
+ <trans-unit id="_msg251">
<source xml:space="preserve">Disable Private Keys</source>
<context-group purpose="location"><context context-type="linenumber">88</context></context-group>
</trans-unit>
- <trans-unit id="_msg236">
+ <trans-unit id="_msg252">
<source xml:space="preserve">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.</source>
<context-group purpose="location"><context context-type="linenumber">95</context></context-group>
</trans-unit>
- <trans-unit id="_msg237">
+ <trans-unit id="_msg253">
<source xml:space="preserve">Make Blank Wallet</source>
<context-group purpose="location"><context context-type="linenumber">98</context></context-group>
</trans-unit>
- <trans-unit id="_msg238">
+ <trans-unit id="_msg254">
<source xml:space="preserve">Use descriptors for scriptPubKey management</source>
<context-group purpose="location"><context context-type="linenumber">105</context></context-group>
</trans-unit>
- <trans-unit id="_msg239">
+ <trans-unit id="_msg255">
<source xml:space="preserve">Descriptor Wallet</source>
<context-group purpose="location"><context context-type="linenumber">108</context></context-group>
</trans-unit>
- <trans-unit id="_msg240">
+ <trans-unit id="_msg256">
<source xml:space="preserve">Use an external signing device such as a hardware wallet. Configure the external signer script in wallet preferences first.</source>
<context-group purpose="location"><context context-type="linenumber">118</context></context-group>
</trans-unit>
- <trans-unit id="_msg241">
+ <trans-unit id="_msg257">
<source xml:space="preserve">External signer</source>
<context-group purpose="location"><context context-type="linenumber">121</context></context-group>
</trans-unit>
@@ -1069,15 +1146,15 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../createwalletdialog.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="CreateWalletDialog">
- <trans-unit id="_msg242">
+ <trans-unit id="_msg258">
<source xml:space="preserve">Create</source>
<context-group purpose="location"><context context-type="linenumber">22</context></context-group>
</trans-unit>
- <trans-unit id="_msg243">
+ <trans-unit id="_msg259">
<source xml:space="preserve">Compiled without sqlite support (required for descriptor wallets)</source>
<context-group purpose="location"><context context-type="linenumber">90</context></context-group>
</trans-unit>
- <trans-unit id="_msg244">
+ <trans-unit id="_msg260">
<source xml:space="preserve">Compiled without external signing support (required for external signing)</source>
<context-group purpose="location"><context context-type="linenumber">104</context></context-group>
<note annotates="source" from="developer">&quot;External signing&quot; means using devices such as hardware wallets.</note>
@@ -1086,23 +1163,23 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../forms/editaddressdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="EditAddressDialog">
- <trans-unit id="_msg245">
+ <trans-unit id="_msg261">
<source xml:space="preserve">Edit Address</source>
<context-group purpose="location"><context context-type="linenumber">14</context></context-group>
</trans-unit>
- <trans-unit id="_msg246">
+ <trans-unit id="_msg262">
<source xml:space="preserve">&amp;Label</source>
<context-group purpose="location"><context context-type="linenumber">25</context></context-group>
</trans-unit>
- <trans-unit id="_msg247">
+ <trans-unit id="_msg263">
<source xml:space="preserve">The label associated with this address list entry</source>
<context-group purpose="location"><context context-type="linenumber">35</context></context-group>
</trans-unit>
- <trans-unit id="_msg248">
+ <trans-unit id="_msg264">
<source xml:space="preserve">The address associated with this address list entry. This can only be modified for sending addresses.</source>
<context-group purpose="location"><context context-type="linenumber">52</context></context-group>
</trans-unit>
- <trans-unit id="_msg249">
+ <trans-unit id="_msg265">
<source xml:space="preserve">&amp;Address</source>
<context-group purpose="location"><context context-type="linenumber">42</context></context-group>
</trans-unit>
@@ -1110,35 +1187,35 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../editaddressdialog.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="EditAddressDialog">
- <trans-unit id="_msg250">
+ <trans-unit id="_msg266">
<source xml:space="preserve">New sending address</source>
<context-group purpose="location"><context context-type="linenumber">29</context></context-group>
</trans-unit>
- <trans-unit id="_msg251">
+ <trans-unit id="_msg267">
<source xml:space="preserve">Edit receiving address</source>
<context-group purpose="location"><context context-type="linenumber">32</context></context-group>
</trans-unit>
- <trans-unit id="_msg252">
+ <trans-unit id="_msg268">
<source xml:space="preserve">Edit sending address</source>
<context-group purpose="location"><context context-type="linenumber">36</context></context-group>
</trans-unit>
- <trans-unit id="_msg253">
+ <trans-unit id="_msg269">
<source xml:space="preserve">The entered address &quot;%1&quot; is not a valid Bitcoin address.</source>
<context-group purpose="location"><context context-type="linenumber">113</context></context-group>
</trans-unit>
- <trans-unit id="_msg254">
+ <trans-unit id="_msg270">
<source xml:space="preserve">Address &quot;%1&quot; already exists as a receiving address with label &quot;%2&quot; and so cannot be added as a sending address.</source>
<context-group purpose="location"><context context-type="linenumber">146</context></context-group>
</trans-unit>
- <trans-unit id="_msg255">
+ <trans-unit id="_msg271">
<source xml:space="preserve">The entered address &quot;%1&quot; is already in the address book with label &quot;%2&quot;.</source>
<context-group purpose="location"><context context-type="linenumber">151</context></context-group>
</trans-unit>
- <trans-unit id="_msg256">
+ <trans-unit id="_msg272">
<source xml:space="preserve">Could not unlock wallet.</source>
<context-group purpose="location"><context context-type="linenumber">123</context></context-group>
</trans-unit>
- <trans-unit id="_msg257">
+ <trans-unit id="_msg273">
<source xml:space="preserve">New key generation failed.</source>
<context-group purpose="location"><context context-type="linenumber">128</context></context-group>
</trans-unit>
@@ -1146,75 +1223,90 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../intro.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="FreespaceChecker">
- <trans-unit id="_msg258">
+ <trans-unit id="_msg274">
<source xml:space="preserve">A new data directory will be created.</source>
<context-group purpose="location"><context context-type="linenumber">73</context></context-group>
</trans-unit>
- <trans-unit id="_msg259">
+ <trans-unit id="_msg275">
<source xml:space="preserve">name</source>
<context-group purpose="location"><context context-type="linenumber">95</context></context-group>
</trans-unit>
- <trans-unit id="_msg260">
+ <trans-unit id="_msg276">
<source xml:space="preserve">Directory already exists. Add %1 if you intend to create a new directory here.</source>
<context-group purpose="location"><context context-type="linenumber">97</context></context-group>
</trans-unit>
- <trans-unit id="_msg261">
+ <trans-unit id="_msg277">
<source xml:space="preserve">Path already exists, and is not a directory.</source>
<context-group purpose="location"><context context-type="linenumber">100</context></context-group>
</trans-unit>
- <trans-unit id="_msg262">
+ <trans-unit id="_msg278">
<source xml:space="preserve">Cannot create data directory here.</source>
<context-group purpose="location"><context context-type="linenumber">107</context></context-group>
</trans-unit>
</group>
<group restype="x-trolltech-linguist-context" resname="Intro">
- <trans-unit id="_msg263">
+ <trans-unit id="_msg279">
<source xml:space="preserve">Bitcoin</source>
<context-group purpose="location"><context context-type="linenumber">139</context></context-group>
</trans-unit>
- <trans-unit id="_msg264">
- <source xml:space="preserve">%1 GB of space available</source>
+ <group restype="x-gettext-plurals">
<context-group purpose="location"><context context-type="linenumber">301</context></context-group>
- </trans-unit>
- <trans-unit id="_msg265">
- <source xml:space="preserve">(of %1 GB needed)</source>
+ <trans-unit id="_msg280[0]">
+ <source xml:space="preserve">%n GB of space available</source>
+ </trans-unit>
+ <trans-unit id="_msg280[1]">
+ <source xml:space="preserve">%n GB of space available</source>
+ </trans-unit>
+ </group>
+ <group restype="x-gettext-plurals">
<context-group purpose="location"><context context-type="linenumber">303</context></context-group>
- </trans-unit>
- <trans-unit id="_msg266">
- <source xml:space="preserve">(%1 GB needed for full chain)</source>
+ <trans-unit id="_msg281[0]">
+ <source xml:space="preserve">(of %n GB needed)</source>
+ </trans-unit>
+ <trans-unit id="_msg281[1]">
+ <source xml:space="preserve">(of %n GB needed)</source>
+ </trans-unit>
+ </group>
+ <group restype="x-gettext-plurals">
<context-group purpose="location"><context context-type="linenumber">306</context></context-group>
- </trans-unit>
- <trans-unit id="_msg267">
+ <trans-unit id="_msg282[0]">
+ <source xml:space="preserve">(%n GB needed for full chain)</source>
+ </trans-unit>
+ <trans-unit id="_msg282[1]">
+ <source xml:space="preserve">(%n GB needed for full chain)</source>
+ </trans-unit>
+ </group>
+ <trans-unit id="_msg283">
<source xml:space="preserve">At least %1 GB of data will be stored in this directory, and it will grow over time.</source>
<context-group purpose="location"><context context-type="linenumber">378</context></context-group>
</trans-unit>
- <trans-unit id="_msg268">
+ <trans-unit id="_msg284">
<source xml:space="preserve">Approximately %1 GB of data will be stored in this directory.</source>
<context-group purpose="location"><context context-type="linenumber">381</context></context-group>
</trans-unit>
<group restype="x-gettext-plurals">
<context-group purpose="location"><context context-type="linenumber">390</context></context-group>
<note annotates="source" from="developer">Explanatory text on the capability of the current prune target.</note>
- <trans-unit id="_msg269[0]">
+ <trans-unit id="_msg285[0]">
<source xml:space="preserve">(sufficient to restore backups %n day(s) old)</source>
</trans-unit>
- <trans-unit id="_msg269[1]">
+ <trans-unit id="_msg285[1]">
<source xml:space="preserve">(sufficient to restore backups %n day(s) old)</source>
</trans-unit>
</group>
- <trans-unit id="_msg270">
+ <trans-unit id="_msg286">
<source xml:space="preserve">%1 will download and store a copy of the Bitcoin block chain.</source>
<context-group purpose="location"><context context-type="linenumber">392</context></context-group>
</trans-unit>
- <trans-unit id="_msg271">
+ <trans-unit id="_msg287">
<source xml:space="preserve">The wallet will also be stored in this directory.</source>
<context-group purpose="location"><context context-type="linenumber">394</context></context-group>
</trans-unit>
- <trans-unit id="_msg272">
+ <trans-unit id="_msg288">
<source xml:space="preserve">Error: Specified data directory &quot;%1&quot; cannot be created.</source>
<context-group purpose="location"><context context-type="linenumber">250</context></context-group>
</trans-unit>
- <trans-unit id="_msg273">
+ <trans-unit id="_msg289">
<source xml:space="preserve">Error</source>
<context-group purpose="location"><context context-type="linenumber">280</context></context-group>
</trans-unit>
@@ -1222,25 +1314,25 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../utilitydialog.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="HelpMessageDialog">
- <trans-unit id="_msg274">
+ <trans-unit id="_msg290">
<source xml:space="preserve">version</source>
- <context-group purpose="location"><context context-type="linenumber">37</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">38</context></context-group>
</trans-unit>
- <trans-unit id="_msg275">
+ <trans-unit id="_msg291">
<source xml:space="preserve">About %1</source>
- <context-group purpose="location"><context context-type="linenumber">41</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">42</context></context-group>
</trans-unit>
- <trans-unit id="_msg276">
+ <trans-unit id="_msg292">
<source xml:space="preserve">Command-line options</source>
<context-group purpose="location"><context context-type="linenumber">60</context></context-group>
</trans-unit>
</group>
<group restype="x-trolltech-linguist-context" resname="ShutdownWindow">
- <trans-unit id="_msg277">
+ <trans-unit id="_msg293">
<source xml:space="preserve">%1 is shutting down…</source>
<context-group purpose="location"><context context-type="linenumber">145</context></context-group>
</trans-unit>
- <trans-unit id="_msg278">
+ <trans-unit id="_msg294">
<source xml:space="preserve">Do not shut down the computer until this window disappears.</source>
<context-group purpose="location"><context context-type="linenumber">146</context></context-group>
</trans-unit>
@@ -1248,47 +1340,47 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../forms/intro.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="Intro">
- <trans-unit id="_msg279">
+ <trans-unit id="_msg295">
<source xml:space="preserve">Welcome</source>
<context-group purpose="location"><context context-type="linenumber">14</context></context-group>
</trans-unit>
- <trans-unit id="_msg280">
+ <trans-unit id="_msg296">
<source xml:space="preserve">Welcome to %1.</source>
<context-group purpose="location"><context context-type="linenumber">23</context></context-group>
</trans-unit>
- <trans-unit id="_msg281">
+ <trans-unit id="_msg297">
<source xml:space="preserve">As this is the first time the program is launched, you can choose where %1 will store its data.</source>
<context-group purpose="location"><context context-type="linenumber">49</context></context-group>
</trans-unit>
- <trans-unit id="_msg282">
- <source xml:space="preserve">When you click OK, %1 will begin to download and process the full %4 block chain (%2GB) starting with the earliest transactions in %3 when %4 initially launched.</source>
- <context-group purpose="location"><context context-type="linenumber">206</context></context-group>
- </trans-unit>
- <trans-unit id="_msg283">
+ <trans-unit id="_msg298">
<source xml:space="preserve">Limit block chain storage to</source>
<context-group purpose="location"><context context-type="linenumber">238</context></context-group>
</trans-unit>
- <trans-unit id="_msg284">
+ <trans-unit id="_msg299">
<source xml:space="preserve">Reverting this setting requires re-downloading the entire blockchain. It is faster to download the full chain first and prune it later. Disables some advanced features.</source>
<context-group purpose="location"><context context-type="linenumber">241</context></context-group>
</trans-unit>
- <trans-unit id="_msg285">
+ <trans-unit id="_msg300">
<source xml:space="preserve"> GB</source>
<context-group purpose="location"><context context-type="linenumber">248</context></context-group>
</trans-unit>
- <trans-unit id="_msg286">
+ <trans-unit id="_msg301">
<source xml:space="preserve">This initial synchronisation is very demanding, and may expose hardware problems with your computer that had previously gone unnoticed. Each time you run %1, it will continue downloading where it left off.</source>
<context-group purpose="location"><context context-type="linenumber">216</context></context-group>
</trans-unit>
- <trans-unit id="_msg287">
+ <trans-unit id="_msg302">
+ <source xml:space="preserve">When you click OK, %1 will begin to download and process the full %4 block chain (%2 GB) starting with the earliest transactions in %3 when %4 initially launched.</source>
+ <context-group purpose="location"><context context-type="linenumber">206</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg303">
<source xml:space="preserve">If you have chosen to limit block chain storage (pruning), the historical data must still be downloaded and processed, but will be deleted afterward to keep your disk usage low.</source>
<context-group purpose="location"><context context-type="linenumber">226</context></context-group>
</trans-unit>
- <trans-unit id="_msg288">
+ <trans-unit id="_msg304">
<source xml:space="preserve">Use the default data directory</source>
<context-group purpose="location"><context context-type="linenumber">66</context></context-group>
</trans-unit>
- <trans-unit id="_msg289">
+ <trans-unit id="_msg305">
<source xml:space="preserve">Use a custom data directory:</source>
<context-group purpose="location"><context context-type="linenumber">73</context></context-group>
</trans-unit>
@@ -1296,54 +1388,54 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../forms/modaloverlay.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="ModalOverlay">
- <trans-unit id="_msg290">
+ <trans-unit id="_msg306">
<source xml:space="preserve">Form</source>
<context-group purpose="location"><context context-type="linenumber">14</context></context-group>
</trans-unit>
- <trans-unit id="_msg291">
+ <trans-unit id="_msg307">
<source xml:space="preserve">Recent transactions may not yet be visible, and therefore your wallet&apos;s balance might be incorrect. This information will be correct once your wallet has finished synchronizing with the bitcoin network, as detailed below.</source>
<context-group purpose="location"><context context-type="linenumber">133</context></context-group>
</trans-unit>
- <trans-unit id="_msg292">
+ <trans-unit id="_msg308">
<source xml:space="preserve">Attempting to spend bitcoins that are affected by not-yet-displayed transactions will not be accepted by the network.</source>
<context-group purpose="location"><context context-type="linenumber">152</context></context-group>
</trans-unit>
- <trans-unit id="_msg293">
+ <trans-unit id="_msg309">
<source xml:space="preserve">Number of blocks left</source>
<context-group purpose="location"><context context-type="linenumber">215</context></context-group>
</trans-unit>
- <trans-unit id="_msg294">
+ <trans-unit id="_msg310">
<source xml:space="preserve">Unknown…</source>
<context-group purpose="location"><context context-type="linenumber">222</context></context-group>
<context-group purpose="location"><context context-type="linenumber">248</context></context-group>
- <context-group purpose="location"><context context-type="sourcefile">../modaloverlay.cpp</context><context context-type="linenumber">152</context></context-group>
+ <context-group purpose="location"><context context-type="sourcefile">../modaloverlay.cpp</context><context context-type="linenumber">155</context></context-group>
</trans-unit>
- <trans-unit id="_msg295">
+ <trans-unit id="_msg311">
<source xml:space="preserve">calculating…</source>
<context-group purpose="location"><context context-type="linenumber">292</context></context-group>
<context-group purpose="location"><context context-type="linenumber">312</context></context-group>
</trans-unit>
- <trans-unit id="_msg296">
+ <trans-unit id="_msg312">
<source xml:space="preserve">Last block time</source>
<context-group purpose="location"><context context-type="linenumber">235</context></context-group>
</trans-unit>
- <trans-unit id="_msg297">
+ <trans-unit id="_msg313">
<source xml:space="preserve">Progress</source>
<context-group purpose="location"><context context-type="linenumber">261</context></context-group>
</trans-unit>
- <trans-unit id="_msg298">
+ <trans-unit id="_msg314">
<source xml:space="preserve">Progress increase per hour</source>
<context-group purpose="location"><context context-type="linenumber">285</context></context-group>
</trans-unit>
- <trans-unit id="_msg299">
+ <trans-unit id="_msg315">
<source xml:space="preserve">Estimated time left until synced</source>
<context-group purpose="location"><context context-type="linenumber">305</context></context-group>
</trans-unit>
- <trans-unit id="_msg300">
+ <trans-unit id="_msg316">
<source xml:space="preserve">Hide</source>
<context-group purpose="location"><context context-type="linenumber">342</context></context-group>
</trans-unit>
- <trans-unit id="_msg301">
+ <trans-unit id="_msg317">
<source xml:space="preserve">Esc</source>
<context-group purpose="location"><context context-type="linenumber">345</context></context-group>
</trans-unit>
@@ -1351,33 +1443,37 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../modaloverlay.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="ModalOverlay">
- <trans-unit id="_msg302">
+ <trans-unit id="_msg318">
<source xml:space="preserve">%1 is currently syncing. It will download headers and blocks from peers and validate them until reaching the tip of the block chain.</source>
<context-group purpose="location"><context context-type="linenumber">34</context></context-group>
</trans-unit>
- <trans-unit id="_msg303">
+ <trans-unit id="_msg319">
<source xml:space="preserve">Unknown. Syncing Headers (%1, %2%)…</source>
- <context-group purpose="location"><context context-type="linenumber">158</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">161</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg320">
+ <source xml:space="preserve">Unknown. Pre-syncing Headers (%1, %2%)…</source>
+ <context-group purpose="location"><context context-type="linenumber">166</context></context-group>
</trans-unit>
</group>
<group restype="x-trolltech-linguist-context" resname="QObject">
- <trans-unit id="_msg304">
+ <trans-unit id="_msg321">
<source xml:space="preserve">unknown</source>
- <context-group purpose="location"><context context-type="linenumber">123</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">126</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../forms/openuridialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="OpenURIDialog">
- <trans-unit id="_msg305">
+ <trans-unit id="_msg322">
<source xml:space="preserve">Open bitcoin URI</source>
<context-group purpose="location"><context context-type="linenumber">14</context></context-group>
</trans-unit>
- <trans-unit id="_msg306">
+ <trans-unit id="_msg323">
<source xml:space="preserve">URI:</source>
<context-group purpose="location"><context context-type="linenumber">22</context></context-group>
</trans-unit>
- <trans-unit id="_msg307">
+ <trans-unit id="_msg324">
<source xml:space="preserve">Paste address from clipboard</source>
<context-group purpose="location"><context context-type="linenumber">36</context></context-group>
<note annotates="source" from="developer">Tooltip text for button that allows you to paste an address that is in your clipboard.</note>
@@ -1386,310 +1482,310 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../forms/optionsdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="OptionsDialog">
- <trans-unit id="_msg308">
+ <trans-unit id="_msg325">
<source xml:space="preserve">Options</source>
<context-group purpose="location"><context context-type="linenumber">14</context></context-group>
</trans-unit>
- <trans-unit id="_msg309">
+ <trans-unit id="_msg326">
<source xml:space="preserve">&amp;Main</source>
<context-group purpose="location"><context context-type="linenumber">27</context></context-group>
</trans-unit>
- <trans-unit id="_msg310">
+ <trans-unit id="_msg327">
<source xml:space="preserve">Automatically start %1 after logging in to the system.</source>
<context-group purpose="location"><context context-type="linenumber">33</context></context-group>
</trans-unit>
- <trans-unit id="_msg311">
+ <trans-unit id="_msg328">
<source xml:space="preserve">&amp;Start %1 on system login</source>
<context-group purpose="location"><context context-type="linenumber">36</context></context-group>
</trans-unit>
- <trans-unit id="_msg312">
+ <trans-unit id="_msg329">
<source xml:space="preserve">Enabling pruning significantly reduces the disk space required to store transactions. All blocks are still fully validated. Reverting this setting requires re-downloading the entire blockchain.</source>
<context-group purpose="location"><context context-type="linenumber">58</context></context-group>
</trans-unit>
- <trans-unit id="_msg313">
+ <trans-unit id="_msg330">
<source xml:space="preserve">Size of &amp;database cache</source>
<context-group purpose="location"><context context-type="linenumber">111</context></context-group>
</trans-unit>
- <trans-unit id="_msg314">
+ <trans-unit id="_msg331">
<source xml:space="preserve">Number of script &amp;verification threads</source>
<context-group purpose="location"><context context-type="linenumber">157</context></context-group>
</trans-unit>
- <trans-unit id="_msg315">
+ <trans-unit id="_msg332">
<source xml:space="preserve">IP address of the proxy (e.g. IPv4: 127.0.0.1 / IPv6: ::1)</source>
<context-group purpose="location"><context context-type="linenumber">388</context></context-group>
<context-group purpose="location"><context context-type="linenumber">575</context></context-group>
</trans-unit>
- <trans-unit id="_msg316">
+ <trans-unit id="_msg333">
<source xml:space="preserve">Shows if the supplied default SOCKS5 proxy is used to reach peers via this network type.</source>
<context-group purpose="location"><context context-type="linenumber">457</context></context-group>
<context-group purpose="location"><context context-type="linenumber">480</context></context-group>
<context-group purpose="location"><context context-type="linenumber">503</context></context-group>
</trans-unit>
- <trans-unit id="_msg317">
+ <trans-unit id="_msg334">
<source xml:space="preserve">Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Exit in the menu.</source>
<context-group purpose="location"><context context-type="linenumber">672</context></context-group>
</trans-unit>
- <trans-unit id="_msg318">
+ <trans-unit id="_msg335">
+ <source xml:space="preserve">Options set in this dialog are overridden by the command line:</source>
+ <context-group purpose="location"><context context-type="linenumber">899</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg336">
<source xml:space="preserve">Open the %1 configuration file from the working directory.</source>
<context-group purpose="location"><context context-type="linenumber">944</context></context-group>
</trans-unit>
- <trans-unit id="_msg319">
+ <trans-unit id="_msg337">
<source xml:space="preserve">Open Configuration File</source>
<context-group purpose="location"><context context-type="linenumber">947</context></context-group>
</trans-unit>
- <trans-unit id="_msg320">
+ <trans-unit id="_msg338">
<source xml:space="preserve">Reset all client options to default.</source>
<context-group purpose="location"><context context-type="linenumber">957</context></context-group>
</trans-unit>
- <trans-unit id="_msg321">
+ <trans-unit id="_msg339">
<source xml:space="preserve">&amp;Reset Options</source>
<context-group purpose="location"><context context-type="linenumber">960</context></context-group>
</trans-unit>
- <trans-unit id="_msg322">
+ <trans-unit id="_msg340">
<source xml:space="preserve">&amp;Network</source>
<context-group purpose="location"><context context-type="linenumber">315</context></context-group>
</trans-unit>
- <trans-unit id="_msg323">
+ <trans-unit id="_msg341">
<source xml:space="preserve">Prune &amp;block storage to</source>
<context-group purpose="location"><context context-type="linenumber">61</context></context-group>
</trans-unit>
- <trans-unit id="_msg324">
+ <trans-unit id="_msg342">
<source xml:space="preserve">GB</source>
<context-group purpose="location"><context context-type="linenumber">71</context></context-group>
</trans-unit>
- <trans-unit id="_msg325">
+ <trans-unit id="_msg343">
<source xml:space="preserve">Reverting this setting requires re-downloading the entire blockchain.</source>
<context-group purpose="location"><context context-type="linenumber">96</context></context-group>
</trans-unit>
- <trans-unit id="_msg326">
+ <trans-unit id="_msg344">
<source xml:space="preserve">Maximum database cache size. A larger cache can contribute to faster sync, after which the benefit is less pronounced for most use cases. Lowering the cache size will reduce memory usage. Unused mempool memory is shared for this cache.</source>
<context-group purpose="location"><context context-type="linenumber">108</context></context-group>
<note annotates="source" from="developer">Tooltip text for Options window setting that sets the size of the database cache. Explains the corresponding effects of increasing/decreasing this value.</note>
</trans-unit>
- <trans-unit id="_msg327">
+ <trans-unit id="_msg345">
<source xml:space="preserve">MiB</source>
<context-group purpose="location"><context context-type="linenumber">127</context></context-group>
</trans-unit>
- <trans-unit id="_msg328">
+ <trans-unit id="_msg346">
<source xml:space="preserve">Set the number of script verification threads. Negative values correspond to the number of cores you want to leave free to the system.</source>
<context-group purpose="location"><context context-type="linenumber">154</context></context-group>
<note annotates="source" from="developer">Tooltip text for Options window setting that sets the number of script verification threads. Explains that negative values mean to leave these many cores free to the system.</note>
</trans-unit>
- <trans-unit id="_msg329">
+ <trans-unit id="_msg347">
<source xml:space="preserve">(0 = auto, &lt;0 = leave that many cores free)</source>
<context-group purpose="location"><context context-type="linenumber">170</context></context-group>
</trans-unit>
- <trans-unit id="_msg330">
+ <trans-unit id="_msg348">
<source xml:space="preserve">This allows you or a third party tool to communicate with the node through command-line and JSON-RPC commands.</source>
<context-group purpose="location"><context context-type="linenumber">192</context></context-group>
<note annotates="source" from="developer">Tooltip text for Options window setting that enables the RPC server.</note>
</trans-unit>
- <trans-unit id="_msg331">
+ <trans-unit id="_msg349">
<source xml:space="preserve">Enable R&amp;PC server</source>
<context-group purpose="location"><context context-type="linenumber">195</context></context-group>
<note annotates="source" from="developer">An Options window setting to enable the RPC server.</note>
</trans-unit>
- <trans-unit id="_msg332">
+ <trans-unit id="_msg350">
<source xml:space="preserve">W&amp;allet</source>
<context-group purpose="location"><context context-type="linenumber">216</context></context-group>
</trans-unit>
- <trans-unit id="_msg333">
+ <trans-unit id="_msg351">
<source xml:space="preserve">Whether to set subtract fee from amount as default or not.</source>
<context-group purpose="location"><context context-type="linenumber">222</context></context-group>
<note annotates="source" from="developer">Tooltip text for Options window setting that sets subtracting the fee from a sending amount as default.</note>
</trans-unit>
- <trans-unit id="_msg334">
+ <trans-unit id="_msg352">
<source xml:space="preserve">Subtract &amp;fee from amount by default</source>
<context-group purpose="location"><context context-type="linenumber">225</context></context-group>
<note annotates="source" from="developer">An Options window setting to set subtracting the fee from a sending amount as default.</note>
</trans-unit>
- <trans-unit id="_msg335">
+ <trans-unit id="_msg353">
<source xml:space="preserve">Expert</source>
<context-group purpose="location"><context context-type="linenumber">232</context></context-group>
</trans-unit>
- <trans-unit id="_msg336">
+ <trans-unit id="_msg354">
<source xml:space="preserve">Enable coin &amp;control features</source>
<context-group purpose="location"><context context-type="linenumber">241</context></context-group>
</trans-unit>
- <trans-unit id="_msg337">
+ <trans-unit id="_msg355">
<source xml:space="preserve">If you disable the spending of unconfirmed change, the change from a transaction cannot be used until that transaction has at least one confirmation. This also affects how your balance is computed.</source>
<context-group purpose="location"><context context-type="linenumber">248</context></context-group>
</trans-unit>
- <trans-unit id="_msg338">
+ <trans-unit id="_msg356">
<source xml:space="preserve">&amp;Spend unconfirmed change</source>
<context-group purpose="location"><context context-type="linenumber">251</context></context-group>
</trans-unit>
- <trans-unit id="_msg339">
+ <trans-unit id="_msg357">
<source xml:space="preserve">Enable &amp;PSBT controls</source>
<context-group purpose="location"><context context-type="linenumber">258</context></context-group>
<note annotates="source" from="developer">An options window setting to enable PSBT controls.</note>
</trans-unit>
- <trans-unit id="_msg340">
+ <trans-unit id="_msg358">
<source xml:space="preserve">Whether to show PSBT controls.</source>
<context-group purpose="location"><context context-type="linenumber">261</context></context-group>
<note annotates="source" from="developer">Tooltip text for options window setting that enables PSBT controls.</note>
</trans-unit>
- <trans-unit id="_msg341">
+ <trans-unit id="_msg359">
<source xml:space="preserve">External Signer (e.g. hardware wallet)</source>
<context-group purpose="location"><context context-type="linenumber">271</context></context-group>
</trans-unit>
- <trans-unit id="_msg342">
+ <trans-unit id="_msg360">
<source xml:space="preserve">&amp;External signer script path</source>
<context-group purpose="location"><context context-type="linenumber">279</context></context-group>
</trans-unit>
- <trans-unit id="_msg343">
+ <trans-unit id="_msg361">
<source xml:space="preserve">Full path to a Bitcoin Core compatible script (e.g. C:\Downloads\hwi.exe or /Users/you/Downloads/hwi.py). Beware: malware can steal your coins!</source>
<context-group purpose="location"><context context-type="linenumber">289</context></context-group>
</trans-unit>
- <trans-unit id="_msg344">
+ <trans-unit id="_msg362">
<source xml:space="preserve">Automatically open the Bitcoin client port on the router. This only works when your router supports UPnP and it is enabled.</source>
<context-group purpose="location"><context context-type="linenumber">321</context></context-group>
</trans-unit>
- <trans-unit id="_msg345">
+ <trans-unit id="_msg363">
<source xml:space="preserve">Map port using &amp;UPnP</source>
<context-group purpose="location"><context context-type="linenumber">324</context></context-group>
</trans-unit>
- <trans-unit id="_msg346">
+ <trans-unit id="_msg364">
<source xml:space="preserve">Automatically open the Bitcoin client port on the router. This only works when your router supports NAT-PMP and it is enabled. The external port could be random.</source>
<context-group purpose="location"><context context-type="linenumber">331</context></context-group>
</trans-unit>
- <trans-unit id="_msg347">
+ <trans-unit id="_msg365">
<source xml:space="preserve">Map port using NA&amp;T-PMP</source>
<context-group purpose="location"><context context-type="linenumber">334</context></context-group>
</trans-unit>
- <trans-unit id="_msg348">
+ <trans-unit id="_msg366">
<source xml:space="preserve">Accept connections from outside.</source>
<context-group purpose="location"><context context-type="linenumber">341</context></context-group>
</trans-unit>
- <trans-unit id="_msg349">
+ <trans-unit id="_msg367">
<source xml:space="preserve">Allow incomin&amp;g connections</source>
<context-group purpose="location"><context context-type="linenumber">344</context></context-group>
</trans-unit>
- <trans-unit id="_msg350">
+ <trans-unit id="_msg368">
<source xml:space="preserve">Connect to the Bitcoin network through a SOCKS5 proxy.</source>
<context-group purpose="location"><context context-type="linenumber">351</context></context-group>
</trans-unit>
- <trans-unit id="_msg351">
+ <trans-unit id="_msg369">
<source xml:space="preserve">&amp;Connect through SOCKS5 proxy (default proxy):</source>
<context-group purpose="location"><context context-type="linenumber">354</context></context-group>
</trans-unit>
- <trans-unit id="_msg352">
+ <trans-unit id="_msg370">
<source xml:space="preserve">Proxy &amp;IP:</source>
<context-group purpose="location"><context context-type="linenumber">363</context></context-group>
<context-group purpose="location"><context context-type="linenumber">550</context></context-group>
</trans-unit>
- <trans-unit id="_msg353">
+ <trans-unit id="_msg371">
<source xml:space="preserve">&amp;Port:</source>
<context-group purpose="location"><context context-type="linenumber">395</context></context-group>
<context-group purpose="location"><context context-type="linenumber">582</context></context-group>
</trans-unit>
- <trans-unit id="_msg354">
+ <trans-unit id="_msg372">
<source xml:space="preserve">Port of the proxy (e.g. 9050)</source>
<context-group purpose="location"><context context-type="linenumber">420</context></context-group>
<context-group purpose="location"><context context-type="linenumber">607</context></context-group>
</trans-unit>
- <trans-unit id="_msg355">
+ <trans-unit id="_msg373">
<source xml:space="preserve">Used for reaching peers via:</source>
<context-group purpose="location"><context context-type="linenumber">444</context></context-group>
</trans-unit>
- <trans-unit id="_msg356">
+ <trans-unit id="_msg374">
<source xml:space="preserve">IPv4</source>
<context-group purpose="location"><context context-type="linenumber">467</context></context-group>
</trans-unit>
- <trans-unit id="_msg357">
+ <trans-unit id="_msg375">
<source xml:space="preserve">IPv6</source>
<context-group purpose="location"><context context-type="linenumber">490</context></context-group>
</trans-unit>
- <trans-unit id="_msg358">
+ <trans-unit id="_msg376">
<source xml:space="preserve">Tor</source>
<context-group purpose="location"><context context-type="linenumber">513</context></context-group>
</trans-unit>
- <trans-unit id="_msg359">
+ <trans-unit id="_msg377">
<source xml:space="preserve">&amp;Window</source>
<context-group purpose="location"><context context-type="linenumber">643</context></context-group>
</trans-unit>
- <trans-unit id="_msg360">
+ <trans-unit id="_msg378">
<source xml:space="preserve">Show the icon in the system tray.</source>
<context-group purpose="location"><context context-type="linenumber">649</context></context-group>
</trans-unit>
- <trans-unit id="_msg361">
+ <trans-unit id="_msg379">
<source xml:space="preserve">&amp;Show tray icon</source>
<context-group purpose="location"><context context-type="linenumber">652</context></context-group>
</trans-unit>
- <trans-unit id="_msg362">
+ <trans-unit id="_msg380">
<source xml:space="preserve">Show only a tray icon after minimizing the window.</source>
<context-group purpose="location"><context context-type="linenumber">662</context></context-group>
</trans-unit>
- <trans-unit id="_msg363">
+ <trans-unit id="_msg381">
<source xml:space="preserve">&amp;Minimize to the tray instead of the taskbar</source>
<context-group purpose="location"><context context-type="linenumber">665</context></context-group>
</trans-unit>
- <trans-unit id="_msg364">
+ <trans-unit id="_msg382">
<source xml:space="preserve">M&amp;inimize on close</source>
<context-group purpose="location"><context context-type="linenumber">675</context></context-group>
</trans-unit>
- <trans-unit id="_msg365">
+ <trans-unit id="_msg383">
<source xml:space="preserve">&amp;Display</source>
<context-group purpose="location"><context context-type="linenumber">696</context></context-group>
</trans-unit>
- <trans-unit id="_msg366">
+ <trans-unit id="_msg384">
<source xml:space="preserve">User Interface &amp;language:</source>
<context-group purpose="location"><context context-type="linenumber">704</context></context-group>
</trans-unit>
- <trans-unit id="_msg367">
+ <trans-unit id="_msg385">
<source xml:space="preserve">The user interface language can be set here. This setting will take effect after restarting %1.</source>
<context-group purpose="location"><context context-type="linenumber">717</context></context-group>
</trans-unit>
- <trans-unit id="_msg368">
+ <trans-unit id="_msg386">
<source xml:space="preserve">&amp;Unit to show amounts in:</source>
<context-group purpose="location"><context context-type="linenumber">728</context></context-group>
</trans-unit>
- <trans-unit id="_msg369">
+ <trans-unit id="_msg387">
<source xml:space="preserve">Choose the default subdivision unit to show in the interface and when sending coins.</source>
<context-group purpose="location"><context context-type="linenumber">741</context></context-group>
</trans-unit>
- <trans-unit id="_msg370">
+ <trans-unit id="_msg388">
<source xml:space="preserve">Third-party URLs (e.g. a block explorer) that appear in the transactions tab as context menu items. %s in the URL is replaced by transaction hash. Multiple URLs are separated by vertical bar |.</source>
<context-group purpose="location"><context context-type="linenumber">752</context></context-group>
<context-group purpose="location"><context context-type="linenumber">765</context></context-group>
</trans-unit>
- <trans-unit id="_msg371">
+ <trans-unit id="_msg389">
<source xml:space="preserve">&amp;Third-party transaction URLs</source>
<context-group purpose="location"><context context-type="linenumber">755</context></context-group>
</trans-unit>
- <trans-unit id="_msg372">
+ <trans-unit id="_msg390">
<source xml:space="preserve">Whether to show coin control features or not.</source>
<context-group purpose="location"><context context-type="linenumber">238</context></context-group>
</trans-unit>
- <trans-unit id="_msg373">
+ <trans-unit id="_msg391">
<source xml:space="preserve">Connect to the Bitcoin network through a separate SOCKS5 proxy for Tor onion services.</source>
<context-group purpose="location"><context context-type="linenumber">538</context></context-group>
</trans-unit>
- <trans-unit id="_msg374">
+ <trans-unit id="_msg392">
<source xml:space="preserve">Use separate SOCKS&amp;5 proxy to reach peers via Tor onion services:</source>
<context-group purpose="location"><context context-type="linenumber">541</context></context-group>
</trans-unit>
- <trans-unit id="_msg375">
+ <trans-unit id="_msg393">
<source xml:space="preserve">Monospaced font in the Overview tab:</source>
<context-group purpose="location"><context context-type="linenumber">777</context></context-group>
</trans-unit>
- <trans-unit id="_msg376">
+ <trans-unit id="_msg394">
<source xml:space="preserve">embedded &quot;%1&quot;</source>
<context-group purpose="location"><context context-type="linenumber">785</context></context-group>
</trans-unit>
- <trans-unit id="_msg377">
+ <trans-unit id="_msg395">
<source xml:space="preserve">closest matching &quot;%1&quot;</source>
<context-group purpose="location"><context context-type="linenumber">834</context></context-group>
</trans-unit>
- <trans-unit id="_msg378">
- <source xml:space="preserve">Options set in this dialog are overridden by the command line or in the configuration file:</source>
- <context-group purpose="location"><context context-type="linenumber">899</context></context-group>
- </trans-unit>
- <trans-unit id="_msg379">
+ <trans-unit id="_msg396">
<source xml:space="preserve">&amp;OK</source>
<context-group purpose="location"><context context-type="linenumber">1040</context></context-group>
</trans-unit>
- <trans-unit id="_msg380">
+ <trans-unit id="_msg397">
<source xml:space="preserve">&amp;Cancel</source>
<context-group purpose="location"><context context-type="linenumber">1053</context></context-group>
</trans-unit>
@@ -1697,140 +1793,156 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../optionsdialog.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="OptionsDialog">
- <trans-unit id="_msg381">
+ <trans-unit id="_msg398">
<source xml:space="preserve">Compiled without external signing support (required for external signing)</source>
- <context-group purpose="location"><context context-type="linenumber">99</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">96</context></context-group>
<note annotates="source" from="developer">&quot;External signing&quot; means using devices such as hardware wallets.</note>
</trans-unit>
- <trans-unit id="_msg382">
+ <trans-unit id="_msg399">
<source xml:space="preserve">default</source>
- <context-group purpose="location"><context context-type="linenumber">111</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">108</context></context-group>
</trans-unit>
- <trans-unit id="_msg383">
+ <trans-unit id="_msg400">
<source xml:space="preserve">none</source>
- <context-group purpose="location"><context context-type="linenumber">192</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">194</context></context-group>
</trans-unit>
- <trans-unit id="_msg384">
+ <trans-unit id="_msg401">
<source xml:space="preserve">Confirm options reset</source>
- <context-group purpose="location"><context context-type="linenumber">289</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">301</context></context-group>
+ <note annotates="source" from="developer">Window title text of pop-up window shown when the user has chosen to reset options.</note>
</trans-unit>
- <trans-unit id="_msg385">
+ <trans-unit id="_msg402">
<source xml:space="preserve">Client restart required to activate changes.</source>
- <context-group purpose="location"><context context-type="linenumber">290</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">360</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">292</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">371</context></context-group>
+ <note annotates="source" from="developer">Text explaining that the settings changed will not come into effect until the client is restarted.</note>
</trans-unit>
- <trans-unit id="_msg386">
+ <trans-unit id="_msg403">
+ <source xml:space="preserve">Current settings will be backed up at &quot;%1&quot;.</source>
+ <context-group purpose="location"><context context-type="linenumber">296</context></context-group>
+ <note annotates="source" from="developer">Text explaining to the user that the client&apos;s current settings will be backed up at a specific location. %1 is a stand-in argument for the backup location&apos;s path.</note>
+ </trans-unit>
+ <trans-unit id="_msg404">
<source xml:space="preserve">Client will be shut down. Do you want to proceed?</source>
- <context-group purpose="location"><context context-type="linenumber">290</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">299</context></context-group>
+ <note annotates="source" from="developer">Text asking the user to confirm if they would like to proceed with a client shutdown.</note>
</trans-unit>
- <trans-unit id="_msg387">
+ <trans-unit id="_msg405">
<source xml:space="preserve">Configuration options</source>
- <context-group purpose="location"><context context-type="linenumber">308</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">319</context></context-group>
<note annotates="source" from="developer">Window title text of pop-up box that allows opening up of configuration file.</note>
</trans-unit>
- <trans-unit id="_msg388">
+ <trans-unit id="_msg406">
<source xml:space="preserve">The configuration file is used to specify advanced user options which override GUI settings. Additionally, any command-line options will override this configuration file.</source>
- <context-group purpose="location"><context context-type="linenumber">311</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">322</context></context-group>
<note annotates="source" from="developer">Explanatory text about the priority order of instructions considered by client. The order from high to low being: command-line, configuration file, GUI settings.</note>
</trans-unit>
- <trans-unit id="_msg389">
+ <trans-unit id="_msg407">
<source xml:space="preserve">Continue</source>
- <context-group purpose="location"><context context-type="linenumber">314</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">325</context></context-group>
</trans-unit>
- <trans-unit id="_msg390">
+ <trans-unit id="_msg408">
<source xml:space="preserve">Cancel</source>
- <context-group purpose="location"><context context-type="linenumber">315</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">326</context></context-group>
</trans-unit>
- <trans-unit id="_msg391">
+ <trans-unit id="_msg409">
<source xml:space="preserve">Error</source>
- <context-group purpose="location"><context context-type="linenumber">324</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">335</context></context-group>
</trans-unit>
- <trans-unit id="_msg392">
+ <trans-unit id="_msg410">
<source xml:space="preserve">The configuration file could not be opened.</source>
- <context-group purpose="location"><context context-type="linenumber">324</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">335</context></context-group>
</trans-unit>
- <trans-unit id="_msg393">
+ <trans-unit id="_msg411">
<source xml:space="preserve">This change would require a client restart.</source>
- <context-group purpose="location"><context context-type="linenumber">364</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">375</context></context-group>
</trans-unit>
- <trans-unit id="_msg394">
+ <trans-unit id="_msg412">
<source xml:space="preserve">The supplied proxy address is invalid.</source>
- <context-group purpose="location"><context context-type="linenumber">392</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">403</context></context-group>
+ </trans-unit>
+ </group>
+ </body></file>
+ <file original="../optionsmodel.cpp" datatype="cpp" source-language="en"><body>
+ <group restype="x-trolltech-linguist-context" resname="OptionsModel">
+ <trans-unit id="_msg413">
+ <source xml:space="preserve">Could not read setting &quot;%1&quot;, %2.</source>
+ <context-group purpose="location"><context context-type="linenumber">204</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../forms/overviewpage.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="OverviewPage">
- <trans-unit id="_msg395">
+ <trans-unit id="_msg414">
<source xml:space="preserve">Form</source>
<context-group purpose="location"><context context-type="linenumber">14</context></context-group>
</trans-unit>
- <trans-unit id="_msg396">
+ <trans-unit id="_msg415">
<source xml:space="preserve">The displayed information may be out of date. Your wallet automatically synchronizes with the Bitcoin network after a connection is established, but this process has not completed yet.</source>
<context-group purpose="location"><context context-type="linenumber">76</context></context-group>
<context-group purpose="location"><context context-type="linenumber">411</context></context-group>
</trans-unit>
- <trans-unit id="_msg397">
+ <trans-unit id="_msg416">
<source xml:space="preserve">Watch-only:</source>
<context-group purpose="location"><context context-type="linenumber">284</context></context-group>
</trans-unit>
- <trans-unit id="_msg398">
+ <trans-unit id="_msg417">
<source xml:space="preserve">Available:</source>
<context-group purpose="location"><context context-type="linenumber">294</context></context-group>
</trans-unit>
- <trans-unit id="_msg399">
+ <trans-unit id="_msg418">
<source xml:space="preserve">Your current spendable balance</source>
<context-group purpose="location"><context context-type="linenumber">304</context></context-group>
</trans-unit>
- <trans-unit id="_msg400">
+ <trans-unit id="_msg419">
<source xml:space="preserve">Pending:</source>
<context-group purpose="location"><context context-type="linenumber">339</context></context-group>
</trans-unit>
- <trans-unit id="_msg401">
+ <trans-unit id="_msg420">
<source xml:space="preserve">Total of transactions that have yet to be confirmed, and do not yet count toward the spendable balance</source>
<context-group purpose="location"><context context-type="linenumber">139</context></context-group>
</trans-unit>
- <trans-unit id="_msg402">
+ <trans-unit id="_msg421">
<source xml:space="preserve">Immature:</source>
<context-group purpose="location"><context context-type="linenumber">239</context></context-group>
</trans-unit>
- <trans-unit id="_msg403">
+ <trans-unit id="_msg422">
<source xml:space="preserve">Mined balance that has not yet matured</source>
<context-group purpose="location"><context context-type="linenumber">210</context></context-group>
</trans-unit>
- <trans-unit id="_msg404">
+ <trans-unit id="_msg423">
<source xml:space="preserve">Balances</source>
<context-group purpose="location"><context context-type="linenumber">60</context></context-group>
</trans-unit>
- <trans-unit id="_msg405">
+ <trans-unit id="_msg424">
<source xml:space="preserve">Total:</source>
<context-group purpose="location"><context context-type="linenumber">200</context></context-group>
</trans-unit>
- <trans-unit id="_msg406">
+ <trans-unit id="_msg425">
<source xml:space="preserve">Your current total balance</source>
<context-group purpose="location"><context context-type="linenumber">249</context></context-group>
</trans-unit>
- <trans-unit id="_msg407">
+ <trans-unit id="_msg426">
<source xml:space="preserve">Your current balance in watch-only addresses</source>
<context-group purpose="location"><context context-type="linenumber">323</context></context-group>
</trans-unit>
- <trans-unit id="_msg408">
+ <trans-unit id="_msg427">
<source xml:space="preserve">Spendable:</source>
<context-group purpose="location"><context context-type="linenumber">346</context></context-group>
</trans-unit>
- <trans-unit id="_msg409">
+ <trans-unit id="_msg428">
<source xml:space="preserve">Recent transactions</source>
<context-group purpose="location"><context context-type="linenumber">395</context></context-group>
</trans-unit>
- <trans-unit id="_msg410">
+ <trans-unit id="_msg429">
<source xml:space="preserve">Unconfirmed transactions to watch-only addresses</source>
<context-group purpose="location"><context context-type="linenumber">120</context></context-group>
</trans-unit>
- <trans-unit id="_msg411">
+ <trans-unit id="_msg430">
<source xml:space="preserve">Mined balance in watch-only addresses that has not yet matured</source>
<context-group purpose="location"><context context-type="linenumber">158</context></context-group>
</trans-unit>
- <trans-unit id="_msg412">
+ <trans-unit id="_msg431">
<source xml:space="preserve">Current total balance in watch-only addresses</source>
<context-group purpose="location"><context context-type="linenumber">268</context></context-group>
</trans-unit>
@@ -1838,35 +1950,35 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../overviewpage.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="OverviewPage">
- <trans-unit id="_msg413">
+ <trans-unit id="_msg432">
<source xml:space="preserve">Privacy mode activated for the Overview tab. To unmask the values, uncheck Settings-&gt;Mask values.</source>
- <context-group purpose="location"><context context-type="linenumber">187</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">185</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../forms/psbtoperationsdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="PSBTOperationsDialog">
- <trans-unit id="_msg414">
+ <trans-unit id="_msg433">
<source xml:space="preserve">Dialog</source>
<context-group purpose="location"><context context-type="linenumber">14</context></context-group>
</trans-unit>
- <trans-unit id="_msg415">
+ <trans-unit id="_msg434">
<source xml:space="preserve">Sign Tx</source>
<context-group purpose="location"><context context-type="linenumber">86</context></context-group>
</trans-unit>
- <trans-unit id="_msg416">
+ <trans-unit id="_msg435">
<source xml:space="preserve">Broadcast Tx</source>
<context-group purpose="location"><context context-type="linenumber">102</context></context-group>
</trans-unit>
- <trans-unit id="_msg417">
+ <trans-unit id="_msg436">
<source xml:space="preserve">Copy to Clipboard</source>
<context-group purpose="location"><context context-type="linenumber">122</context></context-group>
</trans-unit>
- <trans-unit id="_msg418">
+ <trans-unit id="_msg437">
<source xml:space="preserve">Save…</source>
<context-group purpose="location"><context context-type="linenumber">129</context></context-group>
</trans-unit>
- <trans-unit id="_msg419">
+ <trans-unit id="_msg438">
<source xml:space="preserve">Close</source>
<context-group purpose="location"><context context-type="linenumber">136</context></context-group>
</trans-unit>
@@ -1874,108 +1986,108 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../psbtoperationsdialog.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="PSBTOperationsDialog">
- <trans-unit id="_msg420">
+ <trans-unit id="_msg439">
<source xml:space="preserve">Failed to load transaction: %1</source>
<context-group purpose="location"><context context-type="linenumber">61</context></context-group>
</trans-unit>
- <trans-unit id="_msg421">
+ <trans-unit id="_msg440">
<source xml:space="preserve">Failed to sign transaction: %1</source>
<context-group purpose="location"><context context-type="linenumber">86</context></context-group>
</trans-unit>
- <trans-unit id="_msg422">
+ <trans-unit id="_msg441">
<source xml:space="preserve">Cannot sign inputs while wallet is locked.</source>
<context-group purpose="location"><context context-type="linenumber">94</context></context-group>
</trans-unit>
- <trans-unit id="_msg423">
+ <trans-unit id="_msg442">
<source xml:space="preserve">Could not sign any more inputs.</source>
<context-group purpose="location"><context context-type="linenumber">96</context></context-group>
</trans-unit>
- <trans-unit id="_msg424">
+ <trans-unit id="_msg443">
<source xml:space="preserve">Signed %1 inputs, but more signatures are still required.</source>
<context-group purpose="location"><context context-type="linenumber">98</context></context-group>
</trans-unit>
- <trans-unit id="_msg425">
+ <trans-unit id="_msg444">
<source xml:space="preserve">Signed transaction successfully. Transaction is ready to broadcast.</source>
<context-group purpose="location"><context context-type="linenumber">101</context></context-group>
</trans-unit>
- <trans-unit id="_msg426">
+ <trans-unit id="_msg445">
<source xml:space="preserve">Unknown error processing transaction.</source>
<context-group purpose="location"><context context-type="linenumber">113</context></context-group>
</trans-unit>
- <trans-unit id="_msg427">
+ <trans-unit id="_msg446">
<source xml:space="preserve">Transaction broadcast successfully! Transaction ID: %1</source>
<context-group purpose="location"><context context-type="linenumber">123</context></context-group>
</trans-unit>
- <trans-unit id="_msg428">
+ <trans-unit id="_msg447">
<source xml:space="preserve">Transaction broadcast failed: %1</source>
<context-group purpose="location"><context context-type="linenumber">126</context></context-group>
</trans-unit>
- <trans-unit id="_msg429">
+ <trans-unit id="_msg448">
<source xml:space="preserve">PSBT copied to clipboard.</source>
<context-group purpose="location"><context context-type="linenumber">135</context></context-group>
</trans-unit>
- <trans-unit id="_msg430">
+ <trans-unit id="_msg449">
<source xml:space="preserve">Save Transaction Data</source>
<context-group purpose="location"><context context-type="linenumber">158</context></context-group>
</trans-unit>
- <trans-unit id="_msg431">
+ <trans-unit id="_msg450">
<source xml:space="preserve">Partially Signed Transaction (Binary)</source>
<context-group purpose="location"><context context-type="linenumber">160</context></context-group>
<note annotates="source" from="developer">Expanded name of the binary PSBT file format. See: BIP 174.</note>
</trans-unit>
- <trans-unit id="_msg432">
+ <trans-unit id="_msg451">
<source xml:space="preserve">PSBT saved to disk.</source>
<context-group purpose="location"><context context-type="linenumber">167</context></context-group>
</trans-unit>
- <trans-unit id="_msg433">
+ <trans-unit id="_msg452">
<source xml:space="preserve"> * Sends %1 to %2</source>
<context-group purpose="location"><context context-type="linenumber">183</context></context-group>
</trans-unit>
- <trans-unit id="_msg434">
+ <trans-unit id="_msg453">
<source xml:space="preserve">Unable to calculate transaction fee or total transaction amount.</source>
<context-group purpose="location"><context context-type="linenumber">193</context></context-group>
</trans-unit>
- <trans-unit id="_msg435">
+ <trans-unit id="_msg454">
<source xml:space="preserve">Pays transaction fee: </source>
<context-group purpose="location"><context context-type="linenumber">195</context></context-group>
</trans-unit>
- <trans-unit id="_msg436">
+ <trans-unit id="_msg455">
<source xml:space="preserve">Total Amount</source>
<context-group purpose="location"><context context-type="linenumber">207</context></context-group>
</trans-unit>
- <trans-unit id="_msg437">
+ <trans-unit id="_msg456">
<source xml:space="preserve">or</source>
<context-group purpose="location"><context context-type="linenumber">210</context></context-group>
</trans-unit>
- <trans-unit id="_msg438">
+ <trans-unit id="_msg457">
<source xml:space="preserve">Transaction has %1 unsigned inputs.</source>
<context-group purpose="location"><context context-type="linenumber">216</context></context-group>
</trans-unit>
- <trans-unit id="_msg439">
+ <trans-unit id="_msg458">
<source xml:space="preserve">Transaction is missing some information about inputs.</source>
<context-group purpose="location"><context context-type="linenumber">262</context></context-group>
</trans-unit>
- <trans-unit id="_msg440">
+ <trans-unit id="_msg459">
<source xml:space="preserve">Transaction still needs signature(s).</source>
<context-group purpose="location"><context context-type="linenumber">266</context></context-group>
</trans-unit>
- <trans-unit id="_msg441">
+ <trans-unit id="_msg460">
<source xml:space="preserve">(But no wallet is loaded.)</source>
<context-group purpose="location"><context context-type="linenumber">269</context></context-group>
</trans-unit>
- <trans-unit id="_msg442">
+ <trans-unit id="_msg461">
<source xml:space="preserve">(But this wallet cannot sign transactions.)</source>
<context-group purpose="location"><context context-type="linenumber">272</context></context-group>
</trans-unit>
- <trans-unit id="_msg443">
+ <trans-unit id="_msg462">
<source xml:space="preserve">(But this wallet does not have the right keys.)</source>
<context-group purpose="location"><context context-type="linenumber">275</context></context-group>
</trans-unit>
- <trans-unit id="_msg444">
+ <trans-unit id="_msg463">
<source xml:space="preserve">Transaction is fully signed and ready for broadcast.</source>
<context-group purpose="location"><context context-type="linenumber">283</context></context-group>
</trans-unit>
- <trans-unit id="_msg445">
+ <trans-unit id="_msg464">
<source xml:space="preserve">Transaction status is unknown.</source>
<context-group purpose="location"><context context-type="linenumber">287</context></context-group>
</trans-unit>
@@ -1983,295 +2095,308 @@ Signing is only possible with addresses of the type &apos;legacy&apos;.</source>
</body></file>
<file original="../paymentserver.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="PaymentServer">
- <trans-unit id="_msg446">
+ <trans-unit id="_msg465">
<source xml:space="preserve">Payment request error</source>
- <context-group purpose="location"><context context-type="linenumber">173</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">152</context></context-group>
</trans-unit>
- <trans-unit id="_msg447">
+ <trans-unit id="_msg466">
<source xml:space="preserve">Cannot start bitcoin: click-to-pay handler</source>
- <context-group purpose="location"><context context-type="linenumber">174</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">153</context></context-group>
</trans-unit>
- <trans-unit id="_msg448">
+ <trans-unit id="_msg467">
<source xml:space="preserve">URI handling</source>
- <context-group purpose="location"><context context-type="linenumber">224</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">240</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">246</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">253</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">201</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">217</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">223</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">230</context></context-group>
</trans-unit>
- <trans-unit id="_msg449">
+ <trans-unit id="_msg468">
<source xml:space="preserve">&apos;bitcoin://&apos; is not a valid URI. Use &apos;bitcoin:&apos; instead.</source>
- <context-group purpose="location"><context context-type="linenumber">224</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">201</context></context-group>
</trans-unit>
- <trans-unit id="_msg450">
+ <trans-unit id="_msg469">
<source xml:space="preserve">Cannot process payment request because BIP70 is not supported.
Due to widespread security flaws in BIP70 it&apos;s strongly recommended that any merchant instructions to switch wallets be ignored.
If you are receiving this error you should request the merchant provide a BIP21 compatible URI.</source>
+ <context-group purpose="location"><context context-type="linenumber">218</context></context-group>
<context-group purpose="location"><context context-type="linenumber">241</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">264</context></context-group>
</trans-unit>
- <trans-unit id="_msg451">
+ <trans-unit id="_msg470">
<source xml:space="preserve">URI cannot be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters.</source>
- <context-group purpose="location"><context context-type="linenumber">254</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">231</context></context-group>
</trans-unit>
- <trans-unit id="_msg452">
+ <trans-unit id="_msg471">
<source xml:space="preserve">Payment request file handling</source>
- <context-group purpose="location"><context context-type="linenumber">263</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">240</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../peertablemodel.h" datatype="c" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="PeerTableModel">
- <trans-unit id="_msg453">
+ <trans-unit id="_msg472">
<source xml:space="preserve">User Agent</source>
- <context-group purpose="location"><context context-type="linenumber">108</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">112</context></context-group>
<note annotates="source" from="developer">Title of Peers Table column which contains the peer&apos;s User Agent string.</note>
</trans-unit>
- <trans-unit id="_msg454">
+ <trans-unit id="_msg473">
<source xml:space="preserve">Ping</source>
- <context-group purpose="location"><context context-type="linenumber">99</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">103</context></context-group>
<note annotates="source" from="developer">Title of Peers Table column which indicates the current latency of the connection with the peer.</note>
</trans-unit>
- <trans-unit id="_msg455">
+ <trans-unit id="_msg474">
<source xml:space="preserve">Peer</source>
- <context-group purpose="location"><context context-type="linenumber">84</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">85</context></context-group>
<note annotates="source" from="developer">Title of Peers Table column which contains a unique number used to identify a connection.</note>
</trans-unit>
- <trans-unit id="_msg456">
+ <trans-unit id="_msg475">
+ <source xml:space="preserve">Age</source>
+ <context-group purpose="location"><context context-type="linenumber">88</context></context-group>
+ <note annotates="source" from="developer">Title of Peers Table column which indicates the duration (length of time) since the peer connection started.</note>
+ </trans-unit>
+ <trans-unit id="_msg476">
<source xml:space="preserve">Direction</source>
- <context-group purpose="location"><context context-type="linenumber">90</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">94</context></context-group>
<note annotates="source" from="developer">Title of Peers Table column which indicates the direction the peer connection was initiated from.</note>
</trans-unit>
- <trans-unit id="_msg457">
+ <trans-unit id="_msg477">
<source xml:space="preserve">Sent</source>
- <context-group purpose="location"><context context-type="linenumber">102</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">106</context></context-group>
<note annotates="source" from="developer">Title of Peers Table column which indicates the total amount of network information we have sent to the peer.</note>
</trans-unit>
- <trans-unit id="_msg458">
+ <trans-unit id="_msg478">
<source xml:space="preserve">Received</source>
- <context-group purpose="location"><context context-type="linenumber">105</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">109</context></context-group>
<note annotates="source" from="developer">Title of Peers Table column which indicates the total amount of network information we have received from the peer.</note>
</trans-unit>
- <trans-unit id="_msg459">
+ <trans-unit id="_msg479">
<source xml:space="preserve">Address</source>
- <context-group purpose="location"><context context-type="linenumber">87</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">91</context></context-group>
<note annotates="source" from="developer">Title of Peers Table column which contains the IP/Onion/I2P address of the connected peer.</note>
</trans-unit>
- <trans-unit id="_msg460">
+ <trans-unit id="_msg480">
<source xml:space="preserve">Type</source>
- <context-group purpose="location"><context context-type="linenumber">93</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">97</context></context-group>
<note annotates="source" from="developer">Title of Peers Table column which describes the type of peer connection. The &quot;type&quot; describes why the connection exists.</note>
</trans-unit>
- <trans-unit id="_msg461">
+ <trans-unit id="_msg481">
<source xml:space="preserve">Network</source>
- <context-group purpose="location"><context context-type="linenumber">96</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">100</context></context-group>
<note annotates="source" from="developer">Title of Peers Table column which states the network the peer connected through.</note>
</trans-unit>
</group>
</body></file>
<file original="../peertablemodel.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="PeerTableModel">
- <trans-unit id="_msg462">
+ <trans-unit id="_msg482">
<source xml:space="preserve">Inbound</source>
- <context-group purpose="location"><context context-type="linenumber">79</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">78</context></context-group>
<note annotates="source" from="developer">An Inbound Connection from a Peer.</note>
</trans-unit>
- <trans-unit id="_msg463">
+ <trans-unit id="_msg483">
<source xml:space="preserve">Outbound</source>
- <context-group purpose="location"><context context-type="linenumber">81</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">80</context></context-group>
<note annotates="source" from="developer">An Outbound Connection to a Peer.</note>
</trans-unit>
</group>
</body></file>
<file original="../bitcoinunits.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="QObject">
- <trans-unit id="_msg464">
+ <trans-unit id="_msg484">
<source xml:space="preserve">Amount</source>
- <context-group purpose="location"><context context-type="linenumber">215</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">197</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../guiutil.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="QObject">
- <trans-unit id="_msg465">
+ <trans-unit id="_msg485">
<source xml:space="preserve">Enter a Bitcoin address (e.g. %1)</source>
- <context-group purpose="location"><context context-type="linenumber">127</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">129</context></context-group>
</trans-unit>
- <trans-unit id="_msg466">
+ <trans-unit id="_msg486">
+ <source xml:space="preserve">Ctrl+W</source>
+ <context-group purpose="location"><context context-type="linenumber">417</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg487">
<source xml:space="preserve">Unroutable</source>
<context-group purpose="location"><context context-type="linenumber">673</context></context-group>
</trans-unit>
- <trans-unit id="_msg467">
+ <trans-unit id="_msg488">
<source xml:space="preserve">Internal</source>
<context-group purpose="location"><context context-type="linenumber">679</context></context-group>
</trans-unit>
- <trans-unit id="_msg468">
+ <trans-unit id="_msg489">
<source xml:space="preserve">Inbound</source>
<context-group purpose="location"><context context-type="linenumber">692</context></context-group>
<note annotates="source" from="developer">An inbound connection from a peer. An inbound connection is a connection initiated by a peer.</note>
</trans-unit>
- <trans-unit id="_msg469">
+ <trans-unit id="_msg490">
<source xml:space="preserve">Outbound</source>
<context-group purpose="location"><context context-type="linenumber">695</context></context-group>
<note annotates="source" from="developer">An outbound connection to a peer. An outbound connection is a connection initiated by us.</note>
</trans-unit>
- <trans-unit id="_msg470">
+ <trans-unit id="_msg491">
<source xml:space="preserve">Full Relay</source>
<context-group purpose="location"><context context-type="linenumber">700</context></context-group>
<note annotates="source" from="developer">Peer connection type that relays all network information.</note>
</trans-unit>
- <trans-unit id="_msg471">
+ <trans-unit id="_msg492">
<source xml:space="preserve">Block Relay</source>
<context-group purpose="location"><context context-type="linenumber">703</context></context-group>
<note annotates="source" from="developer">Peer connection type that relays network information about blocks and not transactions or addresses.</note>
</trans-unit>
- <trans-unit id="_msg472">
+ <trans-unit id="_msg493">
<source xml:space="preserve">Manual</source>
<context-group purpose="location"><context context-type="linenumber">705</context></context-group>
<note annotates="source" from="developer">Peer connection type established manually through one of several methods.</note>
</trans-unit>
- <trans-unit id="_msg473">
+ <trans-unit id="_msg494">
<source xml:space="preserve">Feeler</source>
<context-group purpose="location"><context context-type="linenumber">707</context></context-group>
<note annotates="source" from="developer">Short-lived peer connection type that tests the aliveness of known addresses.</note>
</trans-unit>
- <trans-unit id="_msg474">
+ <trans-unit id="_msg495">
<source xml:space="preserve">Address Fetch</source>
<context-group purpose="location"><context context-type="linenumber">709</context></context-group>
<note annotates="source" from="developer">Short-lived peer connection type that solicits known addresses from a peer.</note>
</trans-unit>
- <trans-unit id="_msg475">
+ <trans-unit id="_msg496">
<source xml:space="preserve">%1 d</source>
- <context-group purpose="location"><context context-type="linenumber">724</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">722</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">734</context></context-group>
</trans-unit>
- <trans-unit id="_msg476">
+ <trans-unit id="_msg497">
<source xml:space="preserve">%1 h</source>
- <context-group purpose="location"><context context-type="linenumber">726</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">723</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">735</context></context-group>
</trans-unit>
- <trans-unit id="_msg477">
+ <trans-unit id="_msg498">
<source xml:space="preserve">%1 m</source>
- <context-group purpose="location"><context context-type="linenumber">728</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">724</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">736</context></context-group>
</trans-unit>
- <trans-unit id="_msg478">
+ <trans-unit id="_msg499">
<source xml:space="preserve">%1 s</source>
- <context-group purpose="location"><context context-type="linenumber">730</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">758</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">726</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">737</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">763</context></context-group>
</trans-unit>
- <trans-unit id="_msg479">
+ <trans-unit id="_msg500">
<source xml:space="preserve">None</source>
- <context-group purpose="location"><context context-type="linenumber">746</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">751</context></context-group>
</trans-unit>
- <trans-unit id="_msg480">
+ <trans-unit id="_msg501">
<source xml:space="preserve">N/A</source>
- <context-group purpose="location"><context context-type="linenumber">752</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">757</context></context-group>
</trans-unit>
- <trans-unit id="_msg481">
+ <trans-unit id="_msg502">
<source xml:space="preserve">%1 ms</source>
- <context-group purpose="location"><context context-type="linenumber">753</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">758</context></context-group>
</trans-unit>
<group restype="x-gettext-plurals">
- <context-group purpose="location"><context context-type="linenumber">771</context></context-group>
- <trans-unit id="_msg482[0]">
+ <context-group purpose="location"><context context-type="linenumber">776</context></context-group>
+ <trans-unit id="_msg503[0]">
<source xml:space="preserve">%n second(s)</source>
</trans-unit>
- <trans-unit id="_msg482[1]">
+ <trans-unit id="_msg503[1]">
<source xml:space="preserve">%n second(s)</source>
</trans-unit>
</group>
<group restype="x-gettext-plurals">
- <context-group purpose="location"><context context-type="linenumber">775</context></context-group>
- <trans-unit id="_msg483[0]">
+ <context-group purpose="location"><context context-type="linenumber">780</context></context-group>
+ <trans-unit id="_msg504[0]">
<source xml:space="preserve">%n minute(s)</source>
</trans-unit>
- <trans-unit id="_msg483[1]">
+ <trans-unit id="_msg504[1]">
<source xml:space="preserve">%n minute(s)</source>
</trans-unit>
</group>
<group restype="x-gettext-plurals">
- <context-group purpose="location"><context context-type="linenumber">779</context></context-group>
- <trans-unit id="_msg484[0]">
+ <context-group purpose="location"><context context-type="linenumber">784</context></context-group>
+ <trans-unit id="_msg505[0]">
<source xml:space="preserve">%n hour(s)</source>
</trans-unit>
- <trans-unit id="_msg484[1]">
+ <trans-unit id="_msg505[1]">
<source xml:space="preserve">%n hour(s)</source>
</trans-unit>
</group>
<group restype="x-gettext-plurals">
- <context-group purpose="location"><context context-type="linenumber">783</context></context-group>
- <trans-unit id="_msg485[0]">
+ <context-group purpose="location"><context context-type="linenumber">788</context></context-group>
+ <trans-unit id="_msg506[0]">
<source xml:space="preserve">%n day(s)</source>
</trans-unit>
- <trans-unit id="_msg485[1]">
+ <trans-unit id="_msg506[1]">
<source xml:space="preserve">%n day(s)</source>
</trans-unit>
</group>
<group restype="x-gettext-plurals">
- <context-group purpose="location"><context context-type="linenumber">787</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">793</context></context-group>
- <trans-unit id="_msg486[0]">
+ <context-group purpose="location"><context context-type="linenumber">792</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">798</context></context-group>
+ <trans-unit id="_msg507[0]">
<source xml:space="preserve">%n week(s)</source>
</trans-unit>
- <trans-unit id="_msg486[1]">
+ <trans-unit id="_msg507[1]">
<source xml:space="preserve">%n week(s)</source>
</trans-unit>
</group>
- <trans-unit id="_msg487">
+ <trans-unit id="_msg508">
<source xml:space="preserve">%1 and %2</source>
- <context-group purpose="location"><context context-type="linenumber">793</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">798</context></context-group>
</trans-unit>
<group restype="x-gettext-plurals">
- <context-group purpose="location"><context context-type="linenumber">793</context></context-group>
- <trans-unit id="_msg488[0]">
+ <context-group purpose="location"><context context-type="linenumber">798</context></context-group>
+ <trans-unit id="_msg509[0]">
<source xml:space="preserve">%n year(s)</source>
</trans-unit>
- <trans-unit id="_msg488[1]">
+ <trans-unit id="_msg509[1]">
<source xml:space="preserve">%n year(s)</source>
</trans-unit>
</group>
- <trans-unit id="_msg489">
+ <trans-unit id="_msg510">
<source xml:space="preserve">%1 B</source>
- <context-group purpose="location"><context context-type="linenumber">801</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">806</context></context-group>
</trans-unit>
- <trans-unit id="_msg490">
+ <trans-unit id="_msg511">
<source xml:space="preserve">%1 kB</source>
- <context-group purpose="location"><context context-type="linenumber">803</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">808</context></context-group>
</trans-unit>
- <trans-unit id="_msg491">
+ <trans-unit id="_msg512">
<source xml:space="preserve">%1 MB</source>
- <context-group purpose="location"><context context-type="linenumber">805</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">810</context></context-group>
</trans-unit>
- <trans-unit id="_msg492">
+ <trans-unit id="_msg513">
<source xml:space="preserve">%1 GB</source>
- <context-group purpose="location"><context context-type="linenumber">807</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">812</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../qrimagewidget.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="QRImageWidget">
- <trans-unit id="_msg493">
+ <trans-unit id="_msg514">
<source xml:space="preserve">&amp;Save Image…</source>
<context-group purpose="location"><context context-type="linenumber">30</context></context-group>
</trans-unit>
- <trans-unit id="_msg494">
+ <trans-unit id="_msg515">
<source xml:space="preserve">&amp;Copy Image</source>
<context-group purpose="location"><context context-type="linenumber">31</context></context-group>
</trans-unit>
- <trans-unit id="_msg495">
+ <trans-unit id="_msg516">
<source xml:space="preserve">Resulting URI too long, try to reduce the text for label / message.</source>
<context-group purpose="location"><context context-type="linenumber">42</context></context-group>
</trans-unit>
- <trans-unit id="_msg496">
+ <trans-unit id="_msg517">
<source xml:space="preserve">Error encoding URI into QR Code.</source>
<context-group purpose="location"><context context-type="linenumber">49</context></context-group>
</trans-unit>
- <trans-unit id="_msg497">
+ <trans-unit id="_msg518">
<source xml:space="preserve">QR code support not available.</source>
<context-group purpose="location"><context context-type="linenumber">90</context></context-group>
</trans-unit>
- <trans-unit id="_msg498">
+ <trans-unit id="_msg519">
<source xml:space="preserve">Save QR Code</source>
<context-group purpose="location"><context context-type="linenumber">120</context></context-group>
</trans-unit>
- <trans-unit id="_msg499">
+ <trans-unit id="_msg520">
<source xml:space="preserve">PNG Image</source>
<context-group purpose="location"><context context-type="linenumber">123</context></context-group>
<note annotates="source" from="developer">Expanded name of the PNG file format. See: https://en.wikipedia.org/wiki/Portable_Network_Graphics.</note>
@@ -2280,7 +2405,7 @@ If you are receiving this error you should request the merchant provide a BIP21
</body></file>
<file original="../forms/debugwindow.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="RPCConsole">
- <trans-unit id="_msg500">
+ <trans-unit id="_msg521">
<source xml:space="preserve">N/A</source>
<context-group purpose="location"><context context-type="linenumber">75</context></context-group>
<context-group purpose="location"><context context-type="linenumber">101</context></context-group>
@@ -2319,290 +2444,293 @@ If you are receiving this error you should request the merchant provide a BIP21
<context-group purpose="location"><context context-type="linenumber">1610</context></context-group>
<context-group purpose="location"><context context-type="linenumber">1636</context></context-group>
<context-group purpose="location"><context context-type="linenumber">1662</context></context-group>
- <context-group purpose="location"><context context-type="sourcefile">../rpcconsole.h</context><context context-type="linenumber">139</context></context-group>
+ <context-group purpose="location"><context context-type="sourcefile">../rpcconsole.h</context><context context-type="linenumber">143</context></context-group>
</trans-unit>
- <trans-unit id="_msg501">
+ <trans-unit id="_msg522">
<source xml:space="preserve">Client version</source>
<context-group purpose="location"><context context-type="linenumber">65</context></context-group>
</trans-unit>
- <trans-unit id="_msg502">
+ <trans-unit id="_msg523">
<source xml:space="preserve">&amp;Information</source>
<context-group purpose="location"><context context-type="linenumber">43</context></context-group>
</trans-unit>
- <trans-unit id="_msg503">
+ <trans-unit id="_msg524">
<source xml:space="preserve">General</source>
<context-group purpose="location"><context context-type="linenumber">58</context></context-group>
</trans-unit>
- <trans-unit id="_msg504">
+ <trans-unit id="_msg525">
<source xml:space="preserve">Datadir</source>
<context-group purpose="location"><context context-type="linenumber">114</context></context-group>
</trans-unit>
- <trans-unit id="_msg505">
+ <trans-unit id="_msg526">
<source xml:space="preserve">To specify a non-default location of the data directory use the &apos;%1&apos; option.</source>
<context-group purpose="location"><context context-type="linenumber">124</context></context-group>
</trans-unit>
- <trans-unit id="_msg506">
+ <trans-unit id="_msg527">
<source xml:space="preserve">Blocksdir</source>
<context-group purpose="location"><context context-type="linenumber">143</context></context-group>
</trans-unit>
- <trans-unit id="_msg507">
+ <trans-unit id="_msg528">
<source xml:space="preserve">To specify a non-default location of the blocks directory use the &apos;%1&apos; option.</source>
<context-group purpose="location"><context context-type="linenumber">153</context></context-group>
</trans-unit>
- <trans-unit id="_msg508">
+ <trans-unit id="_msg529">
<source xml:space="preserve">Startup time</source>
<context-group purpose="location"><context context-type="linenumber">172</context></context-group>
</trans-unit>
- <trans-unit id="_msg509">
+ <trans-unit id="_msg530">
<source xml:space="preserve">Network</source>
<context-group purpose="location"><context context-type="linenumber">201</context></context-group>
<context-group purpose="location"><context context-type="linenumber">1093</context></context-group>
</trans-unit>
- <trans-unit id="_msg510">
+ <trans-unit id="_msg531">
<source xml:space="preserve">Name</source>
<context-group purpose="location"><context context-type="linenumber">208</context></context-group>
</trans-unit>
- <trans-unit id="_msg511">
+ <trans-unit id="_msg532">
<source xml:space="preserve">Number of connections</source>
<context-group purpose="location"><context context-type="linenumber">231</context></context-group>
</trans-unit>
- <trans-unit id="_msg512">
+ <trans-unit id="_msg533">
<source xml:space="preserve">Block chain</source>
<context-group purpose="location"><context context-type="linenumber">260</context></context-group>
</trans-unit>
- <trans-unit id="_msg513">
+ <trans-unit id="_msg534">
<source xml:space="preserve">Memory Pool</source>
<context-group purpose="location"><context context-type="linenumber">319</context></context-group>
</trans-unit>
- <trans-unit id="_msg514">
+ <trans-unit id="_msg535">
<source xml:space="preserve">Current number of transactions</source>
<context-group purpose="location"><context context-type="linenumber">326</context></context-group>
</trans-unit>
- <trans-unit id="_msg515">
+ <trans-unit id="_msg536">
<source xml:space="preserve">Memory usage</source>
<context-group purpose="location"><context context-type="linenumber">349</context></context-group>
</trans-unit>
- <trans-unit id="_msg516">
+ <trans-unit id="_msg537">
<source xml:space="preserve">Wallet: </source>
<context-group purpose="location"><context context-type="linenumber">443</context></context-group>
</trans-unit>
- <trans-unit id="_msg517">
+ <trans-unit id="_msg538">
<source xml:space="preserve">(none)</source>
<context-group purpose="location"><context context-type="linenumber">454</context></context-group>
</trans-unit>
- <trans-unit id="_msg518">
+ <trans-unit id="_msg539">
<source xml:space="preserve">&amp;Reset</source>
<context-group purpose="location"><context context-type="linenumber">665</context></context-group>
</trans-unit>
- <trans-unit id="_msg519">
+ <trans-unit id="_msg540">
<source xml:space="preserve">Received</source>
<context-group purpose="location"><context context-type="linenumber">745</context></context-group>
<context-group purpose="location"><context context-type="linenumber">1453</context></context-group>
</trans-unit>
- <trans-unit id="_msg520">
+ <trans-unit id="_msg541">
<source xml:space="preserve">Sent</source>
<context-group purpose="location"><context context-type="linenumber">825</context></context-group>
<context-group purpose="location"><context context-type="linenumber">1430</context></context-group>
</trans-unit>
- <trans-unit id="_msg521">
+ <trans-unit id="_msg542">
<source xml:space="preserve">&amp;Peers</source>
<context-group purpose="location"><context context-type="linenumber">866</context></context-group>
</trans-unit>
- <trans-unit id="_msg522">
+ <trans-unit id="_msg543">
<source xml:space="preserve">Banned peers</source>
<context-group purpose="location"><context context-type="linenumber">942</context></context-group>
</trans-unit>
- <trans-unit id="_msg523">
+ <trans-unit id="_msg544">
<source xml:space="preserve">Select a peer to view detailed information.</source>
<context-group purpose="location"><context context-type="linenumber">1010</context></context-group>
- <context-group purpose="location"><context context-type="sourcefile">../rpcconsole.cpp</context><context context-type="linenumber">1160</context></context-group>
+ <context-group purpose="location"><context context-type="sourcefile">../rpcconsole.cpp</context><context context-type="linenumber">1155</context></context-group>
</trans-unit>
- <trans-unit id="_msg524">
+ <trans-unit id="_msg545">
<source xml:space="preserve">Version</source>
<context-group purpose="location"><context context-type="linenumber">1116</context></context-group>
</trans-unit>
- <trans-unit id="_msg525">
+ <trans-unit id="_msg546">
<source xml:space="preserve">Starting Block</source>
<context-group purpose="location"><context context-type="linenumber">1240</context></context-group>
</trans-unit>
- <trans-unit id="_msg526">
+ <trans-unit id="_msg547">
<source xml:space="preserve">Synced Headers</source>
<context-group purpose="location"><context context-type="linenumber">1263</context></context-group>
</trans-unit>
- <trans-unit id="_msg527">
+ <trans-unit id="_msg548">
<source xml:space="preserve">Synced Blocks</source>
<context-group purpose="location"><context context-type="linenumber">1286</context></context-group>
</trans-unit>
- <trans-unit id="_msg528">
+ <trans-unit id="_msg549">
<source xml:space="preserve">Last Transaction</source>
<context-group purpose="location"><context context-type="linenumber">1361</context></context-group>
</trans-unit>
- <trans-unit id="_msg529">
+ <trans-unit id="_msg550">
<source xml:space="preserve">The mapped Autonomous System used for diversifying peer selection.</source>
<context-group purpose="location"><context context-type="linenumber">1571</context></context-group>
</trans-unit>
- <trans-unit id="_msg530">
+ <trans-unit id="_msg551">
<source xml:space="preserve">Mapped AS</source>
<context-group purpose="location"><context context-type="linenumber">1574</context></context-group>
</trans-unit>
- <trans-unit id="_msg531">
+ <trans-unit id="_msg552">
<source xml:space="preserve">Whether we relay addresses to this peer.</source>
<context-group purpose="location"><context context-type="linenumber">1597</context></context-group>
- <note annotates="source" from="developer">Tooltip text for the Address Relay field in the peer details area.</note>
+ <note annotates="source" from="developer">Tooltip text for the Address Relay field in the peer details area, which displays whether we relay addresses to this peer (Yes/No).</note>
</trans-unit>
- <trans-unit id="_msg532">
+ <trans-unit id="_msg553">
<source xml:space="preserve">Address Relay</source>
<context-group purpose="location"><context context-type="linenumber">1600</context></context-group>
+ <note annotates="source" from="developer">Text title for the Address Relay field in the peer details area, which displays whether we relay addresses to this peer (Yes/No).</note>
</trans-unit>
- <trans-unit id="_msg533">
- <source xml:space="preserve">Total number of addresses processed, excluding those dropped due to rate-limiting.</source>
+ <trans-unit id="_msg554">
+ <source xml:space="preserve">The total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</source>
<context-group purpose="location"><context context-type="linenumber">1623</context></context-group>
- <note annotates="source" from="developer">Tooltip text for the Addresses Processed field in the peer details area.</note>
+ <note annotates="source" from="developer">Tooltip text for the Addresses Processed field in the peer details area, which displays the total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</note>
</trans-unit>
- <trans-unit id="_msg534">
+ <trans-unit id="_msg555">
+ <source xml:space="preserve">The total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</source>
+ <context-group purpose="location"><context context-type="linenumber">1649</context></context-group>
+ <note annotates="source" from="developer">Tooltip text for the Addresses Rate-Limited field in the peer details area, which displays the total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</note>
+ </trans-unit>
+ <trans-unit id="_msg556">
<source xml:space="preserve">Addresses Processed</source>
<context-group purpose="location"><context context-type="linenumber">1626</context></context-group>
+ <note annotates="source" from="developer">Text title for the Addresses Processed field in the peer details area, which displays the total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</note>
</trans-unit>
- <trans-unit id="_msg535">
- <source xml:space="preserve">Total number of addresses dropped due to rate-limiting.</source>
- <context-group purpose="location"><context context-type="linenumber">1649</context></context-group>
- <note annotates="source" from="developer">Tooltip text for the Addresses Rate-Limited field in the peer details area.</note>
- </trans-unit>
- <trans-unit id="_msg536">
+ <trans-unit id="_msg557">
<source xml:space="preserve">Addresses Rate-Limited</source>
<context-group purpose="location"><context context-type="linenumber">1652</context></context-group>
+ <note annotates="source" from="developer">Text title for the Addresses Rate-Limited field in the peer details area, which displays the total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</note>
</trans-unit>
- <trans-unit id="_msg537">
+ <trans-unit id="_msg558">
<source xml:space="preserve">User Agent</source>
<context-group purpose="location"><context context-type="linenumber">88</context></context-group>
<context-group purpose="location"><context context-type="linenumber">1139</context></context-group>
</trans-unit>
- <trans-unit id="_msg538">
+ <trans-unit id="_msg559">
<source xml:space="preserve">Node window</source>
<context-group purpose="location"><context context-type="linenumber">14</context></context-group>
</trans-unit>
- <trans-unit id="_msg539">
+ <trans-unit id="_msg560">
<source xml:space="preserve">Current block height</source>
<context-group purpose="location"><context context-type="linenumber">267</context></context-group>
</trans-unit>
- <trans-unit id="_msg540">
+ <trans-unit id="_msg561">
<source xml:space="preserve">Open the %1 debug log file from the current data directory. This can take a few seconds for large log files.</source>
<context-group purpose="location"><context context-type="linenumber">397</context></context-group>
</trans-unit>
- <trans-unit id="_msg541">
+ <trans-unit id="_msg562">
<source xml:space="preserve">Decrease font size</source>
<context-group purpose="location"><context context-type="linenumber">475</context></context-group>
</trans-unit>
- <trans-unit id="_msg542">
+ <trans-unit id="_msg563">
<source xml:space="preserve">Increase font size</source>
<context-group purpose="location"><context context-type="linenumber">495</context></context-group>
</trans-unit>
- <trans-unit id="_msg543">
+ <trans-unit id="_msg564">
<source xml:space="preserve">Permissions</source>
<context-group purpose="location"><context context-type="linenumber">1041</context></context-group>
</trans-unit>
- <trans-unit id="_msg544">
+ <trans-unit id="_msg565">
<source xml:space="preserve">The direction and type of peer connection: %1</source>
<context-group purpose="location"><context context-type="linenumber">1064</context></context-group>
</trans-unit>
- <trans-unit id="_msg545">
+ <trans-unit id="_msg566">
<source xml:space="preserve">Direction/Type</source>
<context-group purpose="location"><context context-type="linenumber">1067</context></context-group>
</trans-unit>
- <trans-unit id="_msg546">
+ <trans-unit id="_msg567">
<source xml:space="preserve">The network protocol this peer is connected through: IPv4, IPv6, Onion, I2P, or CJDNS.</source>
<context-group purpose="location"><context context-type="linenumber">1090</context></context-group>
</trans-unit>
- <trans-unit id="_msg547">
+ <trans-unit id="_msg568">
<source xml:space="preserve">Services</source>
<context-group purpose="location"><context context-type="linenumber">1162</context></context-group>
</trans-unit>
- <trans-unit id="_msg548">
+ <trans-unit id="_msg569">
<source xml:space="preserve">Whether the peer requested us to relay transactions.</source>
<context-group purpose="location"><context context-type="linenumber">1188</context></context-group>
</trans-unit>
- <trans-unit id="_msg549">
+ <trans-unit id="_msg570">
<source xml:space="preserve">Wants Tx Relay</source>
<context-group purpose="location"><context context-type="linenumber">1191</context></context-group>
</trans-unit>
- <trans-unit id="_msg550">
+ <trans-unit id="_msg571">
<source xml:space="preserve">High bandwidth BIP152 compact block relay: %1</source>
<context-group purpose="location"><context context-type="linenumber">1214</context></context-group>
</trans-unit>
- <trans-unit id="_msg551">
+ <trans-unit id="_msg572">
<source xml:space="preserve">High Bandwidth</source>
<context-group purpose="location"><context context-type="linenumber">1217</context></context-group>
</trans-unit>
- <trans-unit id="_msg552">
+ <trans-unit id="_msg573">
<source xml:space="preserve">Connection Time</source>
<context-group purpose="location"><context context-type="linenumber">1309</context></context-group>
</trans-unit>
- <trans-unit id="_msg553">
+ <trans-unit id="_msg574">
<source xml:space="preserve">Elapsed time since a novel block passing initial validity checks was received from this peer.</source>
<context-group purpose="location"><context context-type="linenumber">1332</context></context-group>
</trans-unit>
- <trans-unit id="_msg554">
+ <trans-unit id="_msg575">
<source xml:space="preserve">Last Block</source>
<context-group purpose="location"><context context-type="linenumber">1335</context></context-group>
</trans-unit>
- <trans-unit id="_msg555">
+ <trans-unit id="_msg576">
<source xml:space="preserve">Elapsed time since a novel transaction accepted into our mempool was received from this peer.</source>
<context-group purpose="location"><context context-type="linenumber">1358</context></context-group>
<note annotates="source" from="developer">Tooltip text for the Last Transaction field in the peer details area.</note>
</trans-unit>
- <trans-unit id="_msg556">
+ <trans-unit id="_msg577">
<source xml:space="preserve">Last Send</source>
<context-group purpose="location"><context context-type="linenumber">1384</context></context-group>
</trans-unit>
- <trans-unit id="_msg557">
+ <trans-unit id="_msg578">
<source xml:space="preserve">Last Receive</source>
<context-group purpose="location"><context context-type="linenumber">1407</context></context-group>
</trans-unit>
- <trans-unit id="_msg558">
+ <trans-unit id="_msg579">
<source xml:space="preserve">Ping Time</source>
<context-group purpose="location"><context context-type="linenumber">1476</context></context-group>
</trans-unit>
- <trans-unit id="_msg559">
+ <trans-unit id="_msg580">
<source xml:space="preserve">The duration of a currently outstanding ping.</source>
<context-group purpose="location"><context context-type="linenumber">1499</context></context-group>
</trans-unit>
- <trans-unit id="_msg560">
+ <trans-unit id="_msg581">
<source xml:space="preserve">Ping Wait</source>
<context-group purpose="location"><context context-type="linenumber">1502</context></context-group>
</trans-unit>
- <trans-unit id="_msg561">
+ <trans-unit id="_msg582">
<source xml:space="preserve">Min Ping</source>
<context-group purpose="location"><context context-type="linenumber">1525</context></context-group>
</trans-unit>
- <trans-unit id="_msg562">
+ <trans-unit id="_msg583">
<source xml:space="preserve">Time Offset</source>
<context-group purpose="location"><context context-type="linenumber">1548</context></context-group>
</trans-unit>
- <trans-unit id="_msg563">
+ <trans-unit id="_msg584">
<source xml:space="preserve">Last block time</source>
<context-group purpose="location"><context context-type="linenumber">290</context></context-group>
</trans-unit>
- <trans-unit id="_msg564">
+ <trans-unit id="_msg585">
<source xml:space="preserve">&amp;Open</source>
<context-group purpose="location"><context context-type="linenumber">400</context></context-group>
</trans-unit>
- <trans-unit id="_msg565">
+ <trans-unit id="_msg586">
<source xml:space="preserve">&amp;Console</source>
<context-group purpose="location"><context context-type="linenumber">426</context></context-group>
</trans-unit>
- <trans-unit id="_msg566">
+ <trans-unit id="_msg587">
<source xml:space="preserve">&amp;Network Traffic</source>
<context-group purpose="location"><context context-type="linenumber">613</context></context-group>
</trans-unit>
- <trans-unit id="_msg567">
+ <trans-unit id="_msg588">
<source xml:space="preserve">Totals</source>
<context-group purpose="location"><context context-type="linenumber">681</context></context-group>
</trans-unit>
- <trans-unit id="_msg568">
+ <trans-unit id="_msg589">
<source xml:space="preserve">Debug log file</source>
<context-group purpose="location"><context context-type="linenumber">390</context></context-group>
</trans-unit>
- <trans-unit id="_msg569">
+ <trans-unit id="_msg590">
<source xml:space="preserve">Clear console</source>
<context-group purpose="location"><context context-type="linenumber">515</context></context-group>
</trans-unit>
@@ -2610,123 +2738,139 @@ If you are receiving this error you should request the merchant provide a BIP21
</body></file>
<file original="../rpcconsole.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="RPCConsole">
- <trans-unit id="_msg570">
+ <trans-unit id="_msg591">
<source xml:space="preserve">In:</source>
- <context-group purpose="location"><context context-type="linenumber">959</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">953</context></context-group>
</trans-unit>
- <trans-unit id="_msg571">
+ <trans-unit id="_msg592">
<source xml:space="preserve">Out:</source>
- <context-group purpose="location"><context context-type="linenumber">960</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">954</context></context-group>
</trans-unit>
- <trans-unit id="_msg572">
+ <trans-unit id="_msg593">
<source xml:space="preserve">Inbound: initiated by peer</source>
- <context-group purpose="location"><context context-type="linenumber">503</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">496</context></context-group>
<note annotates="source" from="developer">Explanatory text for an inbound peer connection.</note>
</trans-unit>
- <trans-unit id="_msg573">
+ <trans-unit id="_msg594">
<source xml:space="preserve">Outbound Full Relay: default</source>
- <context-group purpose="location"><context context-type="linenumber">507</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">500</context></context-group>
<note annotates="source" from="developer">Explanatory text for an outbound peer connection that relays all network information. This is the default behavior for outbound connections.</note>
</trans-unit>
- <trans-unit id="_msg574">
+ <trans-unit id="_msg595">
<source xml:space="preserve">Outbound Block Relay: does not relay transactions or addresses</source>
- <context-group purpose="location"><context context-type="linenumber">510</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">503</context></context-group>
<note annotates="source" from="developer">Explanatory text for an outbound peer connection that relays network information about blocks and not transactions or addresses.</note>
</trans-unit>
- <trans-unit id="_msg575">
+ <trans-unit id="_msg596">
<source xml:space="preserve">Outbound Manual: added using RPC %1 or %2/%3 configuration options</source>
- <context-group purpose="location"><context context-type="linenumber">515</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">508</context></context-group>
<note annotates="source" from="developer">Explanatory text for an outbound peer connection that was established manually through one of several methods. The numbered arguments are stand-ins for the methods available to establish manual connections.</note>
</trans-unit>
- <trans-unit id="_msg576">
+ <trans-unit id="_msg597">
<source xml:space="preserve">Outbound Feeler: short-lived, for testing addresses</source>
- <context-group purpose="location"><context context-type="linenumber">521</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">514</context></context-group>
<note annotates="source" from="developer">Explanatory text for a short-lived outbound peer connection that is used to test the aliveness of known addresses.</note>
</trans-unit>
- <trans-unit id="_msg577">
+ <trans-unit id="_msg598">
<source xml:space="preserve">Outbound Address Fetch: short-lived, for soliciting addresses</source>
- <context-group purpose="location"><context context-type="linenumber">524</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">517</context></context-group>
<note annotates="source" from="developer">Explanatory text for a short-lived outbound peer connection that is used to request addresses from a peer.</note>
</trans-unit>
- <trans-unit id="_msg578">
+ <trans-unit id="_msg599">
<source xml:space="preserve">we selected the peer for high bandwidth relay</source>
- <context-group purpose="location"><context context-type="linenumber">528</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">521</context></context-group>
</trans-unit>
- <trans-unit id="_msg579">
+ <trans-unit id="_msg600">
<source xml:space="preserve">the peer selected us for high bandwidth relay</source>
- <context-group purpose="location"><context context-type="linenumber">529</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">522</context></context-group>
</trans-unit>
- <trans-unit id="_msg580">
+ <trans-unit id="_msg601">
<source xml:space="preserve">no high bandwidth relay selected</source>
- <context-group purpose="location"><context context-type="linenumber">530</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">523</context></context-group>
</trans-unit>
- <trans-unit id="_msg581">
+ <trans-unit id="_msg602">
<source xml:space="preserve">Ctrl++</source>
- <context-group purpose="location"><context context-type="linenumber">543</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">536</context></context-group>
<note annotates="source" from="developer">Main shortcut to increase the RPC console font size.</note>
</trans-unit>
- <trans-unit id="_msg582">
+ <trans-unit id="_msg603">
<source xml:space="preserve">Ctrl+=</source>
- <context-group purpose="location"><context context-type="linenumber">545</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">538</context></context-group>
<note annotates="source" from="developer">Secondary shortcut to increase the RPC console font size.</note>
</trans-unit>
- <trans-unit id="_msg583">
+ <trans-unit id="_msg604">
<source xml:space="preserve">Ctrl+-</source>
- <context-group purpose="location"><context context-type="linenumber">549</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">542</context></context-group>
<note annotates="source" from="developer">Main shortcut to decrease the RPC console font size.</note>
</trans-unit>
- <trans-unit id="_msg584">
+ <trans-unit id="_msg605">
<source xml:space="preserve">Ctrl+_</source>
- <context-group purpose="location"><context context-type="linenumber">551</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">544</context></context-group>
<note annotates="source" from="developer">Secondary shortcut to decrease the RPC console font size.</note>
</trans-unit>
- <trans-unit id="_msg585">
+ <trans-unit id="_msg606">
<source xml:space="preserve">&amp;Copy address</source>
- <context-group purpose="location"><context context-type="linenumber">702</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">695</context></context-group>
<note annotates="source" from="developer">Context menu action to copy the address of a peer.</note>
</trans-unit>
- <trans-unit id="_msg586">
+ <trans-unit id="_msg607">
<source xml:space="preserve">&amp;Disconnect</source>
- <context-group purpose="location"><context context-type="linenumber">706</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">699</context></context-group>
</trans-unit>
- <trans-unit id="_msg587">
+ <trans-unit id="_msg608">
<source xml:space="preserve">1 &amp;hour</source>
- <context-group purpose="location"><context context-type="linenumber">707</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">700</context></context-group>
</trans-unit>
- <trans-unit id="_msg588">
+ <trans-unit id="_msg609">
<source xml:space="preserve">1 d&amp;ay</source>
- <context-group purpose="location"><context context-type="linenumber">708</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">701</context></context-group>
</trans-unit>
- <trans-unit id="_msg589">
+ <trans-unit id="_msg610">
<source xml:space="preserve">1 &amp;week</source>
- <context-group purpose="location"><context context-type="linenumber">709</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">702</context></context-group>
</trans-unit>
- <trans-unit id="_msg590">
+ <trans-unit id="_msg611">
<source xml:space="preserve">1 &amp;year</source>
- <context-group purpose="location"><context context-type="linenumber">710</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">703</context></context-group>
</trans-unit>
- <trans-unit id="_msg591">
+ <trans-unit id="_msg612">
<source xml:space="preserve">&amp;Copy IP/Netmask</source>
- <context-group purpose="location"><context context-type="linenumber">735</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">729</context></context-group>
<note annotates="source" from="developer">Context menu action to copy the IP/Netmask of a banned peer. IP/Netmask is the combination of a peer&apos;s IP address and its Netmask. For IP address, see: https://en.wikipedia.org/wiki/IP_address.</note>
</trans-unit>
- <trans-unit id="_msg592">
+ <trans-unit id="_msg613">
<source xml:space="preserve">&amp;Unban</source>
- <context-group purpose="location"><context context-type="linenumber">739</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">733</context></context-group>
</trans-unit>
- <trans-unit id="_msg593">
+ <trans-unit id="_msg614">
<source xml:space="preserve">Network activity disabled</source>
- <context-group purpose="location"><context context-type="linenumber">963</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">957</context></context-group>
</trans-unit>
- <trans-unit id="_msg594">
+ <trans-unit id="_msg615">
<source xml:space="preserve">Executing command without any wallet</source>
- <context-group purpose="location"><context context-type="linenumber">1040</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1035</context></context-group>
</trans-unit>
- <trans-unit id="_msg595">
+ <trans-unit id="_msg616">
+ <source xml:space="preserve">Ctrl+I</source>
+ <context-group purpose="location"><context context-type="linenumber">1351</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg617">
+ <source xml:space="preserve">Ctrl+T</source>
+ <context-group purpose="location"><context context-type="linenumber">1352</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg618">
+ <source xml:space="preserve">Ctrl+N</source>
+ <context-group purpose="location"><context context-type="linenumber">1353</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg619">
+ <source xml:space="preserve">Ctrl+P</source>
+ <context-group purpose="location"><context context-type="linenumber">1354</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg620">
<source xml:space="preserve">Executing command using &quot;%1&quot; wallet</source>
- <context-group purpose="location"><context context-type="linenumber">1038</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1033</context></context-group>
</trans-unit>
- <trans-unit id="_msg596">
+ <trans-unit id="_msg621">
<source xml:space="preserve">Welcome to the %1 RPC console.
Use up and down arrows to navigate history, and %2 to clear screen.
Use %3 and %4 to increase or decrease the font size.
@@ -2734,124 +2878,124 @@ Type %5 for an overview of available commands.
For more information on using this console, type %6.
%7WARNING: Scammers have been active, telling users to type commands here, stealing their wallet contents. Do not use this console without fully understanding the ramifications of a command.%8</source>
- <context-group purpose="location"><context context-type="linenumber">893</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">887</context></context-group>
<note annotates="source" from="developer">RPC console welcome message. Placeholders %7 and %8 are style tags for the warning content, and they are not space separated from the rest of the text intentionally.</note>
</trans-unit>
- <trans-unit id="_msg597">
+ <trans-unit id="_msg622">
<source xml:space="preserve">Executing…</source>
- <context-group purpose="location"><context context-type="linenumber">1048</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1043</context></context-group>
<note annotates="source" from="developer">A console message indicating an entered command is currently being executed.</note>
</trans-unit>
- <trans-unit id="_msg598">
+ <trans-unit id="_msg623">
<source xml:space="preserve">(peer: %1)</source>
- <context-group purpose="location"><context context-type="linenumber">1166</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1161</context></context-group>
</trans-unit>
- <trans-unit id="_msg599">
+ <trans-unit id="_msg624">
<source xml:space="preserve">via %1</source>
- <context-group purpose="location"><context context-type="linenumber">1168</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1163</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../rpcconsole.h" datatype="c" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="RPCConsole">
- <trans-unit id="_msg600">
+ <trans-unit id="_msg625">
<source xml:space="preserve">Yes</source>
- <context-group purpose="location"><context context-type="linenumber">138</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">142</context></context-group>
</trans-unit>
- <trans-unit id="_msg601">
+ <trans-unit id="_msg626">
<source xml:space="preserve">No</source>
- <context-group purpose="location"><context context-type="linenumber">138</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">142</context></context-group>
</trans-unit>
- <trans-unit id="_msg602">
+ <trans-unit id="_msg627">
<source xml:space="preserve">To</source>
- <context-group purpose="location"><context context-type="linenumber">138</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">142</context></context-group>
</trans-unit>
- <trans-unit id="_msg603">
+ <trans-unit id="_msg628">
<source xml:space="preserve">From</source>
- <context-group purpose="location"><context context-type="linenumber">138</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">142</context></context-group>
</trans-unit>
- <trans-unit id="_msg604">
+ <trans-unit id="_msg629">
<source xml:space="preserve">Ban for</source>
- <context-group purpose="location"><context context-type="linenumber">139</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">143</context></context-group>
</trans-unit>
- <trans-unit id="_msg605">
+ <trans-unit id="_msg630">
<source xml:space="preserve">Never</source>
- <context-group purpose="location"><context context-type="linenumber">180</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">185</context></context-group>
</trans-unit>
- <trans-unit id="_msg606">
+ <trans-unit id="_msg631">
<source xml:space="preserve">Unknown</source>
- <context-group purpose="location"><context context-type="linenumber">139</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">143</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../forms/receivecoinsdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="ReceiveCoinsDialog">
- <trans-unit id="_msg607">
+ <trans-unit id="_msg632">
<source xml:space="preserve">&amp;Amount:</source>
<context-group purpose="location"><context context-type="linenumber">37</context></context-group>
</trans-unit>
- <trans-unit id="_msg608">
+ <trans-unit id="_msg633">
<source xml:space="preserve">&amp;Label:</source>
<context-group purpose="location"><context context-type="linenumber">83</context></context-group>
</trans-unit>
- <trans-unit id="_msg609">
+ <trans-unit id="_msg634">
<source xml:space="preserve">&amp;Message:</source>
<context-group purpose="location"><context context-type="linenumber">53</context></context-group>
</trans-unit>
- <trans-unit id="_msg610">
+ <trans-unit id="_msg635">
<source xml:space="preserve">An optional message to attach to the payment request, which will be displayed when the request is opened. Note: The message will not be sent with the payment over the Bitcoin network.</source>
<context-group purpose="location"><context context-type="linenumber">50</context></context-group>
</trans-unit>
- <trans-unit id="_msg611">
+ <trans-unit id="_msg636">
<source xml:space="preserve">An optional label to associate with the new receiving address.</source>
<context-group purpose="location"><context context-type="linenumber">80</context></context-group>
</trans-unit>
- <trans-unit id="_msg612">
+ <trans-unit id="_msg637">
<source xml:space="preserve">Use this form to request payments. All fields are &lt;b&gt;optional&lt;/b&gt;.</source>
<context-group purpose="location"><context context-type="linenumber">73</context></context-group>
</trans-unit>
- <trans-unit id="_msg613">
+ <trans-unit id="_msg638">
<source xml:space="preserve">An optional amount to request. Leave this empty or zero to not request a specific amount.</source>
<context-group purpose="location"><context context-type="linenumber">34</context></context-group>
<context-group purpose="location"><context context-type="linenumber">193</context></context-group>
</trans-unit>
- <trans-unit id="_msg614">
+ <trans-unit id="_msg639">
<source xml:space="preserve">An optional label to associate with the new receiving address (used by you to identify an invoice). It is also attached to the payment request.</source>
<context-group purpose="location"><context context-type="linenumber">66</context></context-group>
</trans-unit>
- <trans-unit id="_msg615">
+ <trans-unit id="_msg640">
<source xml:space="preserve">An optional message that is attached to the payment request and may be displayed to the sender.</source>
<context-group purpose="location"><context context-type="linenumber">96</context></context-group>
</trans-unit>
- <trans-unit id="_msg616">
+ <trans-unit id="_msg641">
<source xml:space="preserve">&amp;Create new receiving address</source>
<context-group purpose="location"><context context-type="linenumber">111</context></context-group>
</trans-unit>
- <trans-unit id="_msg617">
+ <trans-unit id="_msg642">
<source xml:space="preserve">Clear all fields of the form.</source>
<context-group purpose="location"><context context-type="linenumber">134</context></context-group>
</trans-unit>
- <trans-unit id="_msg618">
+ <trans-unit id="_msg643">
<source xml:space="preserve">Clear</source>
<context-group purpose="location"><context context-type="linenumber">137</context></context-group>
</trans-unit>
- <trans-unit id="_msg619">
+ <trans-unit id="_msg644">
<source xml:space="preserve">Requested payments history</source>
<context-group purpose="location"><context context-type="linenumber">273</context></context-group>
</trans-unit>
- <trans-unit id="_msg620">
+ <trans-unit id="_msg645">
<source xml:space="preserve">Show the selected request (does the same as double clicking an entry)</source>
<context-group purpose="location"><context context-type="linenumber">298</context></context-group>
</trans-unit>
- <trans-unit id="_msg621">
+ <trans-unit id="_msg646">
<source xml:space="preserve">Show</source>
<context-group purpose="location"><context context-type="linenumber">301</context></context-group>
</trans-unit>
- <trans-unit id="_msg622">
+ <trans-unit id="_msg647">
<source xml:space="preserve">Remove the selected entries from the list</source>
<context-group purpose="location"><context context-type="linenumber">318</context></context-group>
</trans-unit>
- <trans-unit id="_msg623">
+ <trans-unit id="_msg648">
<source xml:space="preserve">Remove</source>
<context-group purpose="location"><context context-type="linenumber">321</context></context-group>
</trans-unit>
@@ -2859,31 +3003,31 @@ For more information on using this console, type %6.
</body></file>
<file original="../receivecoinsdialog.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="ReceiveCoinsDialog">
- <trans-unit id="_msg624">
+ <trans-unit id="_msg649">
<source xml:space="preserve">Copy &amp;URI</source>
<context-group purpose="location"><context context-type="linenumber">47</context></context-group>
</trans-unit>
- <trans-unit id="_msg625">
+ <trans-unit id="_msg650">
<source xml:space="preserve">&amp;Copy address</source>
<context-group purpose="location"><context context-type="linenumber">48</context></context-group>
</trans-unit>
- <trans-unit id="_msg626">
+ <trans-unit id="_msg651">
<source xml:space="preserve">Copy &amp;label</source>
<context-group purpose="location"><context context-type="linenumber">49</context></context-group>
</trans-unit>
- <trans-unit id="_msg627">
+ <trans-unit id="_msg652">
<source xml:space="preserve">Copy &amp;message</source>
<context-group purpose="location"><context context-type="linenumber">50</context></context-group>
</trans-unit>
- <trans-unit id="_msg628">
+ <trans-unit id="_msg653">
<source xml:space="preserve">Copy &amp;amount</source>
<context-group purpose="location"><context context-type="linenumber">51</context></context-group>
</trans-unit>
- <trans-unit id="_msg629">
+ <trans-unit id="_msg654">
<source xml:space="preserve">Could not unlock wallet.</source>
<context-group purpose="location"><context context-type="linenumber">176</context></context-group>
</trans-unit>
- <trans-unit id="_msg630">
+ <trans-unit id="_msg655">
<source xml:space="preserve">Could not generate new %1 address</source>
<context-group purpose="location"><context context-type="linenumber">181</context></context-group>
</trans-unit>
@@ -2891,51 +3035,51 @@ For more information on using this console, type %6.
</body></file>
<file original="../forms/receiverequestdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="ReceiveRequestDialog">
- <trans-unit id="_msg631">
+ <trans-unit id="_msg656">
<source xml:space="preserve">Request payment to …</source>
<context-group purpose="location"><context context-type="linenumber">14</context></context-group>
</trans-unit>
- <trans-unit id="_msg632">
+ <trans-unit id="_msg657">
<source xml:space="preserve">Address:</source>
<context-group purpose="location"><context context-type="linenumber">90</context></context-group>
</trans-unit>
- <trans-unit id="_msg633">
+ <trans-unit id="_msg658">
<source xml:space="preserve">Amount:</source>
<context-group purpose="location"><context context-type="linenumber">119</context></context-group>
</trans-unit>
- <trans-unit id="_msg634">
+ <trans-unit id="_msg659">
<source xml:space="preserve">Label:</source>
<context-group purpose="location"><context context-type="linenumber">148</context></context-group>
</trans-unit>
- <trans-unit id="_msg635">
+ <trans-unit id="_msg660">
<source xml:space="preserve">Message:</source>
<context-group purpose="location"><context context-type="linenumber">180</context></context-group>
</trans-unit>
- <trans-unit id="_msg636">
+ <trans-unit id="_msg661">
<source xml:space="preserve">Wallet:</source>
<context-group purpose="location"><context context-type="linenumber">212</context></context-group>
</trans-unit>
- <trans-unit id="_msg637">
+ <trans-unit id="_msg662">
<source xml:space="preserve">Copy &amp;URI</source>
<context-group purpose="location"><context context-type="linenumber">240</context></context-group>
</trans-unit>
- <trans-unit id="_msg638">
+ <trans-unit id="_msg663">
<source xml:space="preserve">Copy &amp;Address</source>
<context-group purpose="location"><context context-type="linenumber">250</context></context-group>
</trans-unit>
- <trans-unit id="_msg639">
+ <trans-unit id="_msg664">
<source xml:space="preserve">&amp;Verify</source>
<context-group purpose="location"><context context-type="linenumber">260</context></context-group>
</trans-unit>
- <trans-unit id="_msg640">
+ <trans-unit id="_msg665">
<source xml:space="preserve">Verify this address on e.g. a hardware wallet screen</source>
<context-group purpose="location"><context context-type="linenumber">263</context></context-group>
</trans-unit>
- <trans-unit id="_msg641">
+ <trans-unit id="_msg666">
<source xml:space="preserve">&amp;Save Image…</source>
<context-group purpose="location"><context context-type="linenumber">273</context></context-group>
</trans-unit>
- <trans-unit id="_msg642">
+ <trans-unit id="_msg667">
<source xml:space="preserve">Payment information</source>
<context-group purpose="location"><context context-type="linenumber">39</context></context-group>
</trans-unit>
@@ -2943,7 +3087,7 @@ For more information on using this console, type %6.
</body></file>
<file original="../receiverequestdialog.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="ReceiveRequestDialog">
- <trans-unit id="_msg643">
+ <trans-unit id="_msg668">
<source xml:space="preserve">Request payment to %1</source>
<context-group purpose="location"><context context-type="linenumber">49</context></context-group>
</trans-unit>
@@ -2951,186 +3095,186 @@ For more information on using this console, type %6.
</body></file>
<file original="../recentrequeststablemodel.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="RecentRequestsTableModel">
- <trans-unit id="_msg644">
+ <trans-unit id="_msg669">
<source xml:space="preserve">Date</source>
<context-group purpose="location"><context context-type="linenumber">32</context></context-group>
</trans-unit>
- <trans-unit id="_msg645">
+ <trans-unit id="_msg670">
<source xml:space="preserve">Label</source>
<context-group purpose="location"><context context-type="linenumber">32</context></context-group>
</trans-unit>
- <trans-unit id="_msg646">
+ <trans-unit id="_msg671">
<source xml:space="preserve">Message</source>
<context-group purpose="location"><context context-type="linenumber">32</context></context-group>
</trans-unit>
- <trans-unit id="_msg647">
+ <trans-unit id="_msg672">
<source xml:space="preserve">(no label)</source>
- <context-group purpose="location"><context context-type="linenumber">73</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">70</context></context-group>
</trans-unit>
- <trans-unit id="_msg648">
+ <trans-unit id="_msg673">
<source xml:space="preserve">(no message)</source>
- <context-group purpose="location"><context context-type="linenumber">82</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">79</context></context-group>
</trans-unit>
- <trans-unit id="_msg649">
+ <trans-unit id="_msg674">
<source xml:space="preserve">(no amount requested)</source>
- <context-group purpose="location"><context context-type="linenumber">90</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">87</context></context-group>
</trans-unit>
- <trans-unit id="_msg650">
+ <trans-unit id="_msg675">
<source xml:space="preserve">Requested</source>
- <context-group purpose="location"><context context-type="linenumber">133</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">130</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../forms/sendcoinsdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="SendCoinsDialog">
- <trans-unit id="_msg651">
+ <trans-unit id="_msg676">
<source xml:space="preserve">Send Coins</source>
<context-group purpose="location"><context context-type="linenumber">14</context></context-group>
- <context-group purpose="location"><context context-type="sourcefile">../sendcoinsdialog.cpp</context><context context-type="linenumber">752</context></context-group>
+ <context-group purpose="location"><context context-type="sourcefile">../sendcoinsdialog.cpp</context><context context-type="linenumber">757</context></context-group>
</trans-unit>
- <trans-unit id="_msg652">
+ <trans-unit id="_msg677">
<source xml:space="preserve">Coin Control Features</source>
<context-group purpose="location"><context context-type="linenumber">90</context></context-group>
</trans-unit>
- <trans-unit id="_msg653">
+ <trans-unit id="_msg678">
<source xml:space="preserve">automatically selected</source>
<context-group purpose="location"><context context-type="linenumber">120</context></context-group>
</trans-unit>
- <trans-unit id="_msg654">
+ <trans-unit id="_msg679">
<source xml:space="preserve">Insufficient funds!</source>
<context-group purpose="location"><context context-type="linenumber">139</context></context-group>
</trans-unit>
- <trans-unit id="_msg655">
+ <trans-unit id="_msg680">
<source xml:space="preserve">Quantity:</source>
<context-group purpose="location"><context context-type="linenumber">228</context></context-group>
</trans-unit>
- <trans-unit id="_msg656">
+ <trans-unit id="_msg681">
<source xml:space="preserve">Bytes:</source>
<context-group purpose="location"><context context-type="linenumber">263</context></context-group>
</trans-unit>
- <trans-unit id="_msg657">
+ <trans-unit id="_msg682">
<source xml:space="preserve">Amount:</source>
<context-group purpose="location"><context context-type="linenumber">311</context></context-group>
</trans-unit>
- <trans-unit id="_msg658">
+ <trans-unit id="_msg683">
<source xml:space="preserve">Fee:</source>
<context-group purpose="location"><context context-type="linenumber">391</context></context-group>
</trans-unit>
- <trans-unit id="_msg659">
+ <trans-unit id="_msg684">
<source xml:space="preserve">After Fee:</source>
<context-group purpose="location"><context context-type="linenumber">442</context></context-group>
</trans-unit>
- <trans-unit id="_msg660">
+ <trans-unit id="_msg685">
<source xml:space="preserve">Change:</source>
<context-group purpose="location"><context context-type="linenumber">474</context></context-group>
</trans-unit>
- <trans-unit id="_msg661">
+ <trans-unit id="_msg686">
<source xml:space="preserve">If this is activated, but the change address is empty or invalid, change will be sent to a newly generated address.</source>
<context-group purpose="location"><context context-type="linenumber">518</context></context-group>
</trans-unit>
- <trans-unit id="_msg662">
+ <trans-unit id="_msg687">
<source xml:space="preserve">Custom change address</source>
<context-group purpose="location"><context context-type="linenumber">521</context></context-group>
</trans-unit>
- <trans-unit id="_msg663">
+ <trans-unit id="_msg688">
<source xml:space="preserve">Transaction Fee:</source>
<context-group purpose="location"><context context-type="linenumber">727</context></context-group>
</trans-unit>
- <trans-unit id="_msg664">
+ <trans-unit id="_msg689">
<source xml:space="preserve">Using the fallbackfee can result in sending a transaction that will take several hours or days (or never) to confirm. Consider choosing your fee manually or wait until you have validated the complete chain.</source>
<context-group purpose="location"><context context-type="linenumber">765</context></context-group>
</trans-unit>
- <trans-unit id="_msg665">
+ <trans-unit id="_msg690">
<source xml:space="preserve">Warning: Fee estimation is currently not possible.</source>
<context-group purpose="location"><context context-type="linenumber">774</context></context-group>
</trans-unit>
- <trans-unit id="_msg666">
+ <trans-unit id="_msg691">
<source xml:space="preserve">per kilobyte</source>
<context-group purpose="location"><context context-type="linenumber">856</context></context-group>
</trans-unit>
- <trans-unit id="_msg667">
+ <trans-unit id="_msg692">
<source xml:space="preserve">Hide</source>
<context-group purpose="location"><context context-type="linenumber">803</context></context-group>
</trans-unit>
- <trans-unit id="_msg668">
+ <trans-unit id="_msg693">
<source xml:space="preserve">Recommended:</source>
<context-group purpose="location"><context context-type="linenumber">915</context></context-group>
</trans-unit>
- <trans-unit id="_msg669">
+ <trans-unit id="_msg694">
<source xml:space="preserve">Custom:</source>
<context-group purpose="location"><context context-type="linenumber">945</context></context-group>
</trans-unit>
- <trans-unit id="_msg670">
+ <trans-unit id="_msg695">
<source xml:space="preserve">Send to multiple recipients at once</source>
<context-group purpose="location"><context context-type="linenumber">1160</context></context-group>
</trans-unit>
- <trans-unit id="_msg671">
+ <trans-unit id="_msg696">
<source xml:space="preserve">Add &amp;Recipient</source>
<context-group purpose="location"><context context-type="linenumber">1163</context></context-group>
</trans-unit>
- <trans-unit id="_msg672">
+ <trans-unit id="_msg697">
<source xml:space="preserve">Clear all fields of the form.</source>
<context-group purpose="location"><context context-type="linenumber">1143</context></context-group>
</trans-unit>
- <trans-unit id="_msg673">
+ <trans-unit id="_msg698">
<source xml:space="preserve">Inputs…</source>
<context-group purpose="location"><context context-type="linenumber">110</context></context-group>
</trans-unit>
- <trans-unit id="_msg674">
+ <trans-unit id="_msg699">
<source xml:space="preserve">Dust:</source>
<context-group purpose="location"><context context-type="linenumber">343</context></context-group>
</trans-unit>
- <trans-unit id="_msg675">
+ <trans-unit id="_msg700">
<source xml:space="preserve">Choose…</source>
<context-group purpose="location"><context context-type="linenumber">741</context></context-group>
</trans-unit>
- <trans-unit id="_msg676">
+ <trans-unit id="_msg701">
<source xml:space="preserve">Hide transaction fee settings</source>
<context-group purpose="location"><context context-type="linenumber">800</context></context-group>
</trans-unit>
- <trans-unit id="_msg677">
+ <trans-unit id="_msg702">
<source xml:space="preserve">Specify a custom fee per kB (1,000 bytes) of the transaction&apos;s virtual size.
Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100 satoshis per kvB&quot; for a transaction size of 500 virtual bytes (half of 1 kvB) would ultimately yield a fee of only 50 satoshis.</source>
<context-group purpose="location"><context context-type="linenumber">851</context></context-group>
</trans-unit>
- <trans-unit id="_msg678">
+ <trans-unit id="_msg703">
<source xml:space="preserve">When there is less transaction volume than space in the blocks, miners as well as relaying nodes may enforce a minimum fee. Paying only this minimum fee is just fine, but be aware that this can result in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</source>
<context-group purpose="location"><context context-type="linenumber">886</context></context-group>
</trans-unit>
- <trans-unit id="_msg679">
+ <trans-unit id="_msg704">
<source xml:space="preserve">A too low fee might result in a never confirming transaction (read the tooltip)</source>
<context-group purpose="location"><context context-type="linenumber">889</context></context-group>
</trans-unit>
- <trans-unit id="_msg680">
+ <trans-unit id="_msg705">
<source xml:space="preserve">(Smart fee not initialized yet. This usually takes a few blocks…)</source>
<context-group purpose="location"><context context-type="linenumber">994</context></context-group>
</trans-unit>
- <trans-unit id="_msg681">
+ <trans-unit id="_msg706">
<source xml:space="preserve">Confirmation time target:</source>
<context-group purpose="location"><context context-type="linenumber">1020</context></context-group>
</trans-unit>
- <trans-unit id="_msg682">
+ <trans-unit id="_msg707">
<source xml:space="preserve">Enable Replace-By-Fee</source>
<context-group purpose="location"><context context-type="linenumber">1078</context></context-group>
</trans-unit>
- <trans-unit id="_msg683">
+ <trans-unit id="_msg708">
<source xml:space="preserve">With Replace-By-Fee (BIP-125) you can increase a transaction&apos;s fee after it is sent. Without this, a higher fee may be recommended to compensate for increased transaction delay risk.</source>
<context-group purpose="location"><context context-type="linenumber">1081</context></context-group>
</trans-unit>
- <trans-unit id="_msg684">
+ <trans-unit id="_msg709">
<source xml:space="preserve">Clear &amp;All</source>
<context-group purpose="location"><context context-type="linenumber">1146</context></context-group>
</trans-unit>
- <trans-unit id="_msg685">
+ <trans-unit id="_msg710">
<source xml:space="preserve">Balance:</source>
<context-group purpose="location"><context context-type="linenumber">1201</context></context-group>
</trans-unit>
- <trans-unit id="_msg686">
+ <trans-unit id="_msg711">
<source xml:space="preserve">Confirm the send action</source>
<context-group purpose="location"><context context-type="linenumber">1117</context></context-group>
</trans-unit>
- <trans-unit id="_msg687">
+ <trans-unit id="_msg712">
<source xml:space="preserve">S&amp;end</source>
<context-group purpose="location"><context context-type="linenumber">1120</context></context-group>
</trans-unit>
@@ -3138,422 +3282,396 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
</body></file>
<file original="../sendcoinsdialog.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="SendCoinsDialog">
- <trans-unit id="_msg688">
+ <trans-unit id="_msg713">
<source xml:space="preserve">Copy quantity</source>
<context-group purpose="location"><context context-type="linenumber">99</context></context-group>
</trans-unit>
- <trans-unit id="_msg689">
+ <trans-unit id="_msg714">
<source xml:space="preserve">Copy amount</source>
<context-group purpose="location"><context context-type="linenumber">100</context></context-group>
</trans-unit>
- <trans-unit id="_msg690">
+ <trans-unit id="_msg715">
<source xml:space="preserve">Copy fee</source>
<context-group purpose="location"><context context-type="linenumber">101</context></context-group>
</trans-unit>
- <trans-unit id="_msg691">
+ <trans-unit id="_msg716">
<source xml:space="preserve">Copy after fee</source>
<context-group purpose="location"><context context-type="linenumber">102</context></context-group>
</trans-unit>
- <trans-unit id="_msg692">
+ <trans-unit id="_msg717">
<source xml:space="preserve">Copy bytes</source>
<context-group purpose="location"><context context-type="linenumber">103</context></context-group>
</trans-unit>
- <trans-unit id="_msg693">
+ <trans-unit id="_msg718">
<source xml:space="preserve">Copy dust</source>
<context-group purpose="location"><context context-type="linenumber">104</context></context-group>
</trans-unit>
- <trans-unit id="_msg694">
+ <trans-unit id="_msg719">
<source xml:space="preserve">Copy change</source>
<context-group purpose="location"><context context-type="linenumber">105</context></context-group>
</trans-unit>
- <trans-unit id="_msg695">
+ <trans-unit id="_msg720">
<source xml:space="preserve">%1 (%2 blocks)</source>
- <context-group purpose="location"><context context-type="linenumber">181</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">179</context></context-group>
</trans-unit>
- <trans-unit id="_msg696">
+ <trans-unit id="_msg721">
<source xml:space="preserve">Sign on device</source>
- <context-group purpose="location"><context context-type="linenumber">211</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">209</context></context-group>
<note annotates="source" from="developer">&quot;device&quot; usually means a hardware wallet.</note>
</trans-unit>
- <trans-unit id="_msg697">
+ <trans-unit id="_msg722">
<source xml:space="preserve">Connect your hardware wallet first.</source>
- <context-group purpose="location"><context context-type="linenumber">214</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">212</context></context-group>
</trans-unit>
- <trans-unit id="_msg698">
+ <trans-unit id="_msg723">
<source xml:space="preserve">Set external signer script path in Options -&gt; Wallet</source>
- <context-group purpose="location"><context context-type="linenumber">218</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">216</context></context-group>
<note annotates="source" from="developer">&quot;External signer&quot; means using devices such as hardware wallets.</note>
</trans-unit>
- <trans-unit id="_msg699">
+ <trans-unit id="_msg724">
<source xml:space="preserve">Cr&amp;eate Unsigned</source>
- <context-group purpose="location"><context context-type="linenumber">221</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">219</context></context-group>
</trans-unit>
- <trans-unit id="_msg700">
+ <trans-unit id="_msg725">
<source xml:space="preserve">Creates a Partially Signed Bitcoin Transaction (PSBT) for use with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.</source>
- <context-group purpose="location"><context context-type="linenumber">222</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">220</context></context-group>
</trans-unit>
- <trans-unit id="_msg701">
+ <trans-unit id="_msg726">
<source xml:space="preserve"> from wallet &apos;%1&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">312</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">310</context></context-group>
</trans-unit>
- <trans-unit id="_msg702">
+ <trans-unit id="_msg727">
<source xml:space="preserve">%1 to &apos;%2&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">323</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">321</context></context-group>
</trans-unit>
- <trans-unit id="_msg703">
+ <trans-unit id="_msg728">
<source xml:space="preserve">%1 to %2</source>
- <context-group purpose="location"><context context-type="linenumber">328</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">326</context></context-group>
</trans-unit>
- <trans-unit id="_msg704">
+ <trans-unit id="_msg729">
<source xml:space="preserve">To review recipient list click &quot;Show Details…&quot;</source>
- <context-group purpose="location"><context context-type="linenumber">395</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">392</context></context-group>
</trans-unit>
- <trans-unit id="_msg705">
+ <trans-unit id="_msg730">
<source xml:space="preserve">Sign failed</source>
- <context-group purpose="location"><context context-type="linenumber">439</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">452</context></context-group>
</trans-unit>
- <trans-unit id="_msg706">
+ <trans-unit id="_msg731">
<source xml:space="preserve">External signer not found</source>
- <context-group purpose="location"><context context-type="linenumber">445</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">457</context></context-group>
<note annotates="source" from="developer">&quot;External signer&quot; means using devices such as hardware wallets.</note>
</trans-unit>
- <trans-unit id="_msg707">
+ <trans-unit id="_msg732">
<source xml:space="preserve">External signer failure</source>
- <context-group purpose="location"><context context-type="linenumber">451</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">462</context></context-group>
<note annotates="source" from="developer">&quot;External signer&quot; means using devices such as hardware wallets.</note>
</trans-unit>
- <trans-unit id="_msg708">
+ <trans-unit id="_msg733">
<source xml:space="preserve">Save Transaction Data</source>
- <context-group purpose="location"><context context-type="linenumber">509</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">428</context></context-group>
</trans-unit>
- <trans-unit id="_msg709">
+ <trans-unit id="_msg734">
<source xml:space="preserve">Partially Signed Transaction (Binary)</source>
- <context-group purpose="location"><context context-type="linenumber">511</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">430</context></context-group>
<note annotates="source" from="developer">Expanded name of the binary PSBT file format. See: BIP 174.</note>
</trans-unit>
- <trans-unit id="_msg710">
+ <trans-unit id="_msg735">
<source xml:space="preserve">PSBT saved</source>
- <context-group purpose="location"><context context-type="linenumber">518</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">437</context></context-group>
</trans-unit>
- <trans-unit id="_msg711">
+ <trans-unit id="_msg736">
<source xml:space="preserve">External balance:</source>
- <context-group purpose="location"><context context-type="linenumber">694</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">703</context></context-group>
</trans-unit>
- <trans-unit id="_msg712">
+ <trans-unit id="_msg737">
<source xml:space="preserve">or</source>
- <context-group purpose="location"><context context-type="linenumber">391</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">388</context></context-group>
</trans-unit>
- <trans-unit id="_msg713">
+ <trans-unit id="_msg738">
<source xml:space="preserve">You can increase the fee later (signals Replace-By-Fee, BIP-125).</source>
- <context-group purpose="location"><context context-type="linenumber">372</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">370</context></context-group>
</trans-unit>
- <trans-unit id="_msg714">
+ <trans-unit id="_msg739">
<source xml:space="preserve">Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can save or copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.</source>
- <context-group purpose="location"><context context-type="linenumber">342</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">340</context></context-group>
<note annotates="source" from="developer">Text to inform a user attempting to create a transaction of their current options. At this stage, a user can only create a PSBT. This string is displayed when private keys are disabled and an external signer is not available.</note>
</trans-unit>
- <trans-unit id="_msg715">
+ <trans-unit id="_msg740">
<source xml:space="preserve">Do you want to create this transaction?</source>
- <context-group purpose="location"><context context-type="linenumber">336</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">334</context></context-group>
<note annotates="source" from="developer">Message displayed when attempting to create a transaction. Cautionary text to prompt the user to verify that the displayed transaction details represent the transaction the user intends to create.</note>
</trans-unit>
- <trans-unit id="_msg716">
+ <trans-unit id="_msg741">
<source xml:space="preserve">Please, review your transaction. You can create and send this transaction or create a Partially Signed Bitcoin Transaction (PSBT), which you can save or copy and then sign with, e.g., an offline %1 wallet, or a PSBT-compatible hardware wallet.</source>
- <context-group purpose="location"><context context-type="linenumber">347</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">345</context></context-group>
<note annotates="source" from="developer">Text to inform a user attempting to create a transaction of their current options. At this stage, a user can send their transaction or create a PSBT. This string is displayed when both private keys and PSBT controls are enabled.</note>
</trans-unit>
- <trans-unit id="_msg717">
+ <trans-unit id="_msg742">
<source xml:space="preserve">Please, review your transaction.</source>
- <context-group purpose="location"><context context-type="linenumber">350</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">348</context></context-group>
<note annotates="source" from="developer">Text to prompt a user to review the details of the transaction they are attempting to send.</note>
</trans-unit>
- <trans-unit id="_msg718">
+ <trans-unit id="_msg743">
<source xml:space="preserve">Transaction fee</source>
- <context-group purpose="location"><context context-type="linenumber">358</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">356</context></context-group>
</trans-unit>
- <trans-unit id="_msg719">
+ <trans-unit id="_msg744">
<source xml:space="preserve">Not signalling Replace-By-Fee, BIP-125.</source>
- <context-group purpose="location"><context context-type="linenumber">374</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">372</context></context-group>
</trans-unit>
- <trans-unit id="_msg720">
+ <trans-unit id="_msg745">
<source xml:space="preserve">Total Amount</source>
- <context-group purpose="location"><context context-type="linenumber">388</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">385</context></context-group>
</trans-unit>
- <trans-unit id="_msg721">
+ <trans-unit id="_msg746">
<source xml:space="preserve">Confirm send coins</source>
- <context-group purpose="location"><context context-type="linenumber">413</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">484</context></context-group>
</trans-unit>
- <trans-unit id="_msg722">
+ <trans-unit id="_msg747">
<source xml:space="preserve">Watch-only balance:</source>
- <context-group purpose="location"><context context-type="linenumber">697</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">706</context></context-group>
</trans-unit>
- <trans-unit id="_msg723">
+ <trans-unit id="_msg748">
<source xml:space="preserve">The recipient address is not valid. Please recheck.</source>
- <context-group purpose="location"><context context-type="linenumber">721</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">730</context></context-group>
</trans-unit>
- <trans-unit id="_msg724">
+ <trans-unit id="_msg749">
<source xml:space="preserve">The amount to pay must be larger than 0.</source>
- <context-group purpose="location"><context context-type="linenumber">724</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">733</context></context-group>
</trans-unit>
- <trans-unit id="_msg725">
+ <trans-unit id="_msg750">
<source xml:space="preserve">The amount exceeds your balance.</source>
- <context-group purpose="location"><context context-type="linenumber">727</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">736</context></context-group>
</trans-unit>
- <trans-unit id="_msg726">
+ <trans-unit id="_msg751">
<source xml:space="preserve">The total exceeds your balance when the %1 transaction fee is included.</source>
- <context-group purpose="location"><context context-type="linenumber">730</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">739</context></context-group>
</trans-unit>
- <trans-unit id="_msg727">
+ <trans-unit id="_msg752">
<source xml:space="preserve">Duplicate address found: addresses should only be used once each.</source>
- <context-group purpose="location"><context context-type="linenumber">733</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">742</context></context-group>
</trans-unit>
- <trans-unit id="_msg728">
+ <trans-unit id="_msg753">
<source xml:space="preserve">Transaction creation failed!</source>
- <context-group purpose="location"><context context-type="linenumber">736</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">745</context></context-group>
</trans-unit>
- <trans-unit id="_msg729">
+ <trans-unit id="_msg754">
<source xml:space="preserve">A fee higher than %1 is considered an absurdly high fee.</source>
- <context-group purpose="location"><context context-type="linenumber">740</context></context-group>
- </trans-unit>
- <trans-unit id="_msg730">
- <source xml:space="preserve">Payment request expired.</source>
- <context-group purpose="location"><context context-type="linenumber">743</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">749</context></context-group>
</trans-unit>
<group restype="x-gettext-plurals">
- <context-group purpose="location"><context context-type="linenumber">867</context></context-group>
- <trans-unit id="_msg731[0]">
+ <context-group purpose="location"><context context-type="linenumber">872</context></context-group>
+ <trans-unit id="_msg755[0]">
<source xml:space="preserve">Estimated to begin confirmation within %n block(s).</source>
</trans-unit>
- <trans-unit id="_msg731[1]">
+ <trans-unit id="_msg755[1]">
<source xml:space="preserve">Estimated to begin confirmation within %n block(s).</source>
</trans-unit>
</group>
- <trans-unit id="_msg732">
+ <trans-unit id="_msg756">
<source xml:space="preserve">Warning: Invalid Bitcoin address</source>
- <context-group purpose="location"><context context-type="linenumber">968</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">973</context></context-group>
</trans-unit>
- <trans-unit id="_msg733">
+ <trans-unit id="_msg757">
<source xml:space="preserve">Warning: Unknown change address</source>
- <context-group purpose="location"><context context-type="linenumber">973</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">978</context></context-group>
</trans-unit>
- <trans-unit id="_msg734">
+ <trans-unit id="_msg758">
<source xml:space="preserve">Confirm custom change address</source>
- <context-group purpose="location"><context context-type="linenumber">976</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">981</context></context-group>
</trans-unit>
- <trans-unit id="_msg735">
+ <trans-unit id="_msg759">
<source xml:space="preserve">The address you selected for change is not part of this wallet. Any or all funds in your wallet may be sent to this address. Are you sure?</source>
- <context-group purpose="location"><context context-type="linenumber">976</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">981</context></context-group>
</trans-unit>
- <trans-unit id="_msg736">
+ <trans-unit id="_msg760">
<source xml:space="preserve">(no label)</source>
- <context-group purpose="location"><context context-type="linenumber">997</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">1002</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../forms/sendcoinsentry.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="SendCoinsEntry">
- <trans-unit id="_msg737">
+ <trans-unit id="_msg761">
<source xml:space="preserve">A&amp;mount:</source>
- <context-group purpose="location"><context context-type="linenumber">155</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">705</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">1238</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">151</context></context-group>
</trans-unit>
- <trans-unit id="_msg738">
+ <trans-unit id="_msg762">
<source xml:space="preserve">Pay &amp;To:</source>
- <context-group purpose="location"><context context-type="linenumber">39</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">35</context></context-group>
</trans-unit>
- <trans-unit id="_msg739">
+ <trans-unit id="_msg763">
<source xml:space="preserve">&amp;Label:</source>
- <context-group purpose="location"><context context-type="linenumber">132</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">128</context></context-group>
</trans-unit>
- <trans-unit id="_msg740">
+ <trans-unit id="_msg764">
<source xml:space="preserve">Choose previously used address</source>
- <context-group purpose="location"><context context-type="linenumber">64</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">60</context></context-group>
</trans-unit>
- <trans-unit id="_msg741">
+ <trans-unit id="_msg765">
<source xml:space="preserve">The Bitcoin address to send the payment to</source>
- <context-group purpose="location"><context context-type="linenumber">57</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">53</context></context-group>
</trans-unit>
- <trans-unit id="_msg742">
+ <trans-unit id="_msg766">
<source xml:space="preserve">Alt+A</source>
- <context-group purpose="location"><context context-type="linenumber">80</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">76</context></context-group>
</trans-unit>
- <trans-unit id="_msg743">
+ <trans-unit id="_msg767">
<source xml:space="preserve">Paste address from clipboard</source>
- <context-group purpose="location"><context context-type="linenumber">87</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">83</context></context-group>
</trans-unit>
- <trans-unit id="_msg744">
+ <trans-unit id="_msg768">
<source xml:space="preserve">Alt+P</source>
- <context-group purpose="location"><context context-type="linenumber">103</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">99</context></context-group>
</trans-unit>
- <trans-unit id="_msg745">
+ <trans-unit id="_msg769">
<source xml:space="preserve">Remove this entry</source>
- <context-group purpose="location"><context context-type="linenumber">110</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">672</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">1205</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">106</context></context-group>
</trans-unit>
- <trans-unit id="_msg746">
+ <trans-unit id="_msg770">
<source xml:space="preserve">The amount to send in the selected unit</source>
- <context-group purpose="location"><context context-type="linenumber">170</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">166</context></context-group>
</trans-unit>
- <trans-unit id="_msg747">
+ <trans-unit id="_msg771">
<source xml:space="preserve">The fee will be deducted from the amount being sent. The recipient will receive less bitcoins than you enter in the amount field. If multiple recipients are selected, the fee is split equally.</source>
- <context-group purpose="location"><context context-type="linenumber">177</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">173</context></context-group>
</trans-unit>
- <trans-unit id="_msg748">
+ <trans-unit id="_msg772">
<source xml:space="preserve">S&amp;ubtract fee from amount</source>
- <context-group purpose="location"><context context-type="linenumber">180</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">176</context></context-group>
</trans-unit>
- <trans-unit id="_msg749">
+ <trans-unit id="_msg773">
<source xml:space="preserve">Use available balance</source>
- <context-group purpose="location"><context context-type="linenumber">187</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">183</context></context-group>
</trans-unit>
- <trans-unit id="_msg750">
+ <trans-unit id="_msg774">
<source xml:space="preserve">Message:</source>
- <context-group purpose="location"><context context-type="linenumber">196</context></context-group>
- </trans-unit>
- <trans-unit id="_msg751">
- <source xml:space="preserve">This is an unauthenticated payment request.</source>
- <context-group purpose="location"><context context-type="linenumber">639</context></context-group>
- </trans-unit>
- <trans-unit id="_msg752">
- <source xml:space="preserve">This is an authenticated payment request.</source>
- <context-group purpose="location"><context context-type="linenumber">1168</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">192</context></context-group>
</trans-unit>
- <trans-unit id="_msg753">
+ <trans-unit id="_msg775">
<source xml:space="preserve">Enter a label for this address to add it to the list of used addresses</source>
- <context-group purpose="location"><context context-type="linenumber">145</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">148</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">141</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">144</context></context-group>
</trans-unit>
- <trans-unit id="_msg754">
+ <trans-unit id="_msg776">
<source xml:space="preserve">A message that was attached to the bitcoin: URI which will be stored with the transaction for your reference. Note: This message will not be sent over the Bitcoin network.</source>
- <context-group purpose="location"><context context-type="linenumber">206</context></context-group>
- </trans-unit>
- <trans-unit id="_msg755">
- <source xml:space="preserve">Pay To:</source>
- <context-group purpose="location"><context context-type="linenumber">654</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">1183</context></context-group>
- </trans-unit>
- <trans-unit id="_msg756">
- <source xml:space="preserve">Memo:</source>
- <context-group purpose="location"><context context-type="linenumber">688</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">1221</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">202</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../sendcoinsdialog.h" datatype="c" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="SendConfirmationDialog">
- <trans-unit id="_msg757">
+ <trans-unit id="_msg777">
<source xml:space="preserve">Send</source>
- <context-group purpose="location"><context context-type="linenumber">131</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">144</context></context-group>
</trans-unit>
- <trans-unit id="_msg758">
+ <trans-unit id="_msg778">
<source xml:space="preserve">Create Unsigned</source>
- <context-group purpose="location"><context context-type="linenumber">133</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">146</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../forms/signverifymessagedialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="SignVerifyMessageDialog">
- <trans-unit id="_msg759">
+ <trans-unit id="_msg779">
<source xml:space="preserve">Signatures - Sign / Verify a Message</source>
<context-group purpose="location"><context context-type="linenumber">14</context></context-group>
</trans-unit>
- <trans-unit id="_msg760">
+ <trans-unit id="_msg780">
<source xml:space="preserve">&amp;Sign Message</source>
<context-group purpose="location"><context context-type="linenumber">27</context></context-group>
</trans-unit>
- <trans-unit id="_msg761">
+ <trans-unit id="_msg781">
<source xml:space="preserve">You can sign messages/agreements with your addresses to prove you can receive bitcoins sent to them. Be careful not to sign anything vague or random, as phishing attacks may try to trick you into signing your identity over to them. Only sign fully-detailed statements you agree to.</source>
<context-group purpose="location"><context context-type="linenumber">33</context></context-group>
</trans-unit>
- <trans-unit id="_msg762">
+ <trans-unit id="_msg782">
<source xml:space="preserve">The Bitcoin address to sign the message with</source>
<context-group purpose="location"><context context-type="linenumber">51</context></context-group>
</trans-unit>
- <trans-unit id="_msg763">
+ <trans-unit id="_msg783">
<source xml:space="preserve">Choose previously used address</source>
<context-group purpose="location"><context context-type="linenumber">58</context></context-group>
<context-group purpose="location"><context context-type="linenumber">274</context></context-group>
</trans-unit>
- <trans-unit id="_msg764">
+ <trans-unit id="_msg784">
<source xml:space="preserve">Alt+A</source>
<context-group purpose="location"><context context-type="linenumber">68</context></context-group>
<context-group purpose="location"><context context-type="linenumber">284</context></context-group>
</trans-unit>
- <trans-unit id="_msg765">
+ <trans-unit id="_msg785">
<source xml:space="preserve">Paste address from clipboard</source>
<context-group purpose="location"><context context-type="linenumber">78</context></context-group>
</trans-unit>
- <trans-unit id="_msg766">
+ <trans-unit id="_msg786">
<source xml:space="preserve">Alt+P</source>
<context-group purpose="location"><context context-type="linenumber">88</context></context-group>
</trans-unit>
- <trans-unit id="_msg767">
+ <trans-unit id="_msg787">
<source xml:space="preserve">Enter the message you want to sign here</source>
<context-group purpose="location"><context context-type="linenumber">100</context></context-group>
<context-group purpose="location"><context context-type="linenumber">103</context></context-group>
</trans-unit>
- <trans-unit id="_msg768">
+ <trans-unit id="_msg788">
<source xml:space="preserve">Signature</source>
<context-group purpose="location"><context context-type="linenumber">110</context></context-group>
</trans-unit>
- <trans-unit id="_msg769">
+ <trans-unit id="_msg789">
<source xml:space="preserve">Copy the current signature to the system clipboard</source>
<context-group purpose="location"><context context-type="linenumber">140</context></context-group>
</trans-unit>
- <trans-unit id="_msg770">
+ <trans-unit id="_msg790">
<source xml:space="preserve">Sign the message to prove you own this Bitcoin address</source>
<context-group purpose="location"><context context-type="linenumber">161</context></context-group>
</trans-unit>
- <trans-unit id="_msg771">
+ <trans-unit id="_msg791">
<source xml:space="preserve">Sign &amp;Message</source>
<context-group purpose="location"><context context-type="linenumber">164</context></context-group>
</trans-unit>
- <trans-unit id="_msg772">
+ <trans-unit id="_msg792">
<source xml:space="preserve">Reset all sign message fields</source>
<context-group purpose="location"><context context-type="linenumber">178</context></context-group>
</trans-unit>
- <trans-unit id="_msg773">
+ <trans-unit id="_msg793">
<source xml:space="preserve">Clear &amp;All</source>
<context-group purpose="location"><context context-type="linenumber">181</context></context-group>
<context-group purpose="location"><context context-type="linenumber">338</context></context-group>
</trans-unit>
- <trans-unit id="_msg774">
+ <trans-unit id="_msg794">
<source xml:space="preserve">&amp;Verify Message</source>
<context-group purpose="location"><context context-type="linenumber">240</context></context-group>
</trans-unit>
- <trans-unit id="_msg775">
+ <trans-unit id="_msg795">
<source xml:space="preserve">Enter the receiver&apos;s address, message (ensure you copy line breaks, spaces, tabs, etc. exactly) and signature below to verify the message. Be careful not to read more into the signature than what is in the signed message itself, to avoid being tricked by a man-in-the-middle attack. Note that this only proves the signing party receives with the address, it cannot prove sendership of any transaction!</source>
<context-group purpose="location"><context context-type="linenumber">246</context></context-group>
</trans-unit>
- <trans-unit id="_msg776">
+ <trans-unit id="_msg796">
<source xml:space="preserve">The Bitcoin address the message was signed with</source>
<context-group purpose="location"><context context-type="linenumber">267</context></context-group>
</trans-unit>
- <trans-unit id="_msg777">
+ <trans-unit id="_msg797">
<source xml:space="preserve">The signed message to verify</source>
<context-group purpose="location"><context context-type="linenumber">296</context></context-group>
<context-group purpose="location"><context context-type="linenumber">299</context></context-group>
</trans-unit>
- <trans-unit id="_msg778">
+ <trans-unit id="_msg798">
<source xml:space="preserve">The signature given when the message was signed</source>
<context-group purpose="location"><context context-type="linenumber">306</context></context-group>
<context-group purpose="location"><context context-type="linenumber">309</context></context-group>
</trans-unit>
- <trans-unit id="_msg779">
+ <trans-unit id="_msg799">
<source xml:space="preserve">Verify the message to ensure it was signed with the specified Bitcoin address</source>
<context-group purpose="location"><context context-type="linenumber">318</context></context-group>
</trans-unit>
- <trans-unit id="_msg780">
+ <trans-unit id="_msg800">
<source xml:space="preserve">Verify &amp;Message</source>
<context-group purpose="location"><context context-type="linenumber">321</context></context-group>
</trans-unit>
- <trans-unit id="_msg781">
+ <trans-unit id="_msg801">
<source xml:space="preserve">Reset all verify message fields</source>
<context-group purpose="location"><context context-type="linenumber">335</context></context-group>
</trans-unit>
- <trans-unit id="_msg782">
+ <trans-unit id="_msg802">
<source xml:space="preserve">Click &quot;Sign Message&quot; to generate signature</source>
<context-group purpose="location"><context context-type="linenumber">125</context></context-group>
</trans-unit>
@@ -3561,61 +3679,61 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
</body></file>
<file original="../signverifymessagedialog.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="SignVerifyMessageDialog">
- <trans-unit id="_msg783">
+ <trans-unit id="_msg803">
<source xml:space="preserve">The entered address is invalid.</source>
<context-group purpose="location"><context context-type="linenumber">120</context></context-group>
<context-group purpose="location"><context context-type="linenumber">219</context></context-group>
</trans-unit>
- <trans-unit id="_msg784">
+ <trans-unit id="_msg804">
<source xml:space="preserve">Please check the address and try again.</source>
<context-group purpose="location"><context context-type="linenumber">120</context></context-group>
<context-group purpose="location"><context context-type="linenumber">127</context></context-group>
<context-group purpose="location"><context context-type="linenumber">220</context></context-group>
<context-group purpose="location"><context context-type="linenumber">227</context></context-group>
</trans-unit>
- <trans-unit id="_msg785">
+ <trans-unit id="_msg805">
<source xml:space="preserve">The entered address does not refer to a key.</source>
<context-group purpose="location"><context context-type="linenumber">127</context></context-group>
<context-group purpose="location"><context context-type="linenumber">226</context></context-group>
</trans-unit>
- <trans-unit id="_msg786">
+ <trans-unit id="_msg806">
<source xml:space="preserve">Wallet unlock was cancelled.</source>
<context-group purpose="location"><context context-type="linenumber">135</context></context-group>
</trans-unit>
- <trans-unit id="_msg787">
+ <trans-unit id="_msg807">
<source xml:space="preserve">No error</source>
<context-group purpose="location"><context context-type="linenumber">146</context></context-group>
</trans-unit>
- <trans-unit id="_msg788">
+ <trans-unit id="_msg808">
<source xml:space="preserve">Private key for the entered address is not available.</source>
<context-group purpose="location"><context context-type="linenumber">149</context></context-group>
</trans-unit>
- <trans-unit id="_msg789">
+ <trans-unit id="_msg809">
<source xml:space="preserve">Message signing failed.</source>
<context-group purpose="location"><context context-type="linenumber">152</context></context-group>
</trans-unit>
- <trans-unit id="_msg790">
+ <trans-unit id="_msg810">
<source xml:space="preserve">Message signed.</source>
<context-group purpose="location"><context context-type="linenumber">164</context></context-group>
</trans-unit>
- <trans-unit id="_msg791">
+ <trans-unit id="_msg811">
<source xml:space="preserve">The signature could not be decoded.</source>
<context-group purpose="location"><context context-type="linenumber">233</context></context-group>
</trans-unit>
- <trans-unit id="_msg792">
+ <trans-unit id="_msg812">
<source xml:space="preserve">Please check the signature and try again.</source>
<context-group purpose="location"><context context-type="linenumber">234</context></context-group>
<context-group purpose="location"><context context-type="linenumber">241</context></context-group>
</trans-unit>
- <trans-unit id="_msg793">
+ <trans-unit id="_msg813">
<source xml:space="preserve">The signature did not match the message digest.</source>
<context-group purpose="location"><context context-type="linenumber">240</context></context-group>
</trans-unit>
- <trans-unit id="_msg794">
+ <trans-unit id="_msg814">
<source xml:space="preserve">Message verification failed.</source>
<context-group purpose="location"><context context-type="linenumber">246</context></context-group>
</trans-unit>
- <trans-unit id="_msg795">
+ <trans-unit id="_msg815">
<source xml:space="preserve">Message verified.</source>
<context-group purpose="location"><context context-type="linenumber">214</context></context-group>
</trans-unit>
@@ -3623,11 +3741,11 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
</body></file>
<file original="../splashscreen.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="SplashScreen">
- <trans-unit id="_msg796">
+ <trans-unit id="_msg816">
<source xml:space="preserve">(press q to shutdown and continue later)</source>
<context-group purpose="location"><context context-type="linenumber">187</context></context-group>
</trans-unit>
- <trans-unit id="_msg797">
+ <trans-unit id="_msg817">
<source xml:space="preserve">press q to shutdown</source>
<context-group purpose="location"><context context-type="linenumber">188</context></context-group>
</trans-unit>
@@ -3635,7 +3753,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
</body></file>
<file original="../trafficgraphwidget.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="TrafficGraphWidget">
- <trans-unit id="_msg798">
+ <trans-unit id="_msg818">
<source xml:space="preserve">kB/s</source>
<context-group purpose="location"><context context-type="linenumber">79</context></context-group>
</trans-unit>
@@ -3643,190 +3761,192 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
</body></file>
<file original="../transactiondesc.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="TransactionDesc">
- <trans-unit id="_msg799">
+ <trans-unit id="_msg819">
<source xml:space="preserve">conflicted with a transaction with %1 confirmations</source>
- <context-group purpose="location"><context context-type="linenumber">40</context></context-group>
- </trans-unit>
- <trans-unit id="_msg800">
- <source xml:space="preserve">0/unconfirmed, %1</source>
<context-group purpose="location"><context context-type="linenumber">43</context></context-group>
+ <note annotates="source" from="developer">Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an unconfirmed transaction that conflicts with a confirmed transaction.</note>
</trans-unit>
- <trans-unit id="_msg801">
- <source xml:space="preserve">in memory pool</source>
- <context-group purpose="location"><context context-type="linenumber">43</context></context-group>
+ <trans-unit id="_msg820">
+ <source xml:space="preserve">0/unconfirmed, in memory pool</source>
+ <context-group purpose="location"><context context-type="linenumber">50</context></context-group>
+ <note annotates="source" from="developer">Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an unconfirmed transaction that is in the memory pool.</note>
</trans-unit>
- <trans-unit id="_msg802">
- <source xml:space="preserve">not in memory pool</source>
- <context-group purpose="location"><context context-type="linenumber">43</context></context-group>
+ <trans-unit id="_msg821">
+ <source xml:space="preserve">0/unconfirmed, not in memory pool</source>
+ <context-group purpose="location"><context context-type="linenumber">55</context></context-group>
+ <note annotates="source" from="developer">Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an unconfirmed transaction that is not in the memory pool.</note>
</trans-unit>
- <trans-unit id="_msg803">
+ <trans-unit id="_msg822">
<source xml:space="preserve">abandoned</source>
- <context-group purpose="location"><context context-type="linenumber">42</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">61</context></context-group>
+ <note annotates="source" from="developer">Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an abandoned transaction.</note>
</trans-unit>
- <trans-unit id="_msg804">
+ <trans-unit id="_msg823">
<source xml:space="preserve">%1/unconfirmed</source>
- <context-group purpose="location"><context context-type="linenumber">45</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">69</context></context-group>
+ <note annotates="source" from="developer">Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents a transaction confirmed in at least one block, but less than 6 blocks.</note>
</trans-unit>
- <trans-unit id="_msg805">
+ <trans-unit id="_msg824">
<source xml:space="preserve">%1 confirmations</source>
- <context-group purpose="location"><context context-type="linenumber">47</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">74</context></context-group>
+ <note annotates="source" from="developer">Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents a transaction confirmed in 6 or more blocks.</note>
</trans-unit>
- <trans-unit id="_msg806">
+ <trans-unit id="_msg825">
<source xml:space="preserve">Status</source>
- <context-group purpose="location"><context context-type="linenumber">98</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">124</context></context-group>
</trans-unit>
- <trans-unit id="_msg807">
+ <trans-unit id="_msg826">
<source xml:space="preserve">Date</source>
- <context-group purpose="location"><context context-type="linenumber">101</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">127</context></context-group>
</trans-unit>
- <trans-unit id="_msg808">
+ <trans-unit id="_msg827">
<source xml:space="preserve">Source</source>
- <context-group purpose="location"><context context-type="linenumber">108</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">134</context></context-group>
</trans-unit>
- <trans-unit id="_msg809">
+ <trans-unit id="_msg828">
<source xml:space="preserve">Generated</source>
- <context-group purpose="location"><context context-type="linenumber">108</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">134</context></context-group>
</trans-unit>
- <trans-unit id="_msg810">
+ <trans-unit id="_msg829">
<source xml:space="preserve">From</source>
- <context-group purpose="location"><context context-type="linenumber">113</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">127</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">199</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">139</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">153</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">225</context></context-group>
</trans-unit>
- <trans-unit id="_msg811">
+ <trans-unit id="_msg830">
<source xml:space="preserve">unknown</source>
- <context-group purpose="location"><context context-type="linenumber">127</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">153</context></context-group>
</trans-unit>
- <trans-unit id="_msg812">
+ <trans-unit id="_msg831">
<source xml:space="preserve">To</source>
- <context-group purpose="location"><context context-type="linenumber">128</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">148</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">218</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">154</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">174</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">244</context></context-group>
</trans-unit>
- <trans-unit id="_msg813">
+ <trans-unit id="_msg832">
<source xml:space="preserve">own address</source>
- <context-group purpose="location"><context context-type="linenumber">130</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">156</context></context-group>
</trans-unit>
- <trans-unit id="_msg814">
+ <trans-unit id="_msg833">
<source xml:space="preserve">watch-only</source>
- <context-group purpose="location"><context context-type="linenumber">130</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">199</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">156</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">225</context></context-group>
</trans-unit>
- <trans-unit id="_msg815">
+ <trans-unit id="_msg834">
<source xml:space="preserve">label</source>
- <context-group purpose="location"><context context-type="linenumber">132</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">158</context></context-group>
</trans-unit>
- <trans-unit id="_msg816">
+ <trans-unit id="_msg835">
<source xml:space="preserve">Credit</source>
- <context-group purpose="location"><context context-type="linenumber">168</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">180</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">234</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">264</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">324</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">194</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">206</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">260</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">290</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">350</context></context-group>
</trans-unit>
<group restype="x-gettext-plurals">
- <context-group purpose="location"><context context-type="linenumber">170</context></context-group>
- <trans-unit id="_msg817[0]">
+ <context-group purpose="location"><context context-type="linenumber">196</context></context-group>
+ <trans-unit id="_msg836[0]">
<source xml:space="preserve">matures in %n more block(s)</source>
</trans-unit>
- <trans-unit id="_msg817[1]">
+ <trans-unit id="_msg836[1]">
<source xml:space="preserve">matures in %n more block(s)</source>
</trans-unit>
</group>
- <trans-unit id="_msg818">
+ <trans-unit id="_msg837">
<source xml:space="preserve">not accepted</source>
- <context-group purpose="location"><context context-type="linenumber">172</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">198</context></context-group>
</trans-unit>
- <trans-unit id="_msg819">
+ <trans-unit id="_msg838">
<source xml:space="preserve">Debit</source>
- <context-group purpose="location"><context context-type="linenumber">232</context></context-group>
<context-group purpose="location"><context context-type="linenumber">258</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">321</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">284</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">347</context></context-group>
</trans-unit>
- <trans-unit id="_msg820">
+ <trans-unit id="_msg839">
<source xml:space="preserve">Total debit</source>
- <context-group purpose="location"><context context-type="linenumber">242</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">268</context></context-group>
</trans-unit>
- <trans-unit id="_msg821">
+ <trans-unit id="_msg840">
<source xml:space="preserve">Total credit</source>
- <context-group purpose="location"><context context-type="linenumber">243</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">269</context></context-group>
</trans-unit>
- <trans-unit id="_msg822">
+ <trans-unit id="_msg841">
<source xml:space="preserve">Transaction fee</source>
- <context-group purpose="location"><context context-type="linenumber">248</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">274</context></context-group>
</trans-unit>
- <trans-unit id="_msg823">
+ <trans-unit id="_msg842">
<source xml:space="preserve">Net amount</source>
- <context-group purpose="location"><context context-type="linenumber">270</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">296</context></context-group>
</trans-unit>
- <trans-unit id="_msg824">
+ <trans-unit id="_msg843">
<source xml:space="preserve">Message</source>
- <context-group purpose="location"><context context-type="linenumber">276</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">288</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">302</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">314</context></context-group>
</trans-unit>
- <trans-unit id="_msg825">
+ <trans-unit id="_msg844">
<source xml:space="preserve">Comment</source>
- <context-group purpose="location"><context context-type="linenumber">278</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">304</context></context-group>
</trans-unit>
- <trans-unit id="_msg826">
+ <trans-unit id="_msg845">
<source xml:space="preserve">Transaction ID</source>
- <context-group purpose="location"><context context-type="linenumber">280</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">306</context></context-group>
</trans-unit>
- <trans-unit id="_msg827">
+ <trans-unit id="_msg846">
<source xml:space="preserve">Transaction total size</source>
- <context-group purpose="location"><context context-type="linenumber">281</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">307</context></context-group>
</trans-unit>
- <trans-unit id="_msg828">
+ <trans-unit id="_msg847">
<source xml:space="preserve">Transaction virtual size</source>
- <context-group purpose="location"><context context-type="linenumber">282</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">308</context></context-group>
</trans-unit>
- <trans-unit id="_msg829">
+ <trans-unit id="_msg848">
<source xml:space="preserve">Output index</source>
- <context-group purpose="location"><context context-type="linenumber">283</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">309</context></context-group>
</trans-unit>
- <trans-unit id="_msg830">
+ <trans-unit id="_msg849">
<source xml:space="preserve"> (Certificate was not verified)</source>
- <context-group purpose="location"><context context-type="linenumber">299</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">325</context></context-group>
</trans-unit>
- <trans-unit id="_msg831">
+ <trans-unit id="_msg850">
<source xml:space="preserve">Merchant</source>
- <context-group purpose="location"><context context-type="linenumber">302</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">328</context></context-group>
</trans-unit>
- <trans-unit id="_msg832">
+ <trans-unit id="_msg851">
<source xml:space="preserve">Generated coins must mature %1 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to &quot;not accepted&quot; and it won&apos;t be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.</source>
- <context-group purpose="location"><context context-type="linenumber">310</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">336</context></context-group>
</trans-unit>
- <trans-unit id="_msg833">
+ <trans-unit id="_msg852">
<source xml:space="preserve">Debug information</source>
- <context-group purpose="location"><context context-type="linenumber">318</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">344</context></context-group>
</trans-unit>
- <trans-unit id="_msg834">
+ <trans-unit id="_msg853">
<source xml:space="preserve">Transaction</source>
- <context-group purpose="location"><context context-type="linenumber">326</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">352</context></context-group>
</trans-unit>
- <trans-unit id="_msg835">
+ <trans-unit id="_msg854">
<source xml:space="preserve">Inputs</source>
- <context-group purpose="location"><context context-type="linenumber">329</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">355</context></context-group>
</trans-unit>
- <trans-unit id="_msg836">
+ <trans-unit id="_msg855">
<source xml:space="preserve">Amount</source>
- <context-group purpose="location"><context context-type="linenumber">350</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">376</context></context-group>
</trans-unit>
- <trans-unit id="_msg837">
+ <trans-unit id="_msg856">
<source xml:space="preserve">true</source>
- <context-group purpose="location"><context context-type="linenumber">351</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">352</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">377</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">378</context></context-group>
</trans-unit>
- <trans-unit id="_msg838">
+ <trans-unit id="_msg857">
<source xml:space="preserve">false</source>
- <context-group purpose="location"><context context-type="linenumber">351</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">352</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">377</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">378</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../forms/transactiondescdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="TransactionDescDialog">
- <trans-unit id="_msg839">
+ <trans-unit id="_msg858">
<source xml:space="preserve">This pane shows a detailed description of the transaction</source>
<context-group purpose="location"><context context-type="linenumber">20</context></context-group>
</trans-unit>
@@ -3834,7 +3954,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
</body></file>
<file original="../transactiondescdialog.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="TransactionDescDialog">
- <trans-unit id="_msg840">
+ <trans-unit id="_msg859">
<source xml:space="preserve">Details for %1</source>
<context-group purpose="location"><context context-type="linenumber">18</context></context-group>
</trans-unit>
@@ -3842,266 +3962,266 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
</body></file>
<file original="../transactiontablemodel.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="TransactionTableModel">
- <trans-unit id="_msg841">
+ <trans-unit id="_msg860">
<source xml:space="preserve">Date</source>
- <context-group purpose="location"><context context-type="linenumber">260</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">261</context></context-group>
</trans-unit>
- <trans-unit id="_msg842">
+ <trans-unit id="_msg861">
<source xml:space="preserve">Type</source>
- <context-group purpose="location"><context context-type="linenumber">260</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">261</context></context-group>
</trans-unit>
- <trans-unit id="_msg843">
+ <trans-unit id="_msg862">
<source xml:space="preserve">Label</source>
- <context-group purpose="location"><context context-type="linenumber">260</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">261</context></context-group>
</trans-unit>
- <trans-unit id="_msg844">
+ <trans-unit id="_msg863">
<source xml:space="preserve">Unconfirmed</source>
- <context-group purpose="location"><context context-type="linenumber">320</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">321</context></context-group>
</trans-unit>
- <trans-unit id="_msg845">
+ <trans-unit id="_msg864">
<source xml:space="preserve">Abandoned</source>
- <context-group purpose="location"><context context-type="linenumber">323</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">324</context></context-group>
</trans-unit>
- <trans-unit id="_msg846">
+ <trans-unit id="_msg865">
<source xml:space="preserve">Confirming (%1 of %2 recommended confirmations)</source>
- <context-group purpose="location"><context context-type="linenumber">326</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">327</context></context-group>
</trans-unit>
- <trans-unit id="_msg847">
+ <trans-unit id="_msg866">
<source xml:space="preserve">Confirmed (%1 confirmations)</source>
- <context-group purpose="location"><context context-type="linenumber">329</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">330</context></context-group>
</trans-unit>
- <trans-unit id="_msg848">
+ <trans-unit id="_msg867">
<source xml:space="preserve">Conflicted</source>
- <context-group purpose="location"><context context-type="linenumber">332</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">333</context></context-group>
</trans-unit>
- <trans-unit id="_msg849">
+ <trans-unit id="_msg868">
<source xml:space="preserve">Immature (%1 confirmations, will be available after %2)</source>
- <context-group purpose="location"><context context-type="linenumber">335</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">336</context></context-group>
</trans-unit>
- <trans-unit id="_msg850">
+ <trans-unit id="_msg869">
<source xml:space="preserve">Generated but not accepted</source>
- <context-group purpose="location"><context context-type="linenumber">338</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">339</context></context-group>
</trans-unit>
- <trans-unit id="_msg851">
+ <trans-unit id="_msg870">
<source xml:space="preserve">Received with</source>
- <context-group purpose="location"><context context-type="linenumber">377</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">378</context></context-group>
</trans-unit>
- <trans-unit id="_msg852">
+ <trans-unit id="_msg871">
<source xml:space="preserve">Received from</source>
- <context-group purpose="location"><context context-type="linenumber">379</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">380</context></context-group>
</trans-unit>
- <trans-unit id="_msg853">
+ <trans-unit id="_msg872">
<source xml:space="preserve">Sent to</source>
- <context-group purpose="location"><context context-type="linenumber">382</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">383</context></context-group>
</trans-unit>
- <trans-unit id="_msg854">
+ <trans-unit id="_msg873">
<source xml:space="preserve">Payment to yourself</source>
- <context-group purpose="location"><context context-type="linenumber">384</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">385</context></context-group>
</trans-unit>
- <trans-unit id="_msg855">
+ <trans-unit id="_msg874">
<source xml:space="preserve">Mined</source>
- <context-group purpose="location"><context context-type="linenumber">386</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">387</context></context-group>
</trans-unit>
- <trans-unit id="_msg856">
+ <trans-unit id="_msg875">
<source xml:space="preserve">watch-only</source>
- <context-group purpose="location"><context context-type="linenumber">414</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">415</context></context-group>
</trans-unit>
- <trans-unit id="_msg857">
+ <trans-unit id="_msg876">
<source xml:space="preserve">(n/a)</source>
- <context-group purpose="location"><context context-type="linenumber">430</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">431</context></context-group>
</trans-unit>
- <trans-unit id="_msg858">
+ <trans-unit id="_msg877">
<source xml:space="preserve">(no label)</source>
- <context-group purpose="location"><context context-type="linenumber">637</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">638</context></context-group>
</trans-unit>
- <trans-unit id="_msg859">
+ <trans-unit id="_msg878">
<source xml:space="preserve">Transaction status. Hover over this field to show number of confirmations.</source>
- <context-group purpose="location"><context context-type="linenumber">676</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">677</context></context-group>
</trans-unit>
- <trans-unit id="_msg860">
+ <trans-unit id="_msg879">
<source xml:space="preserve">Date and time that the transaction was received.</source>
- <context-group purpose="location"><context context-type="linenumber">678</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">679</context></context-group>
</trans-unit>
- <trans-unit id="_msg861">
+ <trans-unit id="_msg880">
<source xml:space="preserve">Type of transaction.</source>
- <context-group purpose="location"><context context-type="linenumber">680</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">681</context></context-group>
</trans-unit>
- <trans-unit id="_msg862">
+ <trans-unit id="_msg881">
<source xml:space="preserve">Whether or not a watch-only address is involved in this transaction.</source>
- <context-group purpose="location"><context context-type="linenumber">682</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">683</context></context-group>
</trans-unit>
- <trans-unit id="_msg863">
+ <trans-unit id="_msg882">
<source xml:space="preserve">User-defined intent/purpose of the transaction.</source>
- <context-group purpose="location"><context context-type="linenumber">684</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">685</context></context-group>
</trans-unit>
- <trans-unit id="_msg864">
+ <trans-unit id="_msg883">
<source xml:space="preserve">Amount removed from or added to balance.</source>
- <context-group purpose="location"><context context-type="linenumber">686</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">687</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../transactionview.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="TransactionView">
- <trans-unit id="_msg865">
+ <trans-unit id="_msg884">
<source xml:space="preserve">All</source>
<context-group purpose="location"><context context-type="linenumber">73</context></context-group>
<context-group purpose="location"><context context-type="linenumber">89</context></context-group>
</trans-unit>
- <trans-unit id="_msg866">
+ <trans-unit id="_msg885">
<source xml:space="preserve">Today</source>
<context-group purpose="location"><context context-type="linenumber">74</context></context-group>
</trans-unit>
- <trans-unit id="_msg867">
+ <trans-unit id="_msg886">
<source xml:space="preserve">This week</source>
<context-group purpose="location"><context context-type="linenumber">75</context></context-group>
</trans-unit>
- <trans-unit id="_msg868">
+ <trans-unit id="_msg887">
<source xml:space="preserve">This month</source>
<context-group purpose="location"><context context-type="linenumber">76</context></context-group>
</trans-unit>
- <trans-unit id="_msg869">
+ <trans-unit id="_msg888">
<source xml:space="preserve">Last month</source>
<context-group purpose="location"><context context-type="linenumber">77</context></context-group>
</trans-unit>
- <trans-unit id="_msg870">
+ <trans-unit id="_msg889">
<source xml:space="preserve">This year</source>
<context-group purpose="location"><context context-type="linenumber">78</context></context-group>
</trans-unit>
- <trans-unit id="_msg871">
+ <trans-unit id="_msg890">
<source xml:space="preserve">Received with</source>
<context-group purpose="location"><context context-type="linenumber">90</context></context-group>
</trans-unit>
- <trans-unit id="_msg872">
+ <trans-unit id="_msg891">
<source xml:space="preserve">Sent to</source>
<context-group purpose="location"><context context-type="linenumber">92</context></context-group>
</trans-unit>
- <trans-unit id="_msg873">
+ <trans-unit id="_msg892">
<source xml:space="preserve">To yourself</source>
<context-group purpose="location"><context context-type="linenumber">94</context></context-group>
</trans-unit>
- <trans-unit id="_msg874">
+ <trans-unit id="_msg893">
<source xml:space="preserve">Mined</source>
<context-group purpose="location"><context context-type="linenumber">95</context></context-group>
</trans-unit>
- <trans-unit id="_msg875">
+ <trans-unit id="_msg894">
<source xml:space="preserve">Other</source>
<context-group purpose="location"><context context-type="linenumber">96</context></context-group>
</trans-unit>
- <trans-unit id="_msg876">
+ <trans-unit id="_msg895">
<source xml:space="preserve">Enter address, transaction id, or label to search</source>
<context-group purpose="location"><context context-type="linenumber">101</context></context-group>
</trans-unit>
- <trans-unit id="_msg877">
+ <trans-unit id="_msg896">
<source xml:space="preserve">Min amount</source>
<context-group purpose="location"><context context-type="linenumber">105</context></context-group>
</trans-unit>
- <trans-unit id="_msg878">
+ <trans-unit id="_msg897">
<source xml:space="preserve">Range…</source>
<context-group purpose="location"><context context-type="linenumber">79</context></context-group>
</trans-unit>
- <trans-unit id="_msg879">
+ <trans-unit id="_msg898">
<source xml:space="preserve">&amp;Copy address</source>
<context-group purpose="location"><context context-type="linenumber">169</context></context-group>
</trans-unit>
- <trans-unit id="_msg880">
+ <trans-unit id="_msg899">
<source xml:space="preserve">Copy &amp;label</source>
<context-group purpose="location"><context context-type="linenumber">170</context></context-group>
</trans-unit>
- <trans-unit id="_msg881">
+ <trans-unit id="_msg900">
<source xml:space="preserve">Copy &amp;amount</source>
<context-group purpose="location"><context context-type="linenumber">171</context></context-group>
</trans-unit>
- <trans-unit id="_msg882">
+ <trans-unit id="_msg901">
<source xml:space="preserve">Copy transaction &amp;ID</source>
<context-group purpose="location"><context context-type="linenumber">172</context></context-group>
</trans-unit>
- <trans-unit id="_msg883">
+ <trans-unit id="_msg902">
<source xml:space="preserve">Copy &amp;raw transaction</source>
<context-group purpose="location"><context context-type="linenumber">173</context></context-group>
</trans-unit>
- <trans-unit id="_msg884">
+ <trans-unit id="_msg903">
<source xml:space="preserve">Copy full transaction &amp;details</source>
<context-group purpose="location"><context context-type="linenumber">174</context></context-group>
</trans-unit>
- <trans-unit id="_msg885">
+ <trans-unit id="_msg904">
<source xml:space="preserve">&amp;Show transaction details</source>
<context-group purpose="location"><context context-type="linenumber">175</context></context-group>
</trans-unit>
- <trans-unit id="_msg886">
+ <trans-unit id="_msg905">
<source xml:space="preserve">Increase transaction &amp;fee</source>
<context-group purpose="location"><context context-type="linenumber">177</context></context-group>
</trans-unit>
- <trans-unit id="_msg887">
+ <trans-unit id="_msg906">
<source xml:space="preserve">A&amp;bandon transaction</source>
<context-group purpose="location"><context context-type="linenumber">180</context></context-group>
</trans-unit>
- <trans-unit id="_msg888">
+ <trans-unit id="_msg907">
<source xml:space="preserve">&amp;Edit address label</source>
<context-group purpose="location"><context context-type="linenumber">181</context></context-group>
</trans-unit>
- <trans-unit id="_msg889">
+ <trans-unit id="_msg908">
<source xml:space="preserve">Show in %1</source>
<context-group purpose="location"><context context-type="linenumber">240</context></context-group>
<note annotates="source" from="developer">Transactions table context menu action to show the selected transaction in a third-party block explorer. %1 is a stand-in argument for the URL of the explorer.</note>
</trans-unit>
- <trans-unit id="_msg890">
+ <trans-unit id="_msg909">
<source xml:space="preserve">Export Transaction History</source>
<context-group purpose="location"><context context-type="linenumber">359</context></context-group>
</trans-unit>
- <trans-unit id="_msg891">
+ <trans-unit id="_msg910">
<source xml:space="preserve">Comma separated file</source>
<context-group purpose="location"><context context-type="linenumber">362</context></context-group>
<note annotates="source" from="developer">Expanded name of the CSV file format. See: https://en.wikipedia.org/wiki/Comma-separated_values.</note>
</trans-unit>
- <trans-unit id="_msg892">
+ <trans-unit id="_msg911">
<source xml:space="preserve">Confirmed</source>
<context-group purpose="location"><context context-type="linenumber">371</context></context-group>
</trans-unit>
- <trans-unit id="_msg893">
+ <trans-unit id="_msg912">
<source xml:space="preserve">Watch-only</source>
<context-group purpose="location"><context context-type="linenumber">373</context></context-group>
</trans-unit>
- <trans-unit id="_msg894">
+ <trans-unit id="_msg913">
<source xml:space="preserve">Date</source>
<context-group purpose="location"><context context-type="linenumber">374</context></context-group>
</trans-unit>
- <trans-unit id="_msg895">
+ <trans-unit id="_msg914">
<source xml:space="preserve">Type</source>
<context-group purpose="location"><context context-type="linenumber">375</context></context-group>
</trans-unit>
- <trans-unit id="_msg896">
+ <trans-unit id="_msg915">
<source xml:space="preserve">Label</source>
<context-group purpose="location"><context context-type="linenumber">376</context></context-group>
</trans-unit>
- <trans-unit id="_msg897">
+ <trans-unit id="_msg916">
<source xml:space="preserve">Address</source>
<context-group purpose="location"><context context-type="linenumber">377</context></context-group>
</trans-unit>
- <trans-unit id="_msg898">
+ <trans-unit id="_msg917">
<source xml:space="preserve">ID</source>
<context-group purpose="location"><context context-type="linenumber">379</context></context-group>
</trans-unit>
- <trans-unit id="_msg899">
+ <trans-unit id="_msg918">
<source xml:space="preserve">Exporting Failed</source>
<context-group purpose="location"><context context-type="linenumber">382</context></context-group>
</trans-unit>
- <trans-unit id="_msg900">
+ <trans-unit id="_msg919">
<source xml:space="preserve">There was an error trying to save the transaction history to %1.</source>
<context-group purpose="location"><context context-type="linenumber">382</context></context-group>
</trans-unit>
- <trans-unit id="_msg901">
+ <trans-unit id="_msg920">
<source xml:space="preserve">Exporting Successful</source>
<context-group purpose="location"><context context-type="linenumber">386</context></context-group>
</trans-unit>
- <trans-unit id="_msg902">
+ <trans-unit id="_msg921">
<source xml:space="preserve">The transaction history was successfully saved to %1.</source>
<context-group purpose="location"><context context-type="linenumber">386</context></context-group>
</trans-unit>
- <trans-unit id="_msg903">
+ <trans-unit id="_msg922">
<source xml:space="preserve">Range:</source>
<context-group purpose="location"><context context-type="linenumber">558</context></context-group>
</trans-unit>
- <trans-unit id="_msg904">
+ <trans-unit id="_msg923">
<source xml:space="preserve">to</source>
<context-group purpose="location"><context context-type="linenumber">566</context></context-group>
</trans-unit>
@@ -4109,810 +4229,936 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of &quot;100
</body></file>
<file original="../walletframe.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="WalletFrame">
- <trans-unit id="_msg905">
+ <trans-unit id="_msg924">
<source xml:space="preserve">No wallet has been loaded.
Go to File &gt; Open Wallet to load a wallet.
- OR -</source>
<context-group purpose="location"><context context-type="linenumber">45</context></context-group>
</trans-unit>
- <trans-unit id="_msg906">
+ <trans-unit id="_msg925">
<source xml:space="preserve">Create a new wallet</source>
<context-group purpose="location"><context context-type="linenumber">50</context></context-group>
</trans-unit>
- <trans-unit id="_msg907">
+ <trans-unit id="_msg926">
<source xml:space="preserve">Error</source>
- <context-group purpose="location"><context context-type="linenumber">204</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">213</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">223</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">201</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">211</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">229</context></context-group>
</trans-unit>
- <trans-unit id="_msg908">
+ <trans-unit id="_msg927">
<source xml:space="preserve">Unable to decode PSBT from clipboard (invalid base64)</source>
- <context-group purpose="location"><context context-type="linenumber">204</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">201</context></context-group>
</trans-unit>
- <trans-unit id="_msg909">
+ <trans-unit id="_msg928">
<source xml:space="preserve">Load Transaction Data</source>
- <context-group purpose="location"><context context-type="linenumber">209</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">207</context></context-group>
</trans-unit>
- <trans-unit id="_msg910">
+ <trans-unit id="_msg929">
<source xml:space="preserve">Partially Signed Transaction (*.psbt)</source>
- <context-group purpose="location"><context context-type="linenumber">210</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">208</context></context-group>
</trans-unit>
- <trans-unit id="_msg911">
+ <trans-unit id="_msg930">
<source xml:space="preserve">PSBT file must be smaller than 100 MiB</source>
- <context-group purpose="location"><context context-type="linenumber">213</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">211</context></context-group>
</trans-unit>
- <trans-unit id="_msg912">
+ <trans-unit id="_msg931">
<source xml:space="preserve">Unable to decode PSBT</source>
- <context-group purpose="location"><context context-type="linenumber">223</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">229</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../walletmodel.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="WalletModel">
- <trans-unit id="_msg913">
+ <trans-unit id="_msg932">
<source xml:space="preserve">Send Coins</source>
- <context-group purpose="location"><context context-type="linenumber">221</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">232</context></context-group>
</trans-unit>
- <trans-unit id="_msg914">
+ <trans-unit id="_msg933">
<source xml:space="preserve">Fee bump error</source>
- <context-group purpose="location"><context context-type="linenumber">481</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">533</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">548</context></context-group>
- <context-group purpose="location"><context context-type="linenumber">553</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">495</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">547</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">562</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">567</context></context-group>
</trans-unit>
- <trans-unit id="_msg915">
+ <trans-unit id="_msg934">
<source xml:space="preserve">Increasing transaction fee failed</source>
- <context-group purpose="location"><context context-type="linenumber">481</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">495</context></context-group>
</trans-unit>
- <trans-unit id="_msg916">
+ <trans-unit id="_msg935">
<source xml:space="preserve">Do you want to increase the fee?</source>
- <context-group purpose="location"><context context-type="linenumber">488</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">502</context></context-group>
<note annotates="source" from="developer">Asks a user if they would like to manually increase the fee of a transaction that has already been created.</note>
</trans-unit>
- <trans-unit id="_msg917">
+ <trans-unit id="_msg936">
<source xml:space="preserve">Current fee:</source>
- <context-group purpose="location"><context context-type="linenumber">492</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">506</context></context-group>
</trans-unit>
- <trans-unit id="_msg918">
+ <trans-unit id="_msg937">
<source xml:space="preserve">Increase:</source>
- <context-group purpose="location"><context context-type="linenumber">496</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">510</context></context-group>
</trans-unit>
- <trans-unit id="_msg919">
+ <trans-unit id="_msg938">
<source xml:space="preserve">New fee:</source>
- <context-group purpose="location"><context context-type="linenumber">500</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">514</context></context-group>
</trans-unit>
- <trans-unit id="_msg920">
+ <trans-unit id="_msg939">
<source xml:space="preserve">Warning: This may 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. These changes may potentially leak privacy.</source>
- <context-group purpose="location"><context context-type="linenumber">508</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">522</context></context-group>
</trans-unit>
- <trans-unit id="_msg921">
+ <trans-unit id="_msg940">
<source xml:space="preserve">Confirm fee bump</source>
- <context-group purpose="location"><context context-type="linenumber">511</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">525</context></context-group>
</trans-unit>
- <trans-unit id="_msg922">
+ <trans-unit id="_msg941">
<source xml:space="preserve">Can&apos;t draft transaction.</source>
- <context-group purpose="location"><context context-type="linenumber">533</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">547</context></context-group>
</trans-unit>
- <trans-unit id="_msg923">
+ <trans-unit id="_msg942">
<source xml:space="preserve">PSBT copied</source>
- <context-group purpose="location"><context context-type="linenumber">540</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">554</context></context-group>
</trans-unit>
- <trans-unit id="_msg924">
+ <trans-unit id="_msg943">
<source xml:space="preserve">Can&apos;t sign transaction.</source>
- <context-group purpose="location"><context context-type="linenumber">548</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">562</context></context-group>
</trans-unit>
- <trans-unit id="_msg925">
+ <trans-unit id="_msg944">
<source xml:space="preserve">Could not commit transaction</source>
- <context-group purpose="location"><context context-type="linenumber">553</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">567</context></context-group>
</trans-unit>
- <trans-unit id="_msg926">
+ <trans-unit id="_msg945">
<source xml:space="preserve">Can&apos;t display address</source>
- <context-group purpose="location"><context context-type="linenumber">567</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">581</context></context-group>
</trans-unit>
- <trans-unit id="_msg927">
+ <trans-unit id="_msg946">
<source xml:space="preserve">default wallet</source>
- <context-group purpose="location"><context context-type="linenumber">585</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">599</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../walletview.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="WalletView">
- <trans-unit id="_msg928">
+ <trans-unit id="_msg947">
<source xml:space="preserve">&amp;Export</source>
- <context-group purpose="location"><context context-type="linenumber">52</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">51</context></context-group>
</trans-unit>
- <trans-unit id="_msg929">
+ <trans-unit id="_msg948">
<source xml:space="preserve">Export the data in the current tab to a file</source>
- <context-group purpose="location"><context context-type="linenumber">53</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">52</context></context-group>
</trans-unit>
- <trans-unit id="_msg930">
+ <trans-unit id="_msg949">
<source xml:space="preserve">Backup Wallet</source>
- <context-group purpose="location"><context context-type="linenumber">217</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">214</context></context-group>
</trans-unit>
- <trans-unit id="_msg931">
+ <trans-unit id="_msg950">
<source xml:space="preserve">Wallet Data</source>
- <context-group purpose="location"><context context-type="linenumber">219</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">216</context></context-group>
<note annotates="source" from="developer">Name of the wallet data file format.</note>
</trans-unit>
- <trans-unit id="_msg932">
+ <trans-unit id="_msg951">
<source xml:space="preserve">Backup Failed</source>
- <context-group purpose="location"><context context-type="linenumber">225</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">222</context></context-group>
</trans-unit>
- <trans-unit id="_msg933">
+ <trans-unit id="_msg952">
<source xml:space="preserve">There was an error trying to save the wallet data to %1.</source>
- <context-group purpose="location"><context context-type="linenumber">225</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">222</context></context-group>
</trans-unit>
- <trans-unit id="_msg934">
+ <trans-unit id="_msg953">
<source xml:space="preserve">Backup Successful</source>
- <context-group purpose="location"><context context-type="linenumber">229</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">226</context></context-group>
</trans-unit>
- <trans-unit id="_msg935">
+ <trans-unit id="_msg954">
<source xml:space="preserve">The wallet data was successfully saved to %1.</source>
- <context-group purpose="location"><context context-type="linenumber">229</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">226</context></context-group>
</trans-unit>
- <trans-unit id="_msg936">
+ <trans-unit id="_msg955">
<source xml:space="preserve">Cancel</source>
- <context-group purpose="location"><context context-type="linenumber">266</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">263</context></context-group>
</trans-unit>
</group>
</body></file>
<file original="../bitcoinstrings.cpp" datatype="cpp" source-language="en"><body>
<group restype="x-trolltech-linguist-context" resname="bitcoin-core">
- <trans-unit id="_msg937">
+ <trans-unit id="_msg956">
<source xml:space="preserve">The %s developers</source>
<context-group purpose="location"><context context-type="linenumber">12</context></context-group>
</trans-unit>
- <trans-unit id="_msg938">
+ <trans-unit id="_msg957">
<source xml:space="preserve">%s corrupt. Try using the wallet tool bitcoin-wallet to salvage or restoring a backup.</source>
<context-group purpose="location"><context context-type="linenumber">13</context></context-group>
</trans-unit>
- <trans-unit id="_msg939">
+ <trans-unit id="_msg958">
<source xml:space="preserve">-maxtxfee is set very high! Fees this large could be paid on a single transaction.</source>
- <context-group purpose="location"><context context-type="linenumber">16</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">20</context></context-group>
</trans-unit>
- <trans-unit id="_msg940">
+ <trans-unit id="_msg959">
<source xml:space="preserve">Cannot downgrade wallet from version %i to version %i. Wallet version unchanged.</source>
- <context-group purpose="location"><context context-type="linenumber">19</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">38</context></context-group>
</trans-unit>
- <trans-unit id="_msg941">
+ <trans-unit id="_msg960">
<source xml:space="preserve">Cannot obtain a lock on data directory %s. %s is probably already running.</source>
- <context-group purpose="location"><context context-type="linenumber">22</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">41</context></context-group>
</trans-unit>
- <trans-unit id="_msg942">
+ <trans-unit id="_msg961">
<source xml:space="preserve">Cannot upgrade a non HD split wallet from version %i to version %i without upgrading to support pre-split keypool. Please use version %i or no version specified.</source>
- <context-group purpose="location"><context context-type="linenumber">27</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">46</context></context-group>
</trans-unit>
- <trans-unit id="_msg943">
+ <trans-unit id="_msg962">
<source xml:space="preserve">Distributed under the MIT software license, see the accompanying file %s or %s</source>
- <context-group purpose="location"><context context-type="linenumber">31</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">50</context></context-group>
</trans-unit>
- <trans-unit id="_msg944">
+ <trans-unit id="_msg963">
<source xml:space="preserve">Error reading %s! All keys read correctly, but transaction data or address book entries might be missing or incorrect.</source>
- <context-group purpose="location"><context context-type="linenumber">37</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">56</context></context-group>
</trans-unit>
- <trans-unit id="_msg945">
+ <trans-unit id="_msg964">
<source xml:space="preserve">Error reading %s! Transaction data may be missing or incorrect. Rescanning wallet.</source>
- <context-group purpose="location"><context context-type="linenumber">40</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">59</context></context-group>
</trans-unit>
- <trans-unit id="_msg946">
+ <trans-unit id="_msg965">
<source xml:space="preserve">Error: Dumpfile format record is incorrect. Got &quot;%s&quot;, expected &quot;format&quot;.</source>
- <context-group purpose="location"><context context-type="linenumber">43</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">65</context></context-group>
</trans-unit>
- <trans-unit id="_msg947">
+ <trans-unit id="_msg966">
<source xml:space="preserve">Error: Dumpfile identifier record is incorrect. Got &quot;%s&quot;, expected &quot;%s&quot;.</source>
- <context-group purpose="location"><context context-type="linenumber">45</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">67</context></context-group>
</trans-unit>
- <trans-unit id="_msg948">
+ <trans-unit id="_msg967">
<source xml:space="preserve">Error: Dumpfile version is not supported. This version of bitcoin-wallet only supports version 1 dumpfiles. Got dumpfile with version %s</source>
- <context-group purpose="location"><context context-type="linenumber">47</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">69</context></context-group>
</trans-unit>
- <trans-unit id="_msg949">
+ <trans-unit id="_msg968">
<source xml:space="preserve">Error: Legacy wallets only support the &quot;legacy&quot;, &quot;p2sh-segwit&quot;, and &quot;bech32&quot; address types</source>
- <context-group purpose="location"><context context-type="linenumber">50</context></context-group>
- </trans-unit>
- <trans-unit id="_msg950">
- <source xml:space="preserve">Error: Listening for incoming connections failed (listen returned error %s)</source>
- <context-group purpose="location"><context context-type="linenumber">53</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">75</context></context-group>
</trans-unit>
- <trans-unit id="_msg951">
+ <trans-unit id="_msg969">
<source xml:space="preserve">Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.</source>
- <context-group purpose="location"><context context-type="linenumber">55</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">87</context></context-group>
</trans-unit>
- <trans-unit id="_msg952">
+ <trans-unit id="_msg970">
<source xml:space="preserve">File %s already exists. If you are sure this is what you want, move it out of the way first.</source>
- <context-group purpose="location"><context context-type="linenumber">58</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">90</context></context-group>
</trans-unit>
- <trans-unit id="_msg953">
+ <trans-unit id="_msg971">
<source xml:space="preserve">Invalid amount for -maxtxfee=&lt;amount&gt;: &apos;%s&apos; (must be at least the minrelay fee of %s to prevent stuck transactions)</source>
- <context-group purpose="location"><context context-type="linenumber">61</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">96</context></context-group>
</trans-unit>
- <trans-unit id="_msg954">
+ <trans-unit id="_msg972">
<source xml:space="preserve">Invalid or corrupt peers.dat (%s). If you believe this is a bug, please report it to %s. As a workaround, you can move the file (%s) out of the way (rename, move, or delete) to have a new one created on the next start.</source>
- <context-group purpose="location"><context context-type="linenumber">64</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">99</context></context-group>
</trans-unit>
- <trans-unit id="_msg955">
+ <trans-unit id="_msg973">
<source xml:space="preserve">More than one onion bind address is provided. Using %s for the automatically created Tor onion service.</source>
- <context-group purpose="location"><context context-type="linenumber">68</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">103</context></context-group>
</trans-unit>
- <trans-unit id="_msg956">
+ <trans-unit id="_msg974">
<source xml:space="preserve">No dump file provided. To use createfromdump, -dumpfile=&lt;filename&gt; must be provided.</source>
- <context-group purpose="location"><context context-type="linenumber">71</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">106</context></context-group>
</trans-unit>
- <trans-unit id="_msg957">
+ <trans-unit id="_msg975">
<source xml:space="preserve">No dump file provided. To use dump, -dumpfile=&lt;filename&gt; must be provided.</source>
- <context-group purpose="location"><context context-type="linenumber">74</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">109</context></context-group>
</trans-unit>
- <trans-unit id="_msg958">
+ <trans-unit id="_msg976">
<source xml:space="preserve">No wallet file format provided. To use createfromdump, -format=&lt;format&gt; must be provided.</source>
- <context-group purpose="location"><context context-type="linenumber">76</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">111</context></context-group>
</trans-unit>
- <trans-unit id="_msg959">
+ <trans-unit id="_msg977">
<source xml:space="preserve">Please check that your computer&apos;s date and time are correct! If your clock is wrong, %s will not work properly.</source>
- <context-group purpose="location"><context context-type="linenumber">79</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">121</context></context-group>
</trans-unit>
- <trans-unit id="_msg960">
+ <trans-unit id="_msg978">
<source xml:space="preserve">Please contribute if you find %s useful. Visit %s for further information about the software.</source>
- <context-group purpose="location"><context context-type="linenumber">82</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">124</context></context-group>
</trans-unit>
- <trans-unit id="_msg961">
+ <trans-unit id="_msg979">
<source xml:space="preserve">Prune configured below the minimum of %d MiB. Please use a higher number.</source>
- <context-group purpose="location"><context context-type="linenumber">85</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">127</context></context-group>
</trans-unit>
- <trans-unit id="_msg962">
+ <trans-unit id="_msg980">
+ <source xml:space="preserve">Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead.</source>
+ <context-group purpose="location"><context context-type="linenumber">129</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg981">
<source xml:space="preserve">Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)</source>
- <context-group purpose="location"><context context-type="linenumber">87</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">132</context></context-group>
</trans-unit>
- <trans-unit id="_msg963">
+ <trans-unit id="_msg982">
<source xml:space="preserve">SQLiteDatabase: Unknown sqlite wallet schema version %d. Only version %d is supported</source>
- <context-group purpose="location"><context context-type="linenumber">90</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">135</context></context-group>
</trans-unit>
- <trans-unit id="_msg964">
+ <trans-unit id="_msg983">
<source xml:space="preserve">The block database contains a block which appears to be from the future. This may be due to your computer&apos;s date and time being set incorrectly. Only rebuild the block database if you are sure that your computer&apos;s date and time are correct</source>
- <context-group purpose="location"><context context-type="linenumber">96</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">141</context></context-group>
</trans-unit>
- <trans-unit id="_msg965">
+ <trans-unit id="_msg984">
<source xml:space="preserve">The block index db contains a legacy &apos;txindex&apos;. To clear the occupied disk space, run a full -reindex, otherwise ignore this error. This error message will not be displayed again.</source>
- <context-group purpose="location"><context context-type="linenumber">101</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">146</context></context-group>
</trans-unit>
- <trans-unit id="_msg966">
+ <trans-unit id="_msg985">
<source xml:space="preserve">The transaction amount is too small to send after the fee has been deducted</source>
- <context-group purpose="location"><context context-type="linenumber">105</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">150</context></context-group>
</trans-unit>
- <trans-unit id="_msg967">
+ <trans-unit id="_msg986">
<source xml:space="preserve">This error could occur if this wallet was not shutdown cleanly and was last loaded using a build with a newer version of Berkeley DB. If so, please use the software that last loaded this wallet</source>
- <context-group purpose="location"><context context-type="linenumber">107</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">152</context></context-group>
</trans-unit>
- <trans-unit id="_msg968">
+ <trans-unit id="_msg987">
<source xml:space="preserve">This is a pre-release test build - use at your own risk - do not use for mining or merchant applications</source>
- <context-group purpose="location"><context context-type="linenumber">111</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">156</context></context-group>
</trans-unit>
- <trans-unit id="_msg969">
+ <trans-unit id="_msg988">
<source xml:space="preserve">This is the maximum transaction fee you pay (in addition to the normal fee) to prioritize partial spend avoidance over regular coin selection.</source>
- <context-group purpose="location"><context context-type="linenumber">114</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">159</context></context-group>
</trans-unit>
- <trans-unit id="_msg970">
+ <trans-unit id="_msg989">
<source xml:space="preserve">This is the transaction fee you may discard if change is smaller than dust at this level</source>
- <context-group purpose="location"><context context-type="linenumber">117</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">162</context></context-group>
</trans-unit>
- <trans-unit id="_msg971">
+ <trans-unit id="_msg990">
<source xml:space="preserve">This is the transaction fee you may pay when fee estimates are not available.</source>
- <context-group purpose="location"><context context-type="linenumber">120</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">165</context></context-group>
</trans-unit>
- <trans-unit id="_msg972">
+ <trans-unit id="_msg991">
<source xml:space="preserve">Total length of network version string (%i) exceeds maximum length (%i). Reduce the number or size of uacomments.</source>
- <context-group purpose="location"><context context-type="linenumber">122</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">167</context></context-group>
</trans-unit>
- <trans-unit id="_msg973">
+ <trans-unit id="_msg992">
<source xml:space="preserve">Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.</source>
- <context-group purpose="location"><context context-type="linenumber">125</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">170</context></context-group>
</trans-unit>
- <trans-unit id="_msg974">
+ <trans-unit id="_msg993">
<source xml:space="preserve">Unknown wallet file format &quot;%s&quot; provided. Please provide one of &quot;bdb&quot; or &quot;sqlite&quot;.</source>
- <context-group purpose="location"><context context-type="linenumber">128</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">173</context></context-group>
</trans-unit>
- <trans-unit id="_msg975">
+ <trans-unit id="_msg994">
+ <source xml:space="preserve">Unsupported chainstate database format found. Please restart with -reindex-chainstate. This will rebuild the chainstate database.</source>
+ <context-group purpose="location"><context context-type="linenumber">184</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg995">
+ <source xml:space="preserve">Wallet created successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future.</source>
+ <context-group purpose="location"><context context-type="linenumber">187</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg996">
<source xml:space="preserve">Warning: Dumpfile wallet format &quot;%s&quot; does not match command line specified format &quot;%s&quot;.</source>
- <context-group purpose="location"><context context-type="linenumber">131</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">191</context></context-group>
</trans-unit>
- <trans-unit id="_msg976">
+ <trans-unit id="_msg997">
<source xml:space="preserve">Warning: Private keys detected in wallet {%s} with disabled private keys</source>
- <context-group purpose="location"><context context-type="linenumber">134</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">194</context></context-group>
</trans-unit>
- <trans-unit id="_msg977">
+ <trans-unit id="_msg998">
<source xml:space="preserve">Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade.</source>
- <context-group purpose="location"><context context-type="linenumber">136</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">196</context></context-group>
</trans-unit>
- <trans-unit id="_msg978">
+ <trans-unit id="_msg999">
<source xml:space="preserve">Witness data for blocks after height %d requires validation. Please restart with -reindex.</source>
- <context-group purpose="location"><context context-type="linenumber">139</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">199</context></context-group>
</trans-unit>
- <trans-unit id="_msg979">
+ <trans-unit id="_msg1000">
<source xml:space="preserve">You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain</source>
- <context-group purpose="location"><context context-type="linenumber">142</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">202</context></context-group>
</trans-unit>
- <trans-unit id="_msg980">
+ <trans-unit id="_msg1001">
<source xml:space="preserve">%s is set very high!</source>
- <context-group purpose="location"><context context-type="linenumber">145</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">211</context></context-group>
</trans-unit>
- <trans-unit id="_msg981">
+ <trans-unit id="_msg1002">
<source xml:space="preserve">-maxmempool must be at least %d MB</source>
- <context-group purpose="location"><context context-type="linenumber">146</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">212</context></context-group>
</trans-unit>
- <trans-unit id="_msg982">
+ <trans-unit id="_msg1003">
<source xml:space="preserve">A fatal internal error occurred, see debug.log for details</source>
- <context-group purpose="location"><context context-type="linenumber">147</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">213</context></context-group>
</trans-unit>
- <trans-unit id="_msg983">
+ <trans-unit id="_msg1004">
<source xml:space="preserve">Cannot resolve -%s address: &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">148</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">214</context></context-group>
</trans-unit>
- <trans-unit id="_msg984">
+ <trans-unit id="_msg1005">
<source xml:space="preserve">Cannot set -forcednsseed to true when setting -dnsseed to false.</source>
- <context-group purpose="location"><context context-type="linenumber">149</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">215</context></context-group>
</trans-unit>
- <trans-unit id="_msg985">
+ <trans-unit id="_msg1006">
<source xml:space="preserve">Cannot set -peerblockfilters without -blockfilterindex.</source>
- <context-group purpose="location"><context context-type="linenumber">150</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">216</context></context-group>
</trans-unit>
- <trans-unit id="_msg986">
+ <trans-unit id="_msg1007">
<source xml:space="preserve">Cannot write to data directory &apos;%s&apos;; check permissions.</source>
- <context-group purpose="location"><context context-type="linenumber">151</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">217</context></context-group>
</trans-unit>
- <trans-unit id="_msg987">
+ <trans-unit id="_msg1008">
<source xml:space="preserve">The -txindex upgrade started by a previous version cannot be completed. Restart with the previous version or run a full -reindex.</source>
- <context-group purpose="location"><context context-type="linenumber">93</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">138</context></context-group>
</trans-unit>
- <trans-unit id="_msg988">
+ <trans-unit id="_msg1009">
+ <source xml:space="preserve">%s request to listen on port %u. This port is considered &quot;bad&quot; and thus it is unlikely that any Bitcoin Core peers connect to it. See doc/p2p-bad-ports.md for details and a full list.</source>
+ <context-group purpose="location"><context context-type="linenumber">16</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1010">
+ <source xml:space="preserve">-reindex-chainstate option is not compatible with -blockfilterindex. Please temporarily disable blockfilterindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.</source>
+ <context-group purpose="location"><context context-type="linenumber">23</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1011">
+ <source xml:space="preserve">-reindex-chainstate option is not compatible with -coinstatsindex. Please temporarily disable coinstatsindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.</source>
+ <context-group purpose="location"><context context-type="linenumber">27</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1012">
+ <source xml:space="preserve">-reindex-chainstate option is not compatible with -txindex. Please temporarily disable txindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.</source>
+ <context-group purpose="location"><context context-type="linenumber">31</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1013">
+ <source xml:space="preserve">Assumed-valid: last wallet synchronisation goes beyond available block data. You need to wait for the background validation chain to download more blocks.</source>
+ <context-group purpose="location"><context context-type="linenumber">35</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1014">
<source xml:space="preserve">Cannot provide specific connections and have addrman find outgoing connections at the same time.</source>
- <context-group purpose="location"><context context-type="linenumber">24</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">43</context></context-group>
</trans-unit>
- <trans-unit id="_msg989">
+ <trans-unit id="_msg1015">
<source xml:space="preserve">Error loading %s: External signer wallet being loaded without external signer support compiled</source>
- <context-group purpose="location"><context context-type="linenumber">34</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">53</context></context-group>
</trans-unit>
- <trans-unit id="_msg990">
+ <trans-unit id="_msg1016">
+ <source xml:space="preserve">Error: Address book data in wallet cannot be identified to belong to migrated wallets</source>
+ <context-group purpose="location"><context context-type="linenumber">62</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1017">
+ <source xml:space="preserve">Error: Duplicate descriptors created during migration. Your wallet may be corrupted.</source>
+ <context-group purpose="location"><context context-type="linenumber">72</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1018">
+ <source xml:space="preserve">Error: Transaction %s in wallet cannot be identified to belong to migrated wallets</source>
+ <context-group purpose="location"><context context-type="linenumber">78</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1019">
+ <source xml:space="preserve">Error: Unable to produce descriptors for this legacy wallet. Make sure the wallet is unlocked first</source>
+ <context-group purpose="location"><context context-type="linenumber">81</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1020">
+ <source xml:space="preserve">Failed to rename invalid peers.dat file. Please move or delete it and try again.</source>
+ <context-group purpose="location"><context context-type="linenumber">84</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1021">
+ <source xml:space="preserve">Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6</source>
+ <context-group purpose="location"><context context-type="linenumber">93</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1022">
+ <source xml:space="preserve">Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is explicitly forbidden: -onion=0</source>
+ <context-group purpose="location"><context context-type="linenumber">114</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1023">
+ <source xml:space="preserve">Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is not provided: none of -proxy, -onion or -listenonion is given</source>
+ <context-group purpose="location"><context context-type="linenumber">117</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1024">
+ <source xml:space="preserve">Unrecognized descriptor found. Loading wallet %s
+
+The wallet might had been created on a newer version.
+Please try running the latest software version.
+</source>
+ <context-group purpose="location"><context context-type="linenumber">176</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1025">
+ <source xml:space="preserve">Unsupported category-specific logging level -loglevel=%s. Expected -loglevel=&lt;category&gt;:&lt;loglevel&gt;. Valid categories: %s. Valid loglevels: %s.</source>
+ <context-group purpose="location"><context context-type="linenumber">181</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1026">
+ <source xml:space="preserve">
+Unable to cleanup failed migration</source>
+ <context-group purpose="location"><context context-type="linenumber">205</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1027">
+ <source xml:space="preserve">
+Unable to restore backup of wallet.</source>
+ <context-group purpose="location"><context context-type="linenumber">208</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1028">
<source xml:space="preserve">Config setting for %s only applied on %s network when in [%s] section.</source>
- <context-group purpose="location"><context context-type="linenumber">152</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">218</context></context-group>
</trans-unit>
- <trans-unit id="_msg991">
+ <trans-unit id="_msg1029">
<source xml:space="preserve">Copyright (C) %i-%i</source>
- <context-group purpose="location"><context context-type="linenumber">153</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">219</context></context-group>
</trans-unit>
- <trans-unit id="_msg992">
+ <trans-unit id="_msg1030">
<source xml:space="preserve">Corrupted block database detected</source>
- <context-group purpose="location"><context context-type="linenumber">154</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">220</context></context-group>
</trans-unit>
- <trans-unit id="_msg993">
+ <trans-unit id="_msg1031">
<source xml:space="preserve">Could not find asmap file %s</source>
- <context-group purpose="location"><context context-type="linenumber">155</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">221</context></context-group>
</trans-unit>
- <trans-unit id="_msg994">
+ <trans-unit id="_msg1032">
<source xml:space="preserve">Could not parse asmap file %s</source>
- <context-group purpose="location"><context context-type="linenumber">156</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">222</context></context-group>
</trans-unit>
- <trans-unit id="_msg995">
+ <trans-unit id="_msg1033">
<source xml:space="preserve">Disk space is too low!</source>
- <context-group purpose="location"><context context-type="linenumber">157</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">223</context></context-group>
</trans-unit>
- <trans-unit id="_msg996">
+ <trans-unit id="_msg1034">
<source xml:space="preserve">Do you want to rebuild the block database now?</source>
- <context-group purpose="location"><context context-type="linenumber">158</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">224</context></context-group>
</trans-unit>
- <trans-unit id="_msg997">
+ <trans-unit id="_msg1035">
<source xml:space="preserve">Done loading</source>
- <context-group purpose="location"><context context-type="linenumber">159</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">225</context></context-group>
</trans-unit>
- <trans-unit id="_msg998">
+ <trans-unit id="_msg1036">
<source xml:space="preserve">Dump file %s does not exist.</source>
- <context-group purpose="location"><context context-type="linenumber">160</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">226</context></context-group>
</trans-unit>
- <trans-unit id="_msg999">
+ <trans-unit id="_msg1037">
<source xml:space="preserve">Error creating %s</source>
- <context-group purpose="location"><context context-type="linenumber">161</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">227</context></context-group>
</trans-unit>
- <trans-unit id="_msg1000">
+ <trans-unit id="_msg1038">
<source xml:space="preserve">Error initializing block database</source>
- <context-group purpose="location"><context context-type="linenumber">162</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">228</context></context-group>
</trans-unit>
- <trans-unit id="_msg1001">
+ <trans-unit id="_msg1039">
<source xml:space="preserve">Error initializing wallet database environment %s!</source>
- <context-group purpose="location"><context context-type="linenumber">163</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">229</context></context-group>
</trans-unit>
- <trans-unit id="_msg1002">
+ <trans-unit id="_msg1040">
<source xml:space="preserve">Error loading %s</source>
- <context-group purpose="location"><context context-type="linenumber">164</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">230</context></context-group>
</trans-unit>
- <trans-unit id="_msg1003">
+ <trans-unit id="_msg1041">
<source xml:space="preserve">Error loading %s: Private keys can only be disabled during creation</source>
- <context-group purpose="location"><context context-type="linenumber">165</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">231</context></context-group>
</trans-unit>
- <trans-unit id="_msg1004">
+ <trans-unit id="_msg1042">
<source xml:space="preserve">Error loading %s: Wallet corrupted</source>
- <context-group purpose="location"><context context-type="linenumber">166</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">232</context></context-group>
</trans-unit>
- <trans-unit id="_msg1005">
+ <trans-unit id="_msg1043">
<source xml:space="preserve">Error loading %s: Wallet requires newer version of %s</source>
- <context-group purpose="location"><context context-type="linenumber">167</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">233</context></context-group>
</trans-unit>
- <trans-unit id="_msg1006">
+ <trans-unit id="_msg1044">
<source xml:space="preserve">Error loading block database</source>
- <context-group purpose="location"><context context-type="linenumber">168</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">234</context></context-group>
</trans-unit>
- <trans-unit id="_msg1007">
+ <trans-unit id="_msg1045">
<source xml:space="preserve">Error opening block database</source>
- <context-group purpose="location"><context context-type="linenumber">169</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">235</context></context-group>
</trans-unit>
- <trans-unit id="_msg1008">
+ <trans-unit id="_msg1046">
<source xml:space="preserve">Error reading from database, shutting down.</source>
- <context-group purpose="location"><context context-type="linenumber">170</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">236</context></context-group>
</trans-unit>
- <trans-unit id="_msg1009">
+ <trans-unit id="_msg1047">
<source xml:space="preserve">Error reading next record from wallet database</source>
- <context-group purpose="location"><context context-type="linenumber">171</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">237</context></context-group>
</trans-unit>
- <trans-unit id="_msg1010">
- <source xml:space="preserve">Error upgrading chainstate database</source>
- <context-group purpose="location"><context context-type="linenumber">172</context></context-group>
+ <trans-unit id="_msg1048">
+ <source xml:space="preserve">Error: Could not add watchonly tx to watchonly wallet</source>
+ <context-group purpose="location"><context context-type="linenumber">238</context></context-group>
</trans-unit>
- <trans-unit id="_msg1011">
+ <trans-unit id="_msg1049">
+ <source xml:space="preserve">Error: Could not delete watchonly transactions</source>
+ <context-group purpose="location"><context context-type="linenumber">239</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1050">
<source xml:space="preserve">Error: Couldn&apos;t create cursor into database</source>
- <context-group purpose="location"><context context-type="linenumber">173</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">240</context></context-group>
</trans-unit>
- <trans-unit id="_msg1012">
+ <trans-unit id="_msg1051">
<source xml:space="preserve">Error: Disk space is low for %s</source>
- <context-group purpose="location"><context context-type="linenumber">174</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">241</context></context-group>
</trans-unit>
- <trans-unit id="_msg1013">
+ <trans-unit id="_msg1052">
<source xml:space="preserve">Error: Dumpfile checksum does not match. Computed %s, expected %s</source>
- <context-group purpose="location"><context context-type="linenumber">175</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">242</context></context-group>
</trans-unit>
- <trans-unit id="_msg1014">
+ <trans-unit id="_msg1053">
+ <source xml:space="preserve">Error: Failed to create new watchonly wallet</source>
+ <context-group purpose="location"><context context-type="linenumber">243</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1054">
<source xml:space="preserve">Error: Got key that was not hex: %s</source>
- <context-group purpose="location"><context context-type="linenumber">176</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">244</context></context-group>
</trans-unit>
- <trans-unit id="_msg1015">
+ <trans-unit id="_msg1055">
<source xml:space="preserve">Error: Got value that was not hex: %s</source>
- <context-group purpose="location"><context context-type="linenumber">177</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">245</context></context-group>
</trans-unit>
- <trans-unit id="_msg1016">
+ <trans-unit id="_msg1056">
<source xml:space="preserve">Error: Keypool ran out, please call keypoolrefill first</source>
- <context-group purpose="location"><context context-type="linenumber">178</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">246</context></context-group>
</trans-unit>
- <trans-unit id="_msg1017">
+ <trans-unit id="_msg1057">
<source xml:space="preserve">Error: Missing checksum</source>
- <context-group purpose="location"><context context-type="linenumber">179</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">247</context></context-group>
</trans-unit>
- <trans-unit id="_msg1018">
+ <trans-unit id="_msg1058">
<source xml:space="preserve">Error: No %s addresses available.</source>
- <context-group purpose="location"><context context-type="linenumber">180</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">248</context></context-group>
</trans-unit>
- <trans-unit id="_msg1019">
+ <trans-unit id="_msg1059">
+ <source xml:space="preserve">Error: Not all watchonly txs could be deleted</source>
+ <context-group purpose="location"><context context-type="linenumber">249</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1060">
+ <source xml:space="preserve">Error: This wallet already uses SQLite</source>
+ <context-group purpose="location"><context context-type="linenumber">250</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1061">
+ <source xml:space="preserve">Error: This wallet is already a descriptor wallet</source>
+ <context-group purpose="location"><context context-type="linenumber">251</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1062">
+ <source xml:space="preserve">Error: Unable to begin reading all records in the database</source>
+ <context-group purpose="location"><context context-type="linenumber">252</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1063">
+ <source xml:space="preserve">Error: Unable to make a backup of your wallet</source>
+ <context-group purpose="location"><context context-type="linenumber">253</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1064">
<source xml:space="preserve">Error: Unable to parse version %u as a uint32_t</source>
- <context-group purpose="location"><context context-type="linenumber">181</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">254</context></context-group>
</trans-unit>
- <trans-unit id="_msg1020">
+ <trans-unit id="_msg1065">
+ <source xml:space="preserve">Error: Unable to read all records in the database</source>
+ <context-group purpose="location"><context context-type="linenumber">255</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1066">
+ <source xml:space="preserve">Error: Unable to remove watchonly address book data</source>
+ <context-group purpose="location"><context context-type="linenumber">256</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1067">
<source xml:space="preserve">Error: Unable to write record to new wallet</source>
- <context-group purpose="location"><context context-type="linenumber">182</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">257</context></context-group>
</trans-unit>
- <trans-unit id="_msg1021">
+ <trans-unit id="_msg1068">
<source xml:space="preserve">Failed to listen on any port. Use -listen=0 if you want this.</source>
- <context-group purpose="location"><context context-type="linenumber">183</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">258</context></context-group>
</trans-unit>
- <trans-unit id="_msg1022">
+ <trans-unit id="_msg1069">
<source xml:space="preserve">Failed to rescan the wallet during initialization</source>
- <context-group purpose="location"><context context-type="linenumber">184</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">259</context></context-group>
</trans-unit>
- <trans-unit id="_msg1023">
+ <trans-unit id="_msg1070">
<source xml:space="preserve">Failed to verify database</source>
- <context-group purpose="location"><context context-type="linenumber">185</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">260</context></context-group>
</trans-unit>
- <trans-unit id="_msg1024">
+ <trans-unit id="_msg1071">
<source xml:space="preserve">Fee rate (%s) is lower than the minimum fee rate setting (%s)</source>
- <context-group purpose="location"><context context-type="linenumber">186</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">261</context></context-group>
</trans-unit>
- <trans-unit id="_msg1025">
+ <trans-unit id="_msg1072">
<source xml:space="preserve">Ignoring duplicate -wallet %s.</source>
- <context-group purpose="location"><context context-type="linenumber">187</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">262</context></context-group>
</trans-unit>
- <trans-unit id="_msg1026">
+ <trans-unit id="_msg1073">
<source xml:space="preserve">Importing…</source>
- <context-group purpose="location"><context context-type="linenumber">188</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">263</context></context-group>
</trans-unit>
- <trans-unit id="_msg1027">
+ <trans-unit id="_msg1074">
<source xml:space="preserve">Incorrect or no genesis block found. Wrong datadir for network?</source>
- <context-group purpose="location"><context context-type="linenumber">189</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">264</context></context-group>
</trans-unit>
- <trans-unit id="_msg1028">
+ <trans-unit id="_msg1075">
<source xml:space="preserve">Initialization sanity check failed. %s is shutting down.</source>
- <context-group purpose="location"><context context-type="linenumber">190</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">265</context></context-group>
</trans-unit>
- <trans-unit id="_msg1029">
+ <trans-unit id="_msg1076">
<source xml:space="preserve">Input not found or already spent</source>
- <context-group purpose="location"><context context-type="linenumber">191</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">266</context></context-group>
</trans-unit>
- <trans-unit id="_msg1030">
+ <trans-unit id="_msg1077">
<source xml:space="preserve">Insufficient funds</source>
- <context-group purpose="location"><context context-type="linenumber">192</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">267</context></context-group>
</trans-unit>
- <trans-unit id="_msg1031">
+ <trans-unit id="_msg1078">
<source xml:space="preserve">Invalid -i2psam address or hostname: &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">193</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">268</context></context-group>
</trans-unit>
- <trans-unit id="_msg1032">
+ <trans-unit id="_msg1079">
<source xml:space="preserve">Invalid -onion address or hostname: &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">194</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">269</context></context-group>
</trans-unit>
- <trans-unit id="_msg1033">
+ <trans-unit id="_msg1080">
<source xml:space="preserve">Invalid -proxy address or hostname: &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">195</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">270</context></context-group>
</trans-unit>
- <trans-unit id="_msg1034">
+ <trans-unit id="_msg1081">
<source xml:space="preserve">Invalid P2P permission: &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">196</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">271</context></context-group>
</trans-unit>
- <trans-unit id="_msg1035">
+ <trans-unit id="_msg1082">
<source xml:space="preserve">Invalid amount for -%s=&lt;amount&gt;: &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">197</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">272</context></context-group>
</trans-unit>
- <trans-unit id="_msg1036">
+ <trans-unit id="_msg1083">
<source xml:space="preserve">Invalid amount for -discardfee=&lt;amount&gt;: &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">198</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">273</context></context-group>
</trans-unit>
- <trans-unit id="_msg1037">
+ <trans-unit id="_msg1084">
<source xml:space="preserve">Invalid amount for -fallbackfee=&lt;amount&gt;: &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">199</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">274</context></context-group>
</trans-unit>
- <trans-unit id="_msg1038">
+ <trans-unit id="_msg1085">
<source xml:space="preserve">Invalid amount for -paytxfee=&lt;amount&gt;: &apos;%s&apos; (must be at least %s)</source>
- <context-group purpose="location"><context context-type="linenumber">200</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">275</context></context-group>
</trans-unit>
- <trans-unit id="_msg1039">
+ <trans-unit id="_msg1086">
<source xml:space="preserve">Invalid netmask specified in -whitelist: &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">201</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">276</context></context-group>
</trans-unit>
- <trans-unit id="_msg1040">
+ <trans-unit id="_msg1087">
+ <source xml:space="preserve">Listening for incoming connections failed (listen returned error %s)</source>
+ <context-group purpose="location"><context context-type="linenumber">277</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1088">
<source xml:space="preserve">Loading P2P addresses…</source>
- <context-group purpose="location"><context context-type="linenumber">202</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">278</context></context-group>
</trans-unit>
- <trans-unit id="_msg1041">
+ <trans-unit id="_msg1089">
<source xml:space="preserve">Loading banlist…</source>
- <context-group purpose="location"><context context-type="linenumber">203</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">279</context></context-group>
</trans-unit>
- <trans-unit id="_msg1042">
+ <trans-unit id="_msg1090">
<source xml:space="preserve">Loading block index…</source>
- <context-group purpose="location"><context context-type="linenumber">204</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">280</context></context-group>
</trans-unit>
- <trans-unit id="_msg1043">
+ <trans-unit id="_msg1091">
<source xml:space="preserve">Loading wallet…</source>
- <context-group purpose="location"><context context-type="linenumber">205</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">281</context></context-group>
</trans-unit>
- <trans-unit id="_msg1044">
+ <trans-unit id="_msg1092">
<source xml:space="preserve">Missing amount</source>
- <context-group purpose="location"><context context-type="linenumber">206</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">282</context></context-group>
</trans-unit>
- <trans-unit id="_msg1045">
+ <trans-unit id="_msg1093">
<source xml:space="preserve">Missing solving data for estimating transaction size</source>
- <context-group purpose="location"><context context-type="linenumber">207</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">283</context></context-group>
</trans-unit>
- <trans-unit id="_msg1046">
+ <trans-unit id="_msg1094">
<source xml:space="preserve">Need to specify a port with -whitebind: &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">208</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">284</context></context-group>
</trans-unit>
- <trans-unit id="_msg1047">
+ <trans-unit id="_msg1095">
<source xml:space="preserve">No addresses available</source>
- <context-group purpose="location"><context context-type="linenumber">209</context></context-group>
- </trans-unit>
- <trans-unit id="_msg1048">
- <source xml:space="preserve">No proxy server specified. Use -proxy=&lt;ip&gt; or -proxy=&lt;ip:port&gt;.</source>
- <context-group purpose="location"><context context-type="linenumber">210</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">285</context></context-group>
</trans-unit>
- <trans-unit id="_msg1049">
+ <trans-unit id="_msg1096">
<source xml:space="preserve">Not enough file descriptors available.</source>
- <context-group purpose="location"><context context-type="linenumber">211</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">286</context></context-group>
</trans-unit>
- <trans-unit id="_msg1050">
+ <trans-unit id="_msg1097">
<source xml:space="preserve">Prune cannot be configured with a negative value.</source>
- <context-group purpose="location"><context context-type="linenumber">212</context></context-group>
- </trans-unit>
- <trans-unit id="_msg1051">
- <source xml:space="preserve">Prune mode is incompatible with -coinstatsindex.</source>
- <context-group purpose="location"><context context-type="linenumber">213</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">287</context></context-group>
</trans-unit>
- <trans-unit id="_msg1052">
+ <trans-unit id="_msg1098">
<source xml:space="preserve">Prune mode is incompatible with -txindex.</source>
- <context-group purpose="location"><context context-type="linenumber">214</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">288</context></context-group>
</trans-unit>
- <trans-unit id="_msg1053">
+ <trans-unit id="_msg1099">
<source xml:space="preserve">Pruning blockstore…</source>
- <context-group purpose="location"><context context-type="linenumber">215</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">289</context></context-group>
</trans-unit>
- <trans-unit id="_msg1054">
+ <trans-unit id="_msg1100">
<source xml:space="preserve">Reducing -maxconnections from %d to %d, because of system limitations.</source>
- <context-group purpose="location"><context context-type="linenumber">216</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">290</context></context-group>
</trans-unit>
- <trans-unit id="_msg1055">
+ <trans-unit id="_msg1101">
<source xml:space="preserve">Replaying blocks…</source>
- <context-group purpose="location"><context context-type="linenumber">217</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">291</context></context-group>
</trans-unit>
- <trans-unit id="_msg1056">
+ <trans-unit id="_msg1102">
<source xml:space="preserve">Rescanning…</source>
- <context-group purpose="location"><context context-type="linenumber">218</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">292</context></context-group>
</trans-unit>
- <trans-unit id="_msg1057">
+ <trans-unit id="_msg1103">
<source xml:space="preserve">SQLiteDatabase: Failed to execute statement to verify database: %s</source>
- <context-group purpose="location"><context context-type="linenumber">219</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">293</context></context-group>
</trans-unit>
- <trans-unit id="_msg1058">
+ <trans-unit id="_msg1104">
<source xml:space="preserve">SQLiteDatabase: Failed to prepare statement to verify database: %s</source>
- <context-group purpose="location"><context context-type="linenumber">220</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">294</context></context-group>
</trans-unit>
- <trans-unit id="_msg1059">
+ <trans-unit id="_msg1105">
<source xml:space="preserve">SQLiteDatabase: Failed to read database verification error: %s</source>
- <context-group purpose="location"><context context-type="linenumber">221</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">295</context></context-group>
</trans-unit>
- <trans-unit id="_msg1060">
+ <trans-unit id="_msg1106">
<source xml:space="preserve">SQLiteDatabase: Unexpected application id. Expected %u, got %u</source>
- <context-group purpose="location"><context context-type="linenumber">222</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">296</context></context-group>
</trans-unit>
- <trans-unit id="_msg1061">
+ <trans-unit id="_msg1107">
<source xml:space="preserve">Section [%s] is not recognized.</source>
- <context-group purpose="location"><context context-type="linenumber">223</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">297</context></context-group>
</trans-unit>
- <trans-unit id="_msg1062">
+ <trans-unit id="_msg1108">
<source xml:space="preserve">Signing transaction failed</source>
- <context-group purpose="location"><context context-type="linenumber">224</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">298</context></context-group>
</trans-unit>
- <trans-unit id="_msg1063">
+ <trans-unit id="_msg1109">
<source xml:space="preserve">Specified -walletdir &quot;%s&quot; does not exist</source>
- <context-group purpose="location"><context context-type="linenumber">225</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">299</context></context-group>
</trans-unit>
- <trans-unit id="_msg1064">
+ <trans-unit id="_msg1110">
<source xml:space="preserve">Specified -walletdir &quot;%s&quot; is a relative path</source>
- <context-group purpose="location"><context context-type="linenumber">226</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">300</context></context-group>
</trans-unit>
- <trans-unit id="_msg1065">
+ <trans-unit id="_msg1111">
<source xml:space="preserve">Specified -walletdir &quot;%s&quot; is not a directory</source>
- <context-group purpose="location"><context context-type="linenumber">227</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">301</context></context-group>
</trans-unit>
- <trans-unit id="_msg1066">
+ <trans-unit id="_msg1112">
<source xml:space="preserve">Specified blocks directory &quot;%s&quot; does not exist.</source>
- <context-group purpose="location"><context context-type="linenumber">228</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">302</context></context-group>
</trans-unit>
- <trans-unit id="_msg1067">
+ <trans-unit id="_msg1113">
<source xml:space="preserve">Starting network threads…</source>
- <context-group purpose="location"><context context-type="linenumber">229</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">303</context></context-group>
</trans-unit>
- <trans-unit id="_msg1068">
+ <trans-unit id="_msg1114">
<source xml:space="preserve">The source code is available from %s.</source>
- <context-group purpose="location"><context context-type="linenumber">230</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">304</context></context-group>
</trans-unit>
- <trans-unit id="_msg1069">
+ <trans-unit id="_msg1115">
<source xml:space="preserve">The specified config file %s does not exist</source>
- <context-group purpose="location"><context context-type="linenumber">231</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">305</context></context-group>
</trans-unit>
- <trans-unit id="_msg1070">
+ <trans-unit id="_msg1116">
<source xml:space="preserve">The transaction amount is too small to pay the fee</source>
- <context-group purpose="location"><context context-type="linenumber">232</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">306</context></context-group>
</trans-unit>
- <trans-unit id="_msg1071">
+ <trans-unit id="_msg1117">
<source xml:space="preserve">The wallet will avoid paying less than the minimum relay fee.</source>
- <context-group purpose="location"><context context-type="linenumber">233</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">307</context></context-group>
</trans-unit>
- <trans-unit id="_msg1072">
+ <trans-unit id="_msg1118">
<source xml:space="preserve">This is experimental software.</source>
- <context-group purpose="location"><context context-type="linenumber">234</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">308</context></context-group>
</trans-unit>
- <trans-unit id="_msg1073">
+ <trans-unit id="_msg1119">
<source xml:space="preserve">This is the minimum transaction fee you pay on every transaction.</source>
- <context-group purpose="location"><context context-type="linenumber">235</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">309</context></context-group>
</trans-unit>
- <trans-unit id="_msg1074">
+ <trans-unit id="_msg1120">
<source xml:space="preserve">This is the transaction fee you will pay if you send a transaction.</source>
- <context-group purpose="location"><context context-type="linenumber">236</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">310</context></context-group>
</trans-unit>
- <trans-unit id="_msg1075">
+ <trans-unit id="_msg1121">
<source xml:space="preserve">Transaction amount too small</source>
- <context-group purpose="location"><context context-type="linenumber">237</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">311</context></context-group>
</trans-unit>
- <trans-unit id="_msg1076">
+ <trans-unit id="_msg1122">
<source xml:space="preserve">Transaction amounts must not be negative</source>
- <context-group purpose="location"><context context-type="linenumber">238</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">312</context></context-group>
</trans-unit>
- <trans-unit id="_msg1077">
+ <trans-unit id="_msg1123">
<source xml:space="preserve">Transaction change output index out of range</source>
- <context-group purpose="location"><context context-type="linenumber">239</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">313</context></context-group>
</trans-unit>
- <trans-unit id="_msg1078">
+ <trans-unit id="_msg1124">
<source xml:space="preserve">Transaction has too long of a mempool chain</source>
- <context-group purpose="location"><context context-type="linenumber">240</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">314</context></context-group>
</trans-unit>
- <trans-unit id="_msg1079">
+ <trans-unit id="_msg1125">
<source xml:space="preserve">Transaction must have at least one recipient</source>
- <context-group purpose="location"><context context-type="linenumber">241</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">315</context></context-group>
</trans-unit>
- <trans-unit id="_msg1080">
+ <trans-unit id="_msg1126">
<source xml:space="preserve">Transaction needs a change address, but we can&apos;t generate it.</source>
- <context-group purpose="location"><context context-type="linenumber">242</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">316</context></context-group>
</trans-unit>
- <trans-unit id="_msg1081">
+ <trans-unit id="_msg1127">
<source xml:space="preserve">Transaction too large</source>
- <context-group purpose="location"><context context-type="linenumber">243</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">317</context></context-group>
</trans-unit>
- <trans-unit id="_msg1082">
+ <trans-unit id="_msg1128">
+ <source xml:space="preserve">Unable to allocate memory for -maxsigcachesize: &apos;%s&apos; MiB</source>
+ <context-group purpose="location"><context context-type="linenumber">318</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1129">
<source xml:space="preserve">Unable to bind to %s on this computer (bind returned error %s)</source>
- <context-group purpose="location"><context context-type="linenumber">244</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">319</context></context-group>
</trans-unit>
- <trans-unit id="_msg1083">
+ <trans-unit id="_msg1130">
<source xml:space="preserve">Unable to bind to %s on this computer. %s is probably already running.</source>
- <context-group purpose="location"><context context-type="linenumber">245</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">320</context></context-group>
</trans-unit>
- <trans-unit id="_msg1084">
+ <trans-unit id="_msg1131">
<source xml:space="preserve">Unable to create the PID file &apos;%s&apos;: %s</source>
- <context-group purpose="location"><context context-type="linenumber">246</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">321</context></context-group>
</trans-unit>
- <trans-unit id="_msg1085">
+ <trans-unit id="_msg1132">
+ <source xml:space="preserve">Unable to find UTXO for external input</source>
+ <context-group purpose="location"><context context-type="linenumber">322</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1133">
<source xml:space="preserve">Unable to generate initial keys</source>
- <context-group purpose="location"><context context-type="linenumber">247</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">323</context></context-group>
</trans-unit>
- <trans-unit id="_msg1086">
+ <trans-unit id="_msg1134">
<source xml:space="preserve">Unable to generate keys</source>
- <context-group purpose="location"><context context-type="linenumber">248</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">324</context></context-group>
</trans-unit>
- <trans-unit id="_msg1087">
+ <trans-unit id="_msg1135">
<source xml:space="preserve">Unable to open %s for writing</source>
- <context-group purpose="location"><context context-type="linenumber">249</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">325</context></context-group>
</trans-unit>
- <trans-unit id="_msg1088">
+ <trans-unit id="_msg1136">
<source xml:space="preserve">Unable to parse -maxuploadtarget: &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">250</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">326</context></context-group>
</trans-unit>
- <trans-unit id="_msg1089">
+ <trans-unit id="_msg1137">
<source xml:space="preserve">Unable to start HTTP server. See debug log for details.</source>
- <context-group purpose="location"><context context-type="linenumber">251</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">327</context></context-group>
</trans-unit>
- <trans-unit id="_msg1090">
+ <trans-unit id="_msg1138">
+ <source xml:space="preserve">Unable to unload the wallet before migrating</source>
+ <context-group purpose="location"><context context-type="linenumber">328</context></context-group>
+ </trans-unit>
+ <trans-unit id="_msg1139">
<source xml:space="preserve">Unknown -blockfilterindex value %s.</source>
- <context-group purpose="location"><context context-type="linenumber">252</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">329</context></context-group>
</trans-unit>
- <trans-unit id="_msg1091">
+ <trans-unit id="_msg1140">
<source xml:space="preserve">Unknown address type &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">253</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">330</context></context-group>
</trans-unit>
- <trans-unit id="_msg1092">
+ <trans-unit id="_msg1141">
<source xml:space="preserve">Unknown change type &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">254</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">331</context></context-group>
</trans-unit>
- <trans-unit id="_msg1093">
+ <trans-unit id="_msg1142">
<source xml:space="preserve">Unknown network specified in -onlynet: &apos;%s&apos;</source>
- <context-group purpose="location"><context context-type="linenumber">255</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">332</context></context-group>
</trans-unit>
- <trans-unit id="_msg1094">
+ <trans-unit id="_msg1143">
<source xml:space="preserve">Unknown new rules activated (versionbit %i)</source>
- <context-group purpose="location"><context context-type="linenumber">256</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">333</context></context-group>
</trans-unit>
- <trans-unit id="_msg1095">
- <source xml:space="preserve">Unsupported logging category %s=%s.</source>
- <context-group purpose="location"><context context-type="linenumber">257</context></context-group>
+ <trans-unit id="_msg1144">
+ <source xml:space="preserve">Unsupported global logging level -loglevel=%s. Valid values: %s.</source>
+ <context-group purpose="location"><context context-type="linenumber">334</context></context-group>
</trans-unit>
- <trans-unit id="_msg1096">
- <source xml:space="preserve">Upgrading UTXO database</source>
- <context-group purpose="location"><context context-type="linenumber">258</context></context-group>
+ <trans-unit id="_msg1145">
+ <source xml:space="preserve">Unsupported logging category %s=%s.</source>
+ <context-group purpose="location"><context context-type="linenumber">335</context></context-group>
</trans-unit>
- <trans-unit id="_msg1097">
+ <trans-unit id="_msg1146">
<source xml:space="preserve">User Agent comment (%s) contains unsafe characters.</source>
- <context-group purpose="location"><context context-type="linenumber">259</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">336</context></context-group>
</trans-unit>
- <trans-unit id="_msg1098">
+ <trans-unit id="_msg1147">
<source xml:space="preserve">Verifying blocks…</source>
- <context-group purpose="location"><context context-type="linenumber">260</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">337</context></context-group>
</trans-unit>
- <trans-unit id="_msg1099">
+ <trans-unit id="_msg1148">
<source xml:space="preserve">Verifying wallet(s)…</source>
- <context-group purpose="location"><context context-type="linenumber">261</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">338</context></context-group>
</trans-unit>
- <trans-unit id="_msg1100">
+ <trans-unit id="_msg1149">
<source xml:space="preserve">Wallet needed to be rewritten: restart %s to complete</source>
- <context-group purpose="location"><context context-type="linenumber">262</context></context-group>
+ <context-group purpose="location"><context context-type="linenumber">339</context></context-group>
</trans-unit>
</group>
</body></file>
diff --git a/src/qt/main.cpp b/src/qt/main.cpp
index e8f39584ad..45131a1cf5 100644
--- a/src/qt/main.cpp
+++ b/src/qt/main.cpp
@@ -4,9 +4,9 @@
#include <qt/bitcoin.h>
+#include <common/url.h>
#include <compat/compat.h>
#include <util/translation.h>
-#include <util/url.h>
#include <QCoreApplication>
diff --git a/src/qt/modaloverlay.cpp b/src/qt/modaloverlay.cpp
index 97ee75a31f..dfa33764f6 100644
--- a/src/qt/modaloverlay.cpp
+++ b/src/qt/modaloverlay.cpp
@@ -78,13 +78,16 @@ bool ModalOverlay::event(QEvent* ev) {
return QWidget::event(ev);
}
-void ModalOverlay::setKnownBestHeight(int count, const QDateTime& blockDate)
+void ModalOverlay::setKnownBestHeight(int count, const QDateTime& blockDate, bool presync)
{
- if (count > bestHeaderHeight) {
+ if (!presync && count > bestHeaderHeight) {
bestHeaderHeight = count;
bestHeaderDate = blockDate;
UpdateHeaderSyncLabel();
}
+ if (presync) {
+ UpdateHeaderPresyncLabel(count, blockDate);
+ }
}
void ModalOverlay::tipUpdate(int count, const QDateTime& blockDate, double nVerificationProgress)
@@ -158,6 +161,11 @@ void ModalOverlay::UpdateHeaderSyncLabel() {
ui->numberOfBlocksLeft->setText(tr("Unknown. Syncing Headers (%1, %2%)…").arg(bestHeaderHeight).arg(QString::number(100.0 / (bestHeaderHeight + est_headers_left) * bestHeaderHeight, 'f', 1)));
}
+void ModalOverlay::UpdateHeaderPresyncLabel(int height, const QDateTime& blockDate) {
+ int est_headers_left = blockDate.secsTo(QDateTime::currentDateTime()) / Params().GetConsensus().nPowTargetSpacing;
+ ui->numberOfBlocksLeft->setText(tr("Unknown. Pre-syncing Headers (%1, %2%)…").arg(height).arg(QString::number(100.0 / (height + est_headers_left) * height, 'f', 1)));
+}
+
void ModalOverlay::toggleVisibility()
{
showHide(layerIsVisible, true);
diff --git a/src/qt/modaloverlay.h b/src/qt/modaloverlay.h
index 1d8af5cbf6..682c94cd01 100644
--- a/src/qt/modaloverlay.h
+++ b/src/qt/modaloverlay.h
@@ -26,7 +26,7 @@ public:
~ModalOverlay();
void tipUpdate(int count, const QDateTime& blockDate, double nVerificationProgress);
- void setKnownBestHeight(int count, const QDateTime& blockDate);
+ void setKnownBestHeight(int count, const QDateTime& blockDate, bool presync);
// will show or hide the modal layer
void showHide(bool hide = false, bool userRequested = false);
@@ -52,6 +52,7 @@ private:
bool userClosed;
QPropertyAnimation m_animation;
void UpdateHeaderSyncLabel();
+ void UpdateHeaderPresyncLabel(int height, const QDateTime& blockDate);
};
#endif // BITCOIN_QT_MODALOVERLAY_H
diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp
index 2b6711ca40..7c91eac37e 100644
--- a/src/qt/optionsdialog.cpp
+++ b/src/qt/optionsdialog.cpp
@@ -91,7 +91,9 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) :
ui->thirdPartyTxUrls->setVisible(false);
}
-#ifndef ENABLE_EXTERNAL_SIGNER
+#ifdef ENABLE_EXTERNAL_SIGNER
+ ui->externalSignerPath->setToolTip(ui->externalSignerPath->toolTip().arg(PACKAGE_NAME));
+#else
//: "External signing" means using devices such as hardware wallets.
ui->externalSignerPath->setToolTip(tr("Compiled without external signing support (required for external signing)"));
ui->externalSignerPath->setEnabled(false);
diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp
index a8133f481e..85a3c36f39 100644
--- a/src/qt/overviewpage.cpp
+++ b/src/qt/overviewpage.cpp
@@ -147,8 +147,6 @@ OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent)
{
ui->setupUi(this);
- m_balances.balance = -1;
-
// use a SingleColorIcon for the "out of sync warning" icon
QIcon icon = m_platform_style->SingleColorIcon(QStringLiteral(":/icons/warning"));
ui->labelTransactionsStatus->setIcon(icon);
@@ -177,8 +175,9 @@ void OverviewPage::handleTransactionClicked(const QModelIndex &index)
void OverviewPage::setPrivacy(bool privacy)
{
m_privacy = privacy;
- if (m_balances.balance != -1) {
- setBalance(m_balances);
+ const auto& balances = walletModel->getCachedBalance();
+ if (balances.balance != -1) {
+ setBalance(balances);
}
ui->listTransactions->setVisible(!m_privacy);
@@ -197,7 +196,6 @@ OverviewPage::~OverviewPage()
void OverviewPage::setBalance(const interfaces::WalletBalances& balances)
{
BitcoinUnit unit = walletModel->getOptionsModel()->getDisplayUnit();
- m_balances = balances;
if (walletModel->wallet().isLegacy()) {
if (walletModel->wallet().privateKeysDisabled()) {
ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
@@ -276,14 +274,13 @@ void OverviewPage::setWalletModel(WalletModel *model)
ui->listTransactions->setModelColumn(TransactionTableModel::ToAddress);
// Keep up to date with wallet
- interfaces::Wallet& wallet = model->wallet();
- interfaces::WalletBalances balances = wallet.getBalances();
- setBalance(balances);
+ setBalance(model->getCachedBalance());
connect(model, &WalletModel::balanceChanged, this, &OverviewPage::setBalance);
connect(model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &OverviewPage::updateDisplayUnit);
- updateWatchOnlyLabels(wallet.haveWatchOnly() && !model->wallet().privateKeysDisabled());
+ interfaces::Wallet& wallet = model->wallet();
+ updateWatchOnlyLabels(wallet.haveWatchOnly() && !wallet.privateKeysDisabled());
connect(model, &WalletModel::notifyWatchonlyChanged, [this](bool showWatchOnly) {
updateWatchOnlyLabels(showWatchOnly && !walletModel->wallet().privateKeysDisabled());
});
@@ -306,10 +303,10 @@ void OverviewPage::changeEvent(QEvent* e)
void OverviewPage::updateDisplayUnit()
{
- if(walletModel && walletModel->getOptionsModel())
- {
- if (m_balances.balance != -1) {
- setBalance(m_balances);
+ if (walletModel && walletModel->getOptionsModel()) {
+ const auto& balances = walletModel->getCachedBalance();
+ if (balances.balance != -1) {
+ setBalance(balances);
}
// Update txdelegate->unit with the current unit
diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h
index 058df1a8c5..56f45907db 100644
--- a/src/qt/overviewpage.h
+++ b/src/qt/overviewpage.h
@@ -52,7 +52,6 @@ private:
Ui::OverviewPage *ui;
ClientModel *clientModel;
WalletModel *walletModel;
- interfaces::WalletBalances m_balances;
bool m_privacy{false};
const PlatformStyle* m_platform_style;
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 70fccdef1c..a07686ab2b 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -661,7 +661,7 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_
setNumConnections(model->getNumConnections());
connect(model, &ClientModel::numConnectionsChanged, this, &RPCConsole::setNumConnections);
- setNumBlocks(bestblock_height, QDateTime::fromSecsSinceEpoch(bestblock_date), verification_progress, false);
+ setNumBlocks(bestblock_height, QDateTime::fromSecsSinceEpoch(bestblock_date), verification_progress, SyncType::BLOCK_SYNC);
connect(model, &ClientModel::numBlocksChanged, this, &RPCConsole::setNumBlocks);
updateNetworkState();
@@ -973,9 +973,9 @@ void RPCConsole::setNetworkActive(bool networkActive)
updateNetworkState();
}
-void RPCConsole::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers)
+void RPCConsole::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype)
{
- if (!headers) {
+ if (synctype == SyncType::BLOCK_SYNC) {
ui->numberOfBlocks->setText(QString::number(count));
ui->lastBlockTime->setText(blockDate.toString());
}
@@ -1178,15 +1178,19 @@ void RPCConsole::updateDetailWidget()
ui->peerPingTime->setText(GUIUtil::formatPingTime(stats->nodeStats.m_last_ping_time));
ui->peerMinPing->setText(GUIUtil::formatPingTime(stats->nodeStats.m_min_ping_time));
ui->timeoffset->setText(GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset));
- ui->peerVersion->setText(QString::number(stats->nodeStats.nVersion));
- ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer));
+ if (stats->nodeStats.nVersion) {
+ ui->peerVersion->setText(QString::number(stats->nodeStats.nVersion));
+ }
+ if (!stats->nodeStats.cleanSubVer.empty()) {
+ ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer));
+ }
ui->peerConnectionType->setText(GUIUtil::ConnectionTypeToQString(stats->nodeStats.m_conn_type, /*prepend_direction=*/true));
ui->peerNetwork->setText(GUIUtil::NetworkToQString(stats->nodeStats.m_network));
- if (stats->nodeStats.m_permissionFlags == NetPermissionFlags::None) {
+ if (stats->nodeStats.m_permission_flags == NetPermissionFlags::None) {
ui->peerPermissions->setText(ts.na);
} else {
QStringList permissions;
- for (const auto& permission : NetPermissions::ToStrings(stats->nodeStats.m_permissionFlags)) {
+ for (const auto& permission : NetPermissions::ToStrings(stats->nodeStats.m_permission_flags)) {
permissions.append(QString::fromStdString(permission));
}
ui->peerPermissions->setText(permissions.join(" & "));
diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h
index 1a54fe0cad..a3c713e966 100644
--- a/src/qt/rpcconsole.h
+++ b/src/qt/rpcconsole.h
@@ -9,6 +9,7 @@
#include <config/bitcoin-config.h>
#endif
+#include <qt/clientmodel.h>
#include <qt/guiutil.h>
#include <qt/peertablemodel.h>
@@ -19,7 +20,6 @@
#include <QThread>
#include <QWidget>
-class ClientModel;
class PlatformStyle;
class RPCExecutor;
class RPCTimerInterface;
@@ -121,7 +121,7 @@ public Q_SLOTS:
/** Set network state shown in the UI */
void setNetworkActive(bool networkActive);
/** Set number of blocks and last block date shown in the UI */
- void setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers);
+ void setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype);
/** Set size (number of transactions and memory usage) of the mempool in the UI */
void setMempoolSize(long numberOfTxs, size_t dynUsage);
/** Go forward or back in history */
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index bd44d12781..57094fc857 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -164,11 +164,9 @@ void SendCoinsDialog::setModel(WalletModel *_model)
}
}
- interfaces::WalletBalances balances = _model->wallet().getBalances();
- setBalance(balances);
connect(_model, &WalletModel::balanceChanged, this, &SendCoinsDialog::setBalance);
- connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::updateDisplayUnit);
- updateDisplayUnit();
+ connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::refreshBalance);
+ refreshBalance();
// Coin Control
connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
@@ -291,7 +289,9 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
updateCoinControlState();
- prepareStatus = model->prepareTransaction(*m_current_transaction, *m_coin_control);
+ CCoinControl coin_control = *m_coin_control;
+ coin_control.m_allow_other_inputs = !coin_control.HasSelected(); // future, could introduce a checkbox to customize this value.
+ prepareStatus = model->prepareTransaction(*m_current_transaction, coin_control);
// process prepareStatus and on error generate message shown to user
processSendCoinsReturn(prepareStatus,
@@ -711,9 +711,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances)
}
}
-void SendCoinsDialog::updateDisplayUnit()
+void SendCoinsDialog::refreshBalance()
{
- setBalance(model->wallet().getBalances());
+ setBalance(model->getCachedBalance());
ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
updateSmartFeeLabel();
}
@@ -786,7 +786,7 @@ void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
// Calculate available amount to send.
- CAmount amount = model->wallet().getAvailableBalance(*m_coin_control);
+ CAmount amount = model->getAvailableBalance(m_coin_control.get());
for (int i = 0; i < ui->entries->count(); ++i) {
SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
if (e && !e->isHidden() && e != entry) {
@@ -841,7 +841,7 @@ void SendCoinsDialog::updateCoinControlState()
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
}
-void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {
+void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state) {
if (sync_state == SynchronizationState::POST_INIT) {
updateSmartFeeLabel();
}
diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h
index 400503d0c0..dcdf189532 100644
--- a/src/qt/sendcoinsdialog.h
+++ b/src/qt/sendcoinsdialog.h
@@ -5,6 +5,7 @@
#ifndef BITCOIN_QT_SENDCOINSDIALOG_H
#define BITCOIN_QT_SENDCOINSDIALOG_H
+#include <qt/clientmodel.h>
#include <qt/walletmodel.h>
#include <QDialog>
@@ -12,7 +13,6 @@
#include <QString>
#include <QTimer>
-class ClientModel;
class PlatformStyle;
class SendCoinsEntry;
class SendCoinsRecipient;
@@ -97,7 +97,7 @@ private Q_SLOTS:
void on_buttonMinimizeFee_clicked();
void removeEntry(SendCoinsEntry* entry);
void useAvailableBalance(SendCoinsEntry* entry);
- void updateDisplayUnit();
+ void refreshBalance();
void coinControlFeatureChanged(bool);
void coinControlButtonClicked();
void coinControlChangeChecked(int);
@@ -111,7 +111,7 @@ private Q_SLOTS:
void coinControlClipboardLowOutput();
void coinControlClipboardChange();
void updateFeeSectionControls();
- void updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state);
+ void updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state);
void updateSmartFeeLabel();
Q_SIGNALS:
diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp
index a4dfffa387..bf04a6dd5f 100644
--- a/src/qt/splashscreen.cpp
+++ b/src/qt/splashscreen.cpp
@@ -44,7 +44,7 @@ SplashScreen::SplashScreen(const NetworkStyle* networkStyle)
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();
+ const QString& titleAddText = networkStyle->getTitleAddText();
QString font = QApplication::font().toString();
diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp
index b8ae62ecab..b71dfb0e9f 100644
--- a/src/qt/test/wallettests.cpp
+++ b/src/qt/test/wallettests.cpp
@@ -129,6 +129,13 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st
QVERIFY(text.indexOf(QString::fromStdString(expectError)) != -1);
}
+void CompareBalance(WalletModel& walletModel, CAmount expected_balance, QLabel* balance_label_to_check)
+{
+ BitcoinUnit unit = walletModel.getOptionsModel()->getDisplayUnit();
+ QString balanceComparison = BitcoinUnits::formatWithUnit(unit, expected_balance, false, BitcoinUnits::SeparatorStyle::ALWAYS);
+ QCOMPARE(balance_label_to_check->text().trimmed(), balanceComparison);
+}
+
//! Simple qt wallet tests.
//
// Test widgets can be debugged interactively calling show() on them and
@@ -168,14 +175,14 @@ void TestGUI(interfaces::Node& node)
if (!wallet->AddWalletDescriptor(w_desc, provider, "", false)) assert(false);
CTxDestination dest = GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type);
wallet->SetAddressBook(dest, "", "receive");
- wallet->SetLastBlockProcessed(105, node.context()->chainman->ActiveChain().Tip()->GetBlockHash());
+ wallet->SetLastBlockProcessed(105, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
}
{
WalletRescanReserver reserver(*wallet);
reserver.reserve();
CWallet::ScanResult result = wallet->ScanForWalletTransactions(Params().GetConsensus().hashGenesisBlock, /*start_height=*/0, /*max_height=*/{}, reserver, /*fUpdate=*/true, /*save_progress=*/false);
QCOMPARE(result.status, CWallet::ScanResult::SUCCESS);
- QCOMPARE(result.last_scanned_block, node.context()->chainman->ActiveChain().Tip()->GetBlockHash());
+ QCOMPARE(result.last_scanned_block, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
QVERIFY(result.last_failed_block.IsNull());
}
wallet->SetBroadcastTransactions(true);
@@ -195,15 +202,10 @@ void TestGUI(interfaces::Node& node)
sendCoinsDialog.setModel(&walletModel);
transactionView.setModel(&walletModel);
- {
- // Check balance in send dialog
- QLabel* balanceLabel = sendCoinsDialog.findChild<QLabel*>("labelBalance");
- QString balanceText = balanceLabel->text();
- BitcoinUnit unit = walletModel.getOptionsModel()->getDisplayUnit();
- CAmount balance = walletModel.wallet().getBalance();
- QString balanceComparison = BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::SeparatorStyle::ALWAYS);
- QCOMPARE(balanceText, balanceComparison);
- }
+ // Update walletModel cached balance which will trigger an update for the 'labelBalance' QLabel.
+ walletModel.pollBalanceChanged();
+ // Check balance in send dialog
+ CompareBalance(walletModel, walletModel.wallet().getBalance(), sendCoinsDialog.findChild<QLabel*>("labelBalance"));
// Send two transactions, and verify they are added to transaction list.
TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel();
@@ -223,12 +225,8 @@ void TestGUI(interfaces::Node& node)
// Check current balance on OverviewPage
OverviewPage overviewPage(platformStyle.get());
overviewPage.setWalletModel(&walletModel);
- QLabel* balanceLabel = overviewPage.findChild<QLabel*>("labelBalance");
- QString balanceText = balanceLabel->text().trimmed();
- BitcoinUnit unit = walletModel.getOptionsModel()->getDisplayUnit();
- CAmount balance = walletModel.wallet().getBalance();
- QString balanceComparison = BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::SeparatorStyle::ALWAYS);
- QCOMPARE(balanceText, balanceComparison);
+ walletModel.pollBalanceChanged(); // Manual balance polling update
+ CompareBalance(walletModel, walletModel.wallet().getBalance(), overviewPage.findChild<QLabel*>("labelBalance"));
// Check Request Payment button
ReceiveCoinsDialog receiveCoinsDialog(platformStyle.get());
diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp
index 4894cac905..331487b51d 100644
--- a/src/qt/utilitydialog.cpp
+++ b/src/qt/utilitydialog.cpp
@@ -17,7 +17,7 @@
#include <util/system.h>
#include <util/strencodings.h>
-#include <stdio.h>
+#include <cstdio>
#include <QCloseEvent>
#include <QLabel>
diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp
index 01d84624e8..8762ba9ab3 100644
--- a/src/qt/walletcontroller.cpp
+++ b/src/qt/walletcontroller.cpp
@@ -262,9 +262,13 @@ void CreateWalletActivity::createWallet()
}
QTimer::singleShot(500ms, worker(), [this, name, flags] {
- std::unique_ptr<interfaces::Wallet> wallet = node().walletLoader().createWallet(name, m_passphrase, flags, m_error_message, m_warning_message);
+ auto wallet{node().walletLoader().createWallet(name, m_passphrase, flags, m_warning_message)};
- if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
+ if (wallet) {
+ m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
+ } else {
+ m_error_message = util::ErrorString(wallet);
+ }
QTimer::singleShot(500ms, this, &CreateWalletActivity::finish);
});
@@ -293,6 +297,10 @@ void CreateWalletActivity::create()
} catch (const std::runtime_error& e) {
QMessageBox::critical(nullptr, tr("Can't list signers"), e.what());
}
+ if (signers.size() > 1) {
+ QMessageBox::critical(nullptr, tr("Too many external signers found"), QString::fromStdString("More than one external signer found. Please connect only one at a time."));
+ signers.clear();
+ }
m_create_wallet_dialog->setSigners(signers);
m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal);
@@ -343,9 +351,13 @@ void OpenWalletActivity::open(const std::string& path)
tr("Opening Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));
QTimer::singleShot(0, worker(), [this, path] {
- std::unique_ptr<interfaces::Wallet> wallet = node().walletLoader().loadWallet(path, m_error_message, m_warning_message);
+ auto wallet{node().walletLoader().loadWallet(path, m_warning_message)};
- if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
+ if (wallet) {
+ m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
+ } else {
+ m_error_message = util::ErrorString(wallet);
+ }
QTimer::singleShot(0, this, &OpenWalletActivity::finish);
});
@@ -393,8 +405,11 @@ void RestoreWalletActivity::restore(const fs::path& backup_file, const std::stri
QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] {
auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message)};
- m_error_message = wallet ? bilingual_str{} : wallet.GetError();
- if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
+ if (wallet) {
+ m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
+ } else {
+ m_error_message = util::ErrorString(wallet);
+ }
QTimer::singleShot(0, this, &RestoreWalletActivity::finish);
});
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index fde136b727..c6f3f5b00c 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -67,6 +67,10 @@ WalletModel::~WalletModel()
void WalletModel::startPollBalance()
{
+ // Update the cached balance right away, so every view can make use of it,
+ // so them don't need to waste resources recalculating it.
+ pollBalanceChanged();
+
// This timer will be fired repeatedly to update the balance
// Since the QTimer::timeout is a private signal, it cannot be used
// in the GUIUtil::ExceptionSafeConnect directly.
@@ -120,12 +124,17 @@ void WalletModel::pollBalanceChanged()
void WalletModel::checkBalanceChanged(const interfaces::WalletBalances& new_balances)
{
- if(new_balances.balanceChanged(m_cached_balances)) {
+ if (new_balances.balanceChanged(m_cached_balances)) {
m_cached_balances = new_balances;
Q_EMIT balanceChanged(new_balances);
}
}
+interfaces::WalletBalances WalletModel::getCachedBalance() const
+{
+ return m_cached_balances;
+}
+
void WalletModel::updateTransaction()
{
// Balance and number of transactions might have changed
@@ -194,7 +203,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
return DuplicateAddress;
}
- CAmount nBalance = m_wallet->getAvailableBalance(coinControl);
+ // If no coin was manually selected, use the cached balance
+ // Future: can merge this call with 'createTransaction'.
+ CAmount nBalance = getAvailableBalance(&coinControl);
if(total > nBalance)
{
@@ -207,7 +218,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
auto& newTx = transaction.getWtx();
const auto& res = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired);
- newTx = res ? res.GetObj() : nullptr;
+ newTx = res ? *res : nullptr;
transaction.setTransactionFee(nFeeRequired);
if (fSubtractFeeFromAmount && newTx)
transaction.reassignAmounts(nChangePosRet);
@@ -218,7 +229,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
{
return SendCoinsReturn(AmountWithFeeExceedsBalance);
}
- Q_EMIT message(tr("Send Coins"), QString::fromStdString(res.GetError().translated),
+ Q_EMIT message(tr("Send Coins"), QString::fromStdString(util::ErrorString(res).translated),
CClientUIInterface::MSG_ERROR);
return TransactionCreationFailed;
}
@@ -602,3 +613,8 @@ uint256 WalletModel::getLastBlockProcessed() const
{
return m_client_model ? m_client_model->getBestBlockHash() : uint256{};
}
+
+CAmount WalletModel::getAvailableBalance(const CCoinControl* control)
+{
+ return control && control->HasSelected() ? wallet().getAvailableBalance(*control) : getCachedBalance().balance;
+}
diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h
index 0184fb8ec2..73dfe0386a 100644
--- a/src/qt/walletmodel.h
+++ b/src/qt/walletmodel.h
@@ -155,6 +155,13 @@ public:
uint256 getLastBlockProcessed() const;
+ // Retrieve the cached wallet balance
+ interfaces::WalletBalances getCachedBalance() const;
+
+ // If coin control has selected outputs, searches the total amount inside the wallet.
+ // Otherwise, uses the wallet's cached available balance.
+ CAmount getAvailableBalance(const wallet::CCoinControl* control);
+
private:
std::unique_ptr<interfaces::Wallet> m_wallet;
std::unique_ptr<interfaces::Handler> m_handler_unload;
diff --git a/src/random.cpp b/src/random.cpp
index f92e679a00..eab54630b1 100644
--- a/src/random.cpp
+++ b/src/random.cpp
@@ -21,7 +21,7 @@
#include <util/time.h> // for GetTimeMicros()
#include <cmath>
-#include <stdlib.h>
+#include <cstdlib>
#include <thread>
#ifndef WIN32
diff --git a/src/rest.cpp b/src/rest.cpp
index 43c248b03b..a10d8a433f 100644
--- a/src/rest.cpp
+++ b/src/rest.cpp
@@ -590,45 +590,73 @@ static bool rest_chaininfo(const std::any& context, HTTPRequest* req, const std:
}
}
-static bool rest_mempool_info(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
+
+RPCHelpMan getdeploymentinfo();
+
+static bool rest_deploymentinfo(const std::any& context, HTTPRequest* req, const std::string& str_uri_part)
{
- if (!CheckWarmup(req))
- return false;
- const CTxMemPool* mempool = GetMemPool(context, req);
- if (!mempool) return false;
- std::string param;
- const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
+ if (!CheckWarmup(req)) return false;
+
+ std::string hash_str;
+ const RESTResponseFormat rf = ParseDataFormat(hash_str, str_uri_part);
switch (rf) {
case RESTResponseFormat::JSON: {
- UniValue mempoolInfoObject = MempoolInfoToJSON(*mempool);
+ JSONRPCRequest jsonRequest;
+ jsonRequest.context = context;
+ jsonRequest.params = UniValue(UniValue::VARR);
+
+ if (!hash_str.empty()) {
+ uint256 hash;
+ if (!ParseHashStr(hash_str, hash)) {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hash_str);
+ }
+
+ const ChainstateManager* chainman = GetChainman(context, req);
+ if (!chainman) return false;
+ if (!WITH_LOCK(::cs_main, return chainman->m_blockman.LookupBlockIndex(ParseHashV(hash_str, "blockhash")))) {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Block not found");
+ }
+
+ jsonRequest.params.pushKV("blockhash", hash_str);
+ }
- std::string strJSON = mempoolInfoObject.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
- req->WriteReply(HTTP_OK, strJSON);
+ req->WriteReply(HTTP_OK, getdeploymentinfo().HandleRequest(jsonRequest).write() + "\n");
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
}
}
+
}
-static bool rest_mempool_contents(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
+static bool rest_mempool(const std::any& context, HTTPRequest* req, const std::string& str_uri_part)
{
- if (!CheckWarmup(req)) return false;
+ if (!CheckWarmup(req))
+ return false;
+
+ std::string param;
+ const RESTResponseFormat rf = ParseDataFormat(param, str_uri_part);
+ if (param != "contents" && param != "info") {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/mempool/<info|contents>.json");
+ }
+
const CTxMemPool* mempool = GetMemPool(context, req);
if (!mempool) return false;
- std::string param;
- const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
case RESTResponseFormat::JSON: {
- UniValue mempoolObject = MempoolToJSON(*mempool, true);
+ std::string str_json;
+ if (param == "contents") {
+ str_json = MempoolToJSON(*mempool, true).write() + "\n";
+ } else {
+ str_json = MempoolInfoToJSON(*mempool).write() + "\n";
+ }
- std::string strJSON = mempoolObject.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
- req->WriteReply(HTTP_OK, strJSON);
+ req->WriteReply(HTTP_OK, str_json);
return true;
}
default: {
@@ -798,14 +826,18 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
ChainstateManager* maybe_chainman = GetChainman(context, req);
if (!maybe_chainman) return false;
ChainstateManager& chainman = *maybe_chainman;
+ decltype(chainman.ActiveHeight()) active_height;
+ uint256 active_hash;
{
- auto process_utxos = [&vOutPoints, &outs, &hits](const CCoinsView& view, const CTxMemPool* mempool) {
+ auto process_utxos = [&vOutPoints, &outs, &hits, &active_height, &active_hash, &chainman](const CCoinsView& view, const CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(chainman.GetMutex()) {
for (const COutPoint& vOutPoint : vOutPoints) {
Coin coin;
bool hit = (!mempool || !mempool->isSpent(vOutPoint)) && view.GetCoin(vOutPoint, coin);
hits.push_back(hit);
if (hit) outs.emplace_back(std::move(coin));
}
+ active_height = chainman.ActiveHeight();
+ active_hash = chainman.ActiveTip()->GetBlockHash();
};
if (fCheckMemPool) {
@@ -833,7 +865,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
// serialize data
// use exact same output as mentioned in Bip64
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
- ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs;
+ ssGetUTXOResponse << active_height << active_hash << bitmap << outs;
std::string ssGetUTXOResponseString = ssGetUTXOResponse.str();
req->WriteHeader("Content-Type", "application/octet-stream");
@@ -843,7 +875,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
case RESTResponseFormat::HEX: {
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
- ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs;
+ ssGetUTXOResponse << active_height << active_hash << bitmap << outs;
std::string strHex = HexStr(ssGetUTXOResponse) + "\n";
req->WriteHeader("Content-Type", "text/plain");
@@ -856,8 +888,8 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
// pack in some essentials
// use more or less the same output as mentioned in Bip64
- objGetUTXOResponse.pushKV("chainHeight", chainman.ActiveChain().Height());
- objGetUTXOResponse.pushKV("chaintipHash", chainman.ActiveChain().Tip()->GetBlockHash().GetHex());
+ objGetUTXOResponse.pushKV("chainHeight", active_height);
+ objGetUTXOResponse.pushKV("chaintipHash", active_hash.GetHex());
objGetUTXOResponse.pushKV("bitmap", bitmapStringRepresentation);
UniValue utxos(UniValue::VARR);
@@ -946,10 +978,11 @@ static const struct {
{"/rest/blockfilter/", rest_block_filter},
{"/rest/blockfilterheaders/", rest_filter_header},
{"/rest/chaininfo", rest_chaininfo},
- {"/rest/mempool/info", rest_mempool_info},
- {"/rest/mempool/contents", rest_mempool_contents},
+ {"/rest/mempool/", rest_mempool},
{"/rest/headers/", rest_headers},
{"/rest/getutxos", rest_getutxos},
+ {"/rest/deploymentinfo/", rest_deploymentinfo},
+ {"/rest/deploymentinfo", rest_deploymentinfo},
{"/rest/blockhashbyheight/", rest_blockhash_by_height},
};
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 8f116a05ef..bc1028aec3 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -459,6 +459,12 @@ static RPCHelpMan getblockfrompeer()
throw JSONRPCError(RPC_MISC_ERROR, "Block header missing");
}
+ // Fetching blocks before the node has syncing past their height can prevent block files from
+ // being pruned, so we avoid it if the node is in prune mode.
+ if (node::fPruneMode && index->nHeight > WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()->nHeight)) {
+ throw JSONRPCError(RPC_MISC_ERROR, "In prune mode, only blocks that the node has already synced previously can be fetched from a peer");
+ }
+
const bool block_has_data = WITH_LOCK(::cs_main, return index->nStatus & BLOCK_HAVE_DATA);
if (block_has_data) {
throw JSONRPCError(RPC_MISC_ERROR, "Block already downloaded");
@@ -767,7 +773,7 @@ static RPCHelpMan pruneblockchain()
ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
- CChainState& active_chainstate = chainman.ActiveChainstate();
+ Chainstate& active_chainstate = chainman.ActiveChainstate();
CChain& active_chain = active_chainstate.m_chain;
int heightParam = request.params[0].getInt<int>();
@@ -833,9 +839,9 @@ static std::optional<kernel::CCoinsStats> GetUTXOStats(CCoinsView* view, node::B
// Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested
if ((hash_type == kernel::CoinStatsHashType::MUHASH || hash_type == kernel::CoinStatsHashType::NONE) && g_coin_stats_index && index_requested) {
if (pindex) {
- return g_coin_stats_index->LookUpStats(pindex);
+ return g_coin_stats_index->LookUpStats(*pindex);
} else {
- CBlockIndex* block_index = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock()));
+ CBlockIndex& block_index = *CHECK_NONFATAL(WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())));
return g_coin_stats_index->LookUpStats(block_index);
}
}
@@ -856,7 +862,7 @@ static RPCHelpMan gettxoutsetinfo()
"Note this call may take some time if you are not using coinstatsindex.\n",
{
{"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized_2"}, "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'muhash', 'none'."},
- {"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).", "", {"", "string or numeric"}},
+ {"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).", RPCArgOptions{.type_str={"", "string or numeric"}}},
{"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."},
},
RPCResult{
@@ -908,7 +914,7 @@ static RPCHelpMan gettxoutsetinfo()
NodeContext& node = EnsureAnyNodeContext(request.context);
ChainstateManager& chainman = EnsureChainman(node);
- CChainState& active_chainstate = chainman.ActiveChainstate();
+ Chainstate& active_chainstate = chainman.ActiveChainstate();
active_chainstate.ForceFlushStateToDisk();
CCoinsView* coins_view;
@@ -1048,7 +1054,7 @@ static RPCHelpMan gettxout()
fMempool = request.params[2].get_bool();
Coin coin;
- CChainState& active_chainstate = chainman.ActiveChainstate();
+ Chainstate& active_chainstate = chainman.ActiveChainstate();
CCoinsViewCache* coins_view = &active_chainstate.CoinsTip();
if (fMempool) {
@@ -1105,7 +1111,7 @@ static RPCHelpMan verifychain()
ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
- CChainState& active_chainstate = chainman.ActiveChainstate();
+ Chainstate& active_chainstate = chainman.ActiveChainstate();
return CVerifyDB().VerifyDB(
active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth);
},
@@ -1233,7 +1239,7 @@ RPCHelpMan getblockchaininfo()
const ArgsManager& args{EnsureAnyArgsman(request.context)};
ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
- CChainState& active_chainstate = chainman.ActiveChainstate();
+ Chainstate& active_chainstate = chainman.ActiveChainstate();
const CBlockIndex& tip{*CHECK_NONFATAL(active_chainstate.m_chain.Tip())};
const int height{tip.nHeight};
@@ -1307,7 +1313,7 @@ UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager&
}
} // anon namespace
-static RPCHelpMan getdeploymentinfo()
+RPCHelpMan getdeploymentinfo()
{
return RPCHelpMan{"getdeploymentinfo",
"Returns an object containing various state info regarding deployments of consensus changes.",
@@ -1328,7 +1334,7 @@ static RPCHelpMan getdeploymentinfo()
{
const ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
- const CChainState& active_chainstate = chainman.ActiveChainstate();
+ const Chainstate& active_chainstate = chainman.ActiveChainstate();
const CBlockIndex* blockindex;
if (request.params[0].isNull()) {
@@ -1726,13 +1732,13 @@ static 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",
{
- {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", "", {"", "string or numeric"}},
+ {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", RPCArgOptions{.type_str={"", "string or numeric"}}},
{"stats", RPCArg::Type::ARR, RPCArg::DefaultHint{"all values"}, "Values to plot (see result below)",
{
{"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"},
{"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"},
},
- "stats"},
+ RPCArgOptions{.oneline_description="stats"}},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -2019,6 +2025,40 @@ public:
}
};
+static const auto scan_action_arg_desc = RPCArg{
+ "action", RPCArg::Type::STR, RPCArg::Optional::NO, "The action to execute\n"
+ "\"start\" for starting a scan\n"
+ "\"abort\" for aborting the current scan (returns true when abort was successful)\n"
+ "\"status\" for progress report (in %) of the current scan"
+};
+
+static const auto scan_objects_arg_desc = RPCArg{
+ "scanobjects", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of scan objects. Required for \"start\" action\n"
+ "Every scan object is either a string descriptor or an object:",
+ {
+ {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
+ {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata",
+ {
+ {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
+ {"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "The range of HD chain indexes to explore (either end or [begin,end])"},
+ }},
+ },
+ RPCArgOptions{.oneline_description="[scanobjects,...]"},
+};
+
+static const auto scan_result_abort = RPCResult{
+ "when action=='abort'", RPCResult::Type::BOOL, "success",
+ "True if scan will be aborted (not necessarily before this RPC returns), or false if there is no scan to abort"
+};
+static const auto scan_result_status_none = RPCResult{
+ "when action=='status' and no scan is in progress - possibly already completed", RPCResult::Type::NONE, "", ""
+};
+static const auto scan_result_status_some = RPCResult{
+ "when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "",
+ {{RPCResult::Type::NUM, "progress", "Approximate percent complete"},}
+};
+
+
static RPCHelpMan scantxoutset()
{
// scriptPubKey corresponding to mainnet address 12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S
@@ -2038,21 +2078,8 @@ static RPCHelpMan scantxoutset()
"In the latter case, a range needs to be specified by below if different from 1000.\n"
"For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n",
{
- {"action", RPCArg::Type::STR, RPCArg::Optional::NO, "The action to execute\n"
- "\"start\" for starting a scan\n"
- "\"abort\" for aborting the current scan (returns true when abort was successful)\n"
- "\"status\" for progress report (in %) of the current scan"},
- {"scanobjects", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of scan objects. Required for \"start\" action\n"
- "Every scan object is either a string descriptor or an object:",
- {
- {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
- {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata",
- {
- {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
- {"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "The range of HD chain indexes to explore (either end or [begin,end])"},
- }},
- },
- "[scanobjects,...]"},
+ scan_action_arg_desc,
+ scan_objects_arg_desc,
},
{
RPCResult{"when action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", {
@@ -2069,17 +2096,15 @@ static RPCHelpMan scantxoutset()
{RPCResult::Type::STR_HEX, "scriptPubKey", "The script key"},
{RPCResult::Type::STR, "desc", "A specialized descriptor for the matched scriptPubKey"},
{RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the unspent output"},
+ {RPCResult::Type::BOOL, "coinbase", "Whether this is a coinbase output"},
{RPCResult::Type::NUM, "height", "Height of the unspent transaction output"},
}},
}},
{RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + CURRENCY_UNIT},
}},
- RPCResult{"when action=='abort'", RPCResult::Type::BOOL, "success", "True if scan will be aborted (not necessarily before this RPC returns), or false if there is no scan to abort"},
- RPCResult{"when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::NUM, "progress", "Approximate percent complete"},
- }},
- RPCResult{"when action=='status' and no scan is in progress - possibly already completed", RPCResult::Type::NONE, "", ""},
+ scan_result_abort,
+ scan_result_status_some,
+ scan_result_status_none,
},
RPCExamples{
HelpExampleCli("scantxoutset", "start \'[\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]\'") +
@@ -2129,7 +2154,7 @@ static RPCHelpMan scantxoutset()
for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
FlatSigningProvider provider;
auto scripts = EvalDescriptorStringOrObject(scanobject, provider);
- for (const auto& script : scripts) {
+ for (CScript& script : scripts) {
std::string inferred = InferDescriptor(script, provider)->ToString();
needles.emplace(script);
descriptors.emplace(std::move(script), std::move(inferred));
@@ -2148,7 +2173,7 @@ static RPCHelpMan scantxoutset()
{
ChainstateManager& chainman = EnsureChainman(node);
LOCK(cs_main);
- CChainState& active_chainstate = chainman.ActiveChainstate();
+ Chainstate& active_chainstate = chainman.ActiveChainstate();
active_chainstate.ForceFlushStateToDisk();
pcursor = CHECK_NONFATAL(active_chainstate.CoinsDB().Cursor());
tip = CHECK_NONFATAL(active_chainstate.m_chain.Tip());
@@ -2172,6 +2197,7 @@ static RPCHelpMan scantxoutset()
unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
unspent.pushKV("desc", descriptors[txo.scriptPubKey]);
unspent.pushKV("amount", ValueFromAmount(txo.nValue));
+ unspent.pushKV("coinbase", coin.IsCoinBase());
unspent.pushKV("height", (int32_t)coin.nHeight);
unspents.push_back(unspent);
@@ -2186,6 +2212,203 @@ static RPCHelpMan scantxoutset()
};
}
+/** RAII object to prevent concurrency issue when scanning blockfilters */
+static std::atomic<int> g_scanfilter_progress;
+static std::atomic<int> g_scanfilter_progress_height;
+static std::atomic<bool> g_scanfilter_in_progress;
+static std::atomic<bool> g_scanfilter_should_abort_scan;
+class BlockFiltersScanReserver
+{
+private:
+ bool m_could_reserve{false};
+public:
+ explicit BlockFiltersScanReserver() = default;
+
+ bool reserve() {
+ CHECK_NONFATAL(!m_could_reserve);
+ if (g_scanfilter_in_progress.exchange(true)) {
+ return false;
+ }
+ m_could_reserve = true;
+ return true;
+ }
+
+ ~BlockFiltersScanReserver() {
+ if (m_could_reserve) {
+ g_scanfilter_in_progress = false;
+ }
+ }
+};
+
+static RPCHelpMan scanblocks()
+{
+ return RPCHelpMan{"scanblocks",
+ "\nReturn relevant blockhashes for given descriptors.\n"
+ "This call may take several minutes. Make sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)",
+ {
+ scan_action_arg_desc,
+ scan_objects_arg_desc,
+ RPCArg{"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "Height to start to scan from"},
+ RPCArg{"stop_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"chain tip"}, "Height to stop to scan"},
+ RPCArg{"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"}
+ },
+ {
+ scan_result_status_none,
+ RPCResult{"When action=='start'", RPCResult::Type::OBJ, "", "", {
+ {RPCResult::Type::NUM, "from_height", "The height we started the scan from"},
+ {RPCResult::Type::NUM, "to_height", "The height we ended the scan at"},
+ {RPCResult::Type::ARR, "relevant_blocks", "", {{RPCResult::Type::STR_HEX, "blockhash", "A relevant blockhash"},}},
+ },
+ },
+ RPCResult{"when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "", {
+ {RPCResult::Type::NUM, "progress", "Approximate percent complete"},
+ {RPCResult::Type::NUM, "current_height", "Height of the block currently being scanned"},
+ },
+ },
+ scan_result_abort,
+ },
+ RPCExamples{
+ HelpExampleCli("scanblocks", "start '[\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"]' 300000") +
+ HelpExampleCli("scanblocks", "start '[\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"]' 100 150 basic") +
+ HelpExampleCli("scanblocks", "status") +
+ HelpExampleRpc("scanblocks", "\"start\", [\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"], 300000") +
+ HelpExampleRpc("scanblocks", "\"start\", [\"addr(bcrt1q4u4nsgk6ug0sqz7r3rj9tykjxrsl0yy4d0wwte)\"], 100, 150, \"basic\"") +
+ HelpExampleRpc("scanblocks", "\"status\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ UniValue ret(UniValue::VOBJ);
+ if (request.params[0].get_str() == "status") {
+ BlockFiltersScanReserver reserver;
+ if (reserver.reserve()) {
+ // no scan in progress
+ return NullUniValue;
+ }
+ ret.pushKV("progress", g_scanfilter_progress.load());
+ ret.pushKV("current_height", g_scanfilter_progress_height.load());
+ return ret;
+ } else if (request.params[0].get_str() == "abort") {
+ BlockFiltersScanReserver reserver;
+ if (reserver.reserve()) {
+ // reserve was possible which means no scan was running
+ return false;
+ }
+ // set the abort flag
+ g_scanfilter_should_abort_scan = true;
+ return true;
+ }
+ else if (request.params[0].get_str() == "start") {
+ BlockFiltersScanReserver reserver;
+ if (!reserver.reserve()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");
+ }
+ const std::string filtertype_name{request.params[4].isNull() ? "basic" : request.params[4].get_str()};
+
+ BlockFilterType filtertype;
+ if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype");
+ }
+
+ BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
+ if (!index) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name);
+ }
+
+ NodeContext& node = EnsureAnyNodeContext(request.context);
+ ChainstateManager& chainman = EnsureChainman(node);
+
+ // set the start-height
+ const CBlockIndex* block = nullptr;
+ const CBlockIndex* stop_block = nullptr;
+ {
+ LOCK(cs_main);
+ CChain& active_chain = chainman.ActiveChain();
+ block = active_chain.Genesis();
+ stop_block = active_chain.Tip();
+ if (!request.params[2].isNull()) {
+ block = active_chain[request.params[2].getInt<int>()];
+ if (!block) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Invalid start_height");
+ }
+ }
+ if (!request.params[3].isNull()) {
+ stop_block = active_chain[request.params[3].getInt<int>()];
+ if (!stop_block || stop_block->nHeight < block->nHeight) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Invalid stop_height");
+ }
+ }
+ }
+ CHECK_NONFATAL(block);
+
+ // loop through the scan objects, add scripts to the needle_set
+ GCSFilter::ElementSet needle_set;
+ for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
+ FlatSigningProvider provider;
+ std::vector<CScript> scripts = EvalDescriptorStringOrObject(scanobject, provider);
+ for (const CScript& script : scripts) {
+ needle_set.emplace(script.begin(), script.end());
+ }
+ }
+ UniValue blocks(UniValue::VARR);
+ const int amount_per_chunk = 10000;
+ const CBlockIndex* start_index = block; // for remembering the start of a blockfilter range
+ std::vector<BlockFilter> filters;
+ const CBlockIndex* start_block = block; // for progress reporting
+ const int total_blocks_to_process = stop_block->nHeight - start_block->nHeight;
+
+ g_scanfilter_should_abort_scan = false;
+ g_scanfilter_progress = 0;
+ g_scanfilter_progress_height = start_block->nHeight;
+
+ while (block) {
+ node.rpc_interruption_point(); // allow a clean shutdown
+ if (g_scanfilter_should_abort_scan) {
+ LogPrintf("scanblocks RPC aborted at height %d.\n", block->nHeight);
+ break;
+ }
+ const CBlockIndex* next = nullptr;
+ {
+ LOCK(cs_main);
+ CChain& active_chain = chainman.ActiveChain();
+ next = active_chain.Next(block);
+ if (block == stop_block) next = nullptr;
+ }
+ if (start_index->nHeight + amount_per_chunk == block->nHeight || next == nullptr) {
+ LogPrint(BCLog::RPC, "Fetching blockfilters from height %d to height %d.\n", start_index->nHeight, block->nHeight);
+ if (index->LookupFilterRange(start_index->nHeight, block, filters)) {
+ for (const BlockFilter& filter : filters) {
+ // compare the elements-set with each filter
+ if (filter.GetFilter().MatchAny(needle_set)) {
+ blocks.push_back(filter.GetBlockHash().GetHex());
+ LogPrint(BCLog::RPC, "scanblocks: found match in %s\n", filter.GetBlockHash().GetHex());
+ }
+ }
+ }
+ start_index = block;
+
+ // update progress
+ int blocks_processed = block->nHeight - start_block->nHeight;
+ if (total_blocks_to_process > 0) { // avoid division by zero
+ g_scanfilter_progress = (int)(100.0 / total_blocks_to_process * blocks_processed);
+ } else {
+ g_scanfilter_progress = 100;
+ }
+ g_scanfilter_progress_height = block->nHeight;
+ }
+ block = next;
+ }
+ ret.pushKV("from_height", start_block->nHeight);
+ ret.pushKV("to_height", g_scanfilter_progress_height.load());
+ ret.pushKV("relevant_blocks", blocks);
+ }
+ else {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command");
+ }
+ return ret;
+},
+ };
+}
+
static RPCHelpMan getblockfilter()
{
return RPCHelpMan{"getblockfilter",
@@ -2328,7 +2551,7 @@ static RPCHelpMan dumptxoutset()
UniValue CreateUTXOSnapshot(
NodeContext& node,
- CChainState& chainstate,
+ Chainstate& chainstate,
AutoFile& afile,
const fs::path& path,
const fs::path& temppath)
@@ -2421,6 +2644,7 @@ void RegisterBlockchainRPCCommands(CRPCTable& t)
{"blockchain", &verifychain},
{"blockchain", &preciousblock},
{"blockchain", &scantxoutset},
+ {"blockchain", &scanblocks},
{"blockchain", &getblockfilter},
{"hidden", &invalidateblock},
{"hidden", &reconsiderblock},
diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h
index a332fd4892..6cdb5fa48b 100644
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@ -20,7 +20,7 @@ extern RecursiveMutex cs_main;
class CBlock;
class CBlockIndex;
-class CChainState;
+class Chainstate;
class UniValue;
namespace node {
struct NodeContext;
@@ -54,7 +54,7 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES],
*/
UniValue CreateUTXOSnapshot(
node::NodeContext& node,
- CChainState& chainstate,
+ Chainstate& chainstate,
AutoFile& afile,
const fs::path& path,
const fs::path& tmppath);
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 9be3ab7df0..8688263ef5 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -74,6 +74,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "listsinceblock", 1, "target_confirmations" },
{ "listsinceblock", 2, "include_watchonly" },
{ "listsinceblock", 3, "include_removed" },
+ { "listsinceblock", 4, "include_change" },
{ "sendmany", 1, "amounts" },
{ "sendmany", 2, "minconf" },
{ "sendmany", 4, "subtractfeefrom" },
@@ -82,6 +83,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "sendmany", 8, "fee_rate"},
{ "sendmany", 9, "verbose" },
{ "deriveaddresses", 1, "range" },
+ { "scanblocks", 1, "scanobjects" },
+ { "scanblocks", 2, "start_height" },
+ { "scanblocks", 3, "stop_height" },
{ "scantxoutset", 1, "scanobjects" },
{ "addmultisigaddress", 0, "nrequired" },
{ "addmultisigaddress", 1, "keys" },
@@ -147,6 +151,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "sendall", 1, "conf_target" },
{ "sendall", 3, "fee_rate"},
{ "sendall", 4, "options" },
+ { "simulaterawtransaction", 0, "rawtxs" },
+ { "simulaterawtransaction", 1, "options" },
{ "importprivkey", 2, "rescan" },
{ "importaddress", 2, "rescan" },
{ "importaddress", 3, "p2sh" },
diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp
index 41f386d443..e50bf00473 100644
--- a/src/rpc/fees.cpp
+++ b/src/rpc/fees.cpp
@@ -6,7 +6,6 @@
#include <core_io.h>
#include <policy/feerate.h>
#include <policy/fees.h>
-#include <policy/settings.h>
#include <rpc/protocol.h>
#include <rpc/request.h>
#include <rpc/server.h>
@@ -65,7 +64,6 @@ static RPCHelpMan estimatesmartfee()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR});
- RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
const NodeContext& node = EnsureAnyNodeContext(request.context);
@@ -88,7 +86,7 @@ static RPCHelpMan estimatesmartfee()
CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)};
if (feeRate != CFeeRate(0)) {
CFeeRate min_mempool_feerate{mempool.GetMinFee()};
- CFeeRate min_relay_feerate{::minRelayTxFee};
+ CFeeRate min_relay_feerate{mempool.m_min_relay_feerate};
feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate});
result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
} else {
@@ -158,7 +156,6 @@ static RPCHelpMan estimaterawfee()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true);
- RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
index 0ae10b6c39..706d783942 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -168,7 +168,7 @@ static RPCHelpMan testmempoolaccept()
NodeContext& node = EnsureAnyNodeContext(request.context);
CTxMemPool& mempool = EnsureMemPool(node);
ChainstateManager& chainman = EnsureChainman(node);
- CChainState& chainstate = chainman.ActiveChainstate();
+ Chainstate& chainstate = chainman.ActiveChainstate();
const PackageMempoolAcceptResult package_result = [&] {
LOCK(::cs_main);
if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true);
@@ -255,7 +255,7 @@ static std::vector<RPCResult> MempoolEntryDescription()
{RPCResult{RPCResult::Type::STR_HEX, "transactionid", "parent transaction id"}}},
RPCResult{RPCResult::Type::ARR, "spentby", "unconfirmed transactions spending outputs from this transaction",
{RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}},
- RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction could be replaced due to BIP125 (replace-by-fee)"},
+ RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction signals BIP125 replaceability or has an unconfirmed ancestor signaling BIP125 replaceability.\n"},
RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"},
};
}
@@ -449,9 +449,8 @@ static RPCHelpMan getmempoolancestors()
}
CTxMemPool::setEntries setAncestors;
- uint64_t noLimit = std::numeric_limits<uint64_t>::max();
std::string dummy;
- mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false);
+ mempool.CalculateMemPoolAncestors(*it, setAncestors, CTxMemPool::Limits::NoLimits(), dummy, false);
if (!fVerbose) {
UniValue o(UniValue::VARR);
@@ -605,8 +604,7 @@ static RPCHelpMan gettxspendingprevout()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- RPCTypeCheckArgument(request.params[0], UniValue::VARR);
- const UniValue& output_params = request.params[0];
+ const UniValue& output_params = request.params[0].get_array();
if (output_params.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, outputs are missing");
}
@@ -666,9 +664,9 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool)
ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage());
ret.pushKV("total_fee", ValueFromAmount(pool.GetTotalFee()));
ret.pushKV("maxmempool", pool.m_max_size_bytes);
- ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(), ::minRelayTxFee).GetFeePerK()));
- ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
- ret.pushKV("incrementalrelayfee", ValueFromAmount(::incrementalRelayFee.GetFeePerK()));
+ ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(), pool.m_min_relay_feerate).GetFeePerK()));
+ ret.pushKV("minrelaytxfee", ValueFromAmount(pool.m_min_relay_feerate.GetFeePerK()));
+ ret.pushKV("incrementalrelayfee", ValueFromAmount(pool.m_incremental_relay_feerate.GetFeePerK()));
ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
ret.pushKV("fullrbf", pool.m_full_rbf);
return ret;
@@ -690,7 +688,7 @@ static RPCHelpMan getmempoolinfo()
{RPCResult::Type::NUM, "maxmempool", "Maximum memory usage for the mempool"},
{RPCResult::Type::STR_AMOUNT, "mempoolminfee", "Minimum fee rate in " + CURRENCY_UNIT + "/kvB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee"},
{RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"},
- {RPCResult::Type::NUM, "incrementalrelayfee", "minimum fee rate increment for mempool limiting or BIP 125 replacement in " + CURRENCY_UNIT + "/kvB"},
+ {RPCResult::Type::NUM, "incrementalrelayfee", "minimum fee rate increment for mempool limiting or replacement in " + CURRENCY_UNIT + "/kvB"},
{RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"},
{RPCResult::Type::BOOL, "fullrbf", "True if the mempool accepts RBF without replaceability signaling inspection"},
}},
@@ -810,7 +808,7 @@ static RPCHelpMan submitpackage()
NodeContext& node = EnsureAnyNodeContext(request.context);
CTxMemPool& mempool = EnsureMemPool(node);
- CChainState& chainstate = EnsureChainman(node).ActiveChainstate();
+ Chainstate& chainstate = EnsureChainman(node).ActiveChainstate();
const auto package_result = WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/ false));
// First catch any errors.
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index 2902b35865..98383fdaca 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -132,7 +132,7 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t&
}
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
- if (!chainman.ProcessNewBlock(shared_pblock, true, nullptr)) {
+ if (!chainman.ProcessNewBlock(shared_pblock, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
}
@@ -524,7 +524,7 @@ static RPCHelpMan getblocktemplate()
{"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "other client side supported softfork deployment"},
}},
},
- "\"template_request\""},
+ RPCArgOptions{.oneline_description="\"template_request\""}},
},
{
RPCResult{"If the proposal was accepted with mode=='proposal'", RPCResult::Type::NONE, "", ""},
@@ -598,8 +598,7 @@ static RPCHelpMan getblocktemplate()
std::string strMode = "template";
UniValue lpval = NullUniValue;
std::set<std::string> setClientRules;
- int64_t nMaxVersionPreVB = -1;
- CChainState& active_chainstate = chainman.ActiveChainstate();
+ Chainstate& active_chainstate = chainman.ActiveChainstate();
CChain& active_chain = active_chainstate.m_chain;
if (!request.params[0].isNull())
{
@@ -650,12 +649,6 @@ static RPCHelpMan getblocktemplate()
const UniValue& v = aClientRules[i];
setClientRules.insert(v.get_str());
}
- } else {
- // NOTE: It is important that this NOT be read if versionbits is supported
- const UniValue& uvMaxVersion = find_value(oparam, "maxversion");
- if (uvMaxVersion.isNum()) {
- nMaxVersionPreVB = uvMaxVersion.getInt<int64_t>();
- }
}
}
@@ -686,7 +679,7 @@ static RPCHelpMan getblocktemplate()
if (lpval.isStr())
{
// Format: <hashBestChain><nTransactionsUpdatedLast>
- std::string lpstr = lpval.get_str();
+ const std::string& lpstr = lpval.get_str();
hashWatchedChain = ParseHashV(lpstr.substr(0, 64), "longpollid");
nTransactionsUpdatedLastLP = LocaleIndependentAtoi<int64_t>(lpstr.substr(64));
@@ -737,10 +730,10 @@ static RPCHelpMan getblocktemplate()
// Update block
static CBlockIndex* pindexPrev;
- static int64_t nStart;
+ static int64_t time_start;
static std::unique_ptr<CBlockTemplate> pblocktemplate;
if (pindexPrev != active_chain.Tip() ||
- (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5))
+ (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - time_start > 5))
{
// Clear pindexPrev so future calls make a new block, despite any failures from here on
pindexPrev = nullptr;
@@ -748,7 +741,7 @@ static RPCHelpMan getblocktemplate()
// Store the pindexBest used before CreateNewBlock, to avoid races
nTransactionsUpdatedLast = mempool.GetTransactionsUpdated();
CBlockIndex* pindexPrevNew = active_chain.Tip();
- nStart = GetTime();
+ time_start = GetTime();
// Create new block
CScript scriptDummy = CScript() << OP_TRUE;
@@ -863,7 +856,6 @@ static RPCHelpMan getblocktemplate()
if (setClientRules.find(vbinfo.name) == setClientRules.end()) {
// Not supported by the client; make sure it's safe to proceed
if (!vbinfo.gbt_force) {
- // If we do anything other than throw an exception here, be sure version/force isn't sent to old clients
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Support for '%s' rule requires explicit client support", vbinfo.name));
}
}
@@ -876,14 +868,6 @@ static RPCHelpMan getblocktemplate()
result.pushKV("vbavailable", vbavailable);
result.pushKV("vbrequired", int(0));
- if (nMaxVersionPreVB >= 2) {
- // If VB is supported by the client, nMaxVersionPreVB is -1, so we won't get here
- // Because BIP 34 changed how the generation transaction is serialized, we can only use version/force back to v2 blocks
- // This is safe to do [otherwise-]unconditionally only because we are throwing an exception above if a non-force deployment gets activated
- // Note that this can probably also be removed entirely after the first BIP9 non-force deployment (ie, probably segwit) gets activated
- aMutable.push_back("version/force");
- }
-
result.pushKV("previousblockhash", pblock->hashPrevBlock.GetHex());
result.pushKV("transactions", transactions);
result.pushKV("coinbaseaux", aux);
@@ -997,7 +981,7 @@ static RPCHelpMan submitblock()
bool new_block;
auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash());
RegisterSharedValidationInterface(sc);
- bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*new_block=*/&new_block);
+ bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block);
UnregisterSharedValidationInterface(sc);
if (!new_block && accepted) {
return "duplicate";
@@ -1039,7 +1023,7 @@ static RPCHelpMan submitheader()
}
BlockValidationState state;
- chainman.ProcessNewBlockHeaders({h}, state);
+ chainman.ProcessNewBlockHeaders({h}, /*min_pow_checked=*/true, state);
if (state.IsValid()) return UniValue::VNULL;
if (state.IsError()) {
throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString());
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 059be61bb4..8d7f4e7f5b 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -23,6 +23,7 @@
#include <timedata.h>
#include <util/strencodings.h>
#include <util/string.h>
+#include <util/time.h>
#include <util/translation.h>
#include <validation.h>
#include <version.h>
@@ -113,7 +114,7 @@ static RPCHelpMan getpeerinfo()
{
{RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"}
}},
- {RPCResult::Type::BOOL, "relaytxes", /*optional=*/true, "Whether peer has asked us to relay transactions to it"},
+ {RPCResult::Type::BOOL, "relaytxes", /*optional=*/true, "Whether we relay transactions to this peer"},
{RPCResult::Type::NUM_TIME, "lastsend", "The " + UNIX_EPOCH_TIME + " of the last send"},
{RPCResult::Type::NUM_TIME, "lastrecv", "The " + UNIX_EPOCH_TIME + " of the last receive"},
{RPCResult::Type::NUM_TIME, "last_transaction", "The " + UNIX_EPOCH_TIME + " of the last valid transaction received from this peer"},
@@ -122,15 +123,16 @@ static RPCHelpMan getpeerinfo()
{RPCResult::Type::NUM, "bytesrecv", "The total bytes received"},
{RPCResult::Type::NUM_TIME, "conntime", "The " + UNIX_EPOCH_TIME + " of the connection"},
{RPCResult::Type::NUM, "timeoffset", "The time offset in seconds"},
- {RPCResult::Type::NUM, "pingtime", /*optional=*/true, "ping time (if available)"},
- {RPCResult::Type::NUM, "minping", /*optional=*/true, "minimum observed ping time (if any at all)"},
- {RPCResult::Type::NUM, "pingwait", /*optional=*/true, "ping wait (if non-zero)"},
+ {RPCResult::Type::NUM, "pingtime", /*optional=*/true, "The last ping time in milliseconds (ms), if any"},
+ {RPCResult::Type::NUM, "minping", /*optional=*/true, "The minimum observed ping time in milliseconds (ms), if any"},
+ {RPCResult::Type::NUM, "pingwait", /*optional=*/true, "The duration in milliseconds (ms) of an outstanding ping (if non-zero)"},
{RPCResult::Type::NUM, "version", "The peer version, such as 70001"},
{RPCResult::Type::STR, "subver", "The string version"},
{RPCResult::Type::BOOL, "inbound", "Inbound (true) or Outbound (false)"},
{RPCResult::Type::BOOL, "bip152_hb_to", "Whether we selected peer as (compact blocks) high-bandwidth peer"},
{RPCResult::Type::BOOL, "bip152_hb_from", "Whether peer selected us as (compact blocks) high-bandwidth peer"},
{RPCResult::Type::NUM, "startingheight", /*optional=*/true, "The starting height (block) of the peer"},
+ {RPCResult::Type::NUM, "presynced_headers", /*optional=*/true, "The current height of header pre-synchronization with this peer, or -1 if no low-work sync is in progress"},
{RPCResult::Type::NUM, "synced_headers", /*optional=*/true, "The last header we have in common with this peer"},
{RPCResult::Type::NUM, "synced_blocks", /*optional=*/true, "The last block we have in common with this peer"},
{RPCResult::Type::ARR, "inflight", /*optional=*/true, "",
@@ -144,7 +146,7 @@ static RPCHelpMan getpeerinfo()
{
{RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"},
}},
- {RPCResult::Type::NUM, "minfeefilter", /*optional=*/true, "The minimum fee rate for transactions this peer accepts"},
+ {RPCResult::Type::NUM, "minfeefilter", "The minimum fee rate for transactions this peer accepts"},
{RPCResult::Type::OBJ_DYN, "bytessent_per_msg", "",
{
{RPCResult::Type::NUM, "msg", "The total bytes sent aggregated by message type\n"
@@ -198,6 +200,9 @@ static RPCHelpMan getpeerinfo()
ServiceFlags services{fStateStats ? statestats.their_services : ServiceFlags::NODE_NONE};
obj.pushKV("services", strprintf("%016x", services));
obj.pushKV("servicesnames", GetServicesNames(services));
+ if (fStateStats) {
+ obj.pushKV("relaytxes", statestats.m_relay_txs);
+ }
obj.pushKV("lastsend", count_seconds(stats.m_last_send));
obj.pushKV("lastrecv", count_seconds(stats.m_last_recv));
obj.pushKV("last_transaction", count_seconds(stats.m_last_tx_time));
@@ -225,6 +230,7 @@ static RPCHelpMan getpeerinfo()
obj.pushKV("bip152_hb_from", stats.m_bip152_highbandwidth_from);
if (fStateStats) {
obj.pushKV("startingheight", statestats.m_starting_height);
+ obj.pushKV("presynced_headers", statestats.presync_height);
obj.pushKV("synced_headers", statestats.nSyncHeight);
obj.pushKV("synced_blocks", statestats.nCommonHeight);
UniValue heights(UniValue::VARR);
@@ -232,17 +238,16 @@ static RPCHelpMan getpeerinfo()
heights.push_back(height);
}
obj.pushKV("inflight", heights);
- obj.pushKV("relaytxes", statestats.m_relay_txs);
- obj.pushKV("minfeefilter", ValueFromAmount(statestats.m_fee_filter_received));
obj.pushKV("addr_relay_enabled", statestats.m_addr_relay_enabled);
obj.pushKV("addr_processed", statestats.m_addr_processed);
obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited);
}
UniValue permissions(UniValue::VARR);
- for (const auto& permission : NetPermissions::ToStrings(stats.m_permissionFlags)) {
+ for (const auto& permission : NetPermissions::ToStrings(stats.m_permission_flags)) {
permissions.push_back(permission);
}
obj.pushKV("permissions", permissions);
+ obj.pushKV("minfeefilter", fStateStats ? ValueFromAmount(statestats.m_fee_filter_received) : 0);
UniValue sendPerMsgType(UniValue::VOBJ);
for (const auto& i : stats.mapSendBytesPerMsgType) {
@@ -604,7 +609,7 @@ static RPCHelpMan getnetworkinfo()
}},
}},
{RPCResult::Type::NUM, "relayfee", "minimum relay fee rate for transactions in " + CURRENCY_UNIT + "/kvB"},
- {RPCResult::Type::NUM, "incrementalfee", "minimum fee rate increment for mempool limiting or BIP 125 replacement in " + CURRENCY_UNIT + "/kvB"},
+ {RPCResult::Type::NUM, "incrementalfee", "minimum fee rate increment for mempool limiting or replacement in " + CURRENCY_UNIT + "/kvB"},
{RPCResult::Type::ARR, "localaddresses", "list of local addresses",
{
{RPCResult::Type::OBJ, "", "",
@@ -645,8 +650,11 @@ static RPCHelpMan getnetworkinfo()
obj.pushKV("connections_out", node.connman->GetNodeCount(ConnectionDirection::Out));
}
obj.pushKV("networks", GetNetworksInfo());
- obj.pushKV("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
- obj.pushKV("incrementalfee", ValueFromAmount(::incrementalRelayFee.GetFeePerK()));
+ if (node.mempool) {
+ // Those fields can be deprecated, to be replaced by the getmempoolinfo fields
+ obj.pushKV("relayfee", ValueFromAmount(node.mempool->m_min_relay_feerate.GetFeePerK()));
+ obj.pushKV("incrementalfee", ValueFromAmount(node.mempool->m_incremental_relay_feerate.GetFeePerK()));
+ }
UniValue localAddresses(UniValue::VARR);
{
LOCK(g_maplocalhost_mutex);
@@ -941,8 +949,9 @@ static RPCHelpMan addpeeraddress()
bool success{false};
if (LookupHost(addr_string, net_addr, false)) {
- CAddress address{{net_addr, port}, ServiceFlags{NODE_NETWORK | NODE_WITNESS}};
- address.nTime = AdjustedTime();
+ CService service{net_addr, port};
+ CAddress address{MaybeFlipIPv6toCJDNS(service), ServiceFlags{NODE_NETWORK | NODE_WITNESS}};
+ address.nTime = Now<NodeSeconds>();
// The source address is set equal to the address. This is equivalent to the peer
// announcing itself.
if (node.addrman->Add({address}, address)) {
diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp
index 744f809814..a980c609e8 100644
--- a/src/rpc/output_script.cpp
+++ b/src/rpc/output_script.cpp
@@ -273,7 +273,7 @@ static RPCHelpMan deriveaddresses()
UniValue addresses(UniValue::VARR);
- for (int i = range_begin; i <= range_end; ++i) {
+ for (int64_t i = range_begin; i <= range_end; ++i) {
FlatSigningProvider provider;
std::vector<CScript> scripts;
if (!desc->Expand(i, key_provider, scripts, provider)) {
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 7ffb499330..d654de1862 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -51,7 +51,7 @@ using node::GetTransaction;
using node::NodeContext;
using node::PSBTAnalysis;
-static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, CChainState& active_chainstate)
+static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, Chainstate& active_chainstate)
{
// Call into TxToUniv() in bitcoin-common to decode the transaction hex.
//
@@ -1241,13 +1241,9 @@ static RPCHelpMan decodepsbt()
}
// Taproot tree
- if (output.m_tap_tree.has_value()) {
+ if (!output.m_tap_tree.empty()) {
UniValue tree(UniValue::VARR);
- const auto& tuples = output.m_tap_tree->GetTreeTuples();
- for (const auto& tuple : tuples) {
- uint8_t depth = std::get<0>(tuple);
- uint8_t leaf_ver = std::get<1>(tuple);
- CScript script = std::get<2>(tuple);
+ for (const auto& [depth, leaf_ver, script] : output.m_tap_tree) {
UniValue elem(UniValue::VOBJ);
elem.pushKV("depth", (int)depth);
elem.pushKV("leaf_ver", (int)leaf_ver);
diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp
index b06e9f6e4b..b078ee8b29 100644
--- a/src/rpc/rawtransaction_util.cpp
+++ b/src/rpc/rawtransaction_util.cpp
@@ -160,14 +160,14 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std::
void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins)
{
if (!prevTxsUnival.isNull()) {
- UniValue prevTxs = prevTxsUnival.get_array();
+ const UniValue& prevTxs = prevTxsUnival.get_array();
for (unsigned int idx = 0; idx < prevTxs.size(); ++idx) {
const UniValue& p = prevTxs[idx];
if (!p.isObject()) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "expected object with {\"txid'\",\"vout\",\"scriptPubKey\"}");
}
- UniValue prevOut = p.get_obj();
+ const UniValue& prevOut = p.get_obj();
RPCTypeCheckObj(prevOut,
{
diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp
index 304c265b31..8595fa78bb 100644
--- a/src/rpc/request.cpp
+++ b/src/rpc/request.cpp
@@ -66,16 +66,16 @@ UniValue JSONRPCError(int code, const std::string& message)
*/
static const std::string COOKIEAUTH_USER = "__cookie__";
/** Default name for auth cookie file */
-static const std::string COOKIEAUTH_FILE = ".cookie";
+static const char* const COOKIEAUTH_FILE = ".cookie";
/** Get name of RPC authentication cookie file */
static fs::path GetAuthCookieFile(bool temp=false)
{
- std::string arg = gArgs.GetArg("-rpccookiefile", COOKIEAUTH_FILE);
+ fs::path arg = gArgs.GetPathArg("-rpccookiefile", COOKIEAUTH_FILE);
if (temp) {
arg += ".tmp";
}
- return AbsPathForConfigVal(fs::PathFromString(arg));
+ return AbsPathForConfigVal(arg);
}
bool GenerateAuthCookie(std::string *cookie_out)
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index e9987d73be..1d7bd2eb94 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -21,8 +21,6 @@
#include <mutex>
#include <unordered_map>
-using SteadyClock = std::chrono::steady_clock;
-
static GlobalMutex g_rpc_warmup_mutex;
static std::atomic<bool> g_rpc_running{false};
static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true;
@@ -170,7 +168,7 @@ static RPCHelpMan stop()
// to the client (intended for testing)
"\nRequest a graceful shutdown of " PACKAGE_NAME ".",
{
- {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", "", {}, /*hidden=*/true},
+ {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", RPCArgOptions{.hidden=true}},
},
RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"},
RPCExamples{""},
@@ -468,8 +466,7 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const
static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler)
{
- try
- {
+ try {
RPCCommandExecution execution(request.strMethod);
// Execute, convert arguments to array if necessary
if (request.params.isObject()) {
@@ -477,9 +474,9 @@ static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& req
} else {
return command.actor(request, result, last_handler);
}
- }
- catch (const std::exception& e)
- {
+ } catch (const UniValue::type_error& e) {
+ throw JSONRPCError(RPC_TYPE_ERROR, e.what());
+ } catch (const std::exception& e) {
throw JSONRPCError(RPC_MISC_ERROR, e.what());
}
}
diff --git a/src/rpc/txoutproof.cpp b/src/rpc/txoutproof.cpp
index dcf6c6bee1..cd8b49bfe1 100644
--- a/src/rpc/txoutproof.cpp
+++ b/src/rpc/txoutproof.cpp
@@ -66,7 +66,7 @@ static RPCHelpMan gettxoutproof()
}
} else {
LOCK(cs_main);
- CChainState& active_chainstate = chainman.ActiveChainstate();
+ Chainstate& active_chainstate = chainman.ActiveChainstate();
// Loop through txids and try to find which block they're in. Exit loop once a block is found.
for (const auto& tx : setTxids) {
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 7517f64ea1..3e98e89791 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -50,7 +50,8 @@ void RPCTypeCheck(const UniValue& params,
void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected)
{
if (!typeExpected.typeAny && value.type() != typeExpected.type) {
- throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected type %s, got %s", uvTypeName(typeExpected.type), uvTypeName(value.type())));
+ throw JSONRPCError(RPC_TYPE_ERROR,
+ strprintf("JSON value of type %s is not of expected type %s", uvTypeName(value.type()), uvTypeName(typeExpected.type)));
}
}
@@ -98,7 +99,7 @@ CAmount AmountFromValue(const UniValue& value, int decimals)
uint256 ParseHashV(const UniValue& v, std::string strName)
{
- std::string strHex(v.get_str());
+ const std::string& strHex(v.get_str());
if (64 != strHex.length())
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be of length %d (not %d, for '%s')", strName, 64, strHex.length(), strHex));
if (!IsHex(strHex)) // Note: IsHex("") is false
@@ -417,8 +418,8 @@ struct Sections {
case RPCArg::Type::BOOL: {
if (outer_type == OuterType::NONE) return; // Nothing more to do for non-recursive types on first recursion
auto left = indent;
- if (arg.m_type_str.size() != 0 && push_name) {
- left += "\"" + arg.GetName() + "\": " + arg.m_type_str.at(0);
+ if (arg.m_opts.type_str.size() != 0 && push_name) {
+ left += "\"" + arg.GetName() + "\": " + arg.m_opts.type_str.at(0);
} else {
left += push_name ? arg.ToStringObj(/*oneline=*/false) : arg.ToString(/*oneline=*/false);
}
@@ -617,7 +618,7 @@ std::string RPCHelpMan::ToString() const
ret += m_name;
bool was_optional{false};
for (const auto& arg : m_args) {
- if (arg.m_hidden) break; // Any arg that follows is also hidden
+ if (arg.m_opts.hidden) break; // Any arg that follows is also hidden
const bool optional = arg.IsOptional();
ret += " ";
if (optional) {
@@ -638,7 +639,7 @@ std::string RPCHelpMan::ToString() const
Sections sections;
for (size_t i{0}; i < m_args.size(); ++i) {
const auto& arg = m_args.at(i);
- if (arg.m_hidden) break; // Any arg that follows is also hidden
+ if (arg.m_opts.hidden) break; // Any arg that follows is also hidden
if (i == 0) ret += "\nArguments:\n";
@@ -703,8 +704,8 @@ std::string RPCArg::ToDescriptionString() const
{
std::string ret;
ret += "(";
- if (m_type_str.size() != 0) {
- ret += m_type_str.at(1);
+ if (m_opts.type_str.size() != 0) {
+ ret += m_opts.type_str.at(1);
} else {
switch (m_type) {
case Type::STR_HEX:
@@ -990,7 +991,7 @@ std::string RPCArg::ToStringObj(const bool oneline) const
std::string RPCArg::ToString(const bool oneline) const
{
- if (oneline && !m_oneline_description.empty()) return m_oneline_description;
+ if (oneline && !m_opts.oneline_description.empty()) return m_opts.oneline_description;
switch (m_type) {
case Type::STR_HEX:
diff --git a/src/rpc/util.h b/src/rpc/util.h
index e883dc008e..9aa5df00b1 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -137,6 +137,12 @@ enum class OuterType {
NONE, // Only set on first recursion
};
+struct RPCArgOptions {
+ std::string oneline_description{}; //!< Should be empty unless it is supposed to override the auto-generated summary line
+ std::vector<std::string> type_str{}; //!< Should be empty unless it is supposed to override the auto-generated type strings. Vector length is either 0 or 2, m_opts.type_str.at(0) will override the type of the value in a key-value pair, m_opts.type_str.at(1) will override the type in the argument description.
+ bool hidden{false}; //!< For testing only
+};
+
struct RPCArg {
enum class Type {
OBJ,
@@ -169,30 +175,25 @@ struct RPCArg {
using DefaultHint = std::string;
using Default = UniValue;
using Fallback = std::variant<Optional, /* hint for default value */ DefaultHint, /* default constant value */ Default>;
+
const std::string m_names; //!< The name of the arg (can be empty for inner args, can contain multiple aliases separated by | for named request arguments)
const Type m_type;
- const bool m_hidden;
const std::vector<RPCArg> m_inner; //!< Only used for arrays or dicts
const Fallback m_fallback;
const std::string m_description;
- const std::string m_oneline_description; //!< Should be empty unless it is supposed to override the auto-generated summary line
- const std::vector<std::string> m_type_str; //!< Should be empty unless it is supposed to override the auto-generated type strings. Vector length is either 0 or 2, m_type_str.at(0) will override the type of the value in a key-value pair, m_type_str.at(1) will override the type in the argument description.
+ const RPCArgOptions m_opts;
RPCArg(
const std::string name,
const Type type,
const Fallback fallback,
const std::string description,
- const std::string oneline_description = "",
- const std::vector<std::string> type_str = {},
- const bool hidden = false)
+ RPCArgOptions opts = {})
: m_names{std::move(name)},
m_type{std::move(type)},
- m_hidden{hidden},
m_fallback{std::move(fallback)},
m_description{std::move(description)},
- m_oneline_description{std::move(oneline_description)},
- m_type_str{std::move(type_str)}
+ m_opts{std::move(opts)}
{
CHECK_NONFATAL(type != Type::ARR && type != Type::OBJ && type != Type::OBJ_USER_KEYS);
}
@@ -203,16 +204,13 @@ struct RPCArg {
const Fallback fallback,
const std::string description,
const std::vector<RPCArg> inner,
- const std::string oneline_description = "",
- const std::vector<std::string> type_str = {})
+ RPCArgOptions opts = {})
: m_names{std::move(name)},
m_type{std::move(type)},
- m_hidden{false},
m_inner{std::move(inner)},
m_fallback{std::move(fallback)},
m_description{std::move(description)},
- m_oneline_description{std::move(oneline_description)},
- m_type_str{std::move(type_str)}
+ m_opts{std::move(opts)}
{
CHECK_NONFATAL(type == Type::ARR || type == Type::OBJ || type == Type::OBJ_USER_KEYS);
}
@@ -227,7 +225,7 @@ struct RPCArg {
/**
* Return the type string of the argument.
- * Set oneline to allow it to be overridden by a custom oneline type string (m_oneline_description).
+ * Set oneline to allow it to be overridden by a custom oneline type string (m_opts.oneline_description).
*/
std::string ToString(bool oneline) const;
/**
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 34a4da74f8..864eb8864f 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -328,7 +328,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider
{
if (!GetExtKey(arg, xprv)) return false;
for (auto entry : m_path) {
- xprv.Derive(xprv, entry);
+ if (!xprv.Derive(xprv, entry)) return false;
if (entry >> 31) {
last_hardened = xprv;
}
@@ -388,14 +388,13 @@ public:
}
} else {
for (auto entry : m_path) {
- der = parent_extkey.Derive(parent_extkey, entry);
- assert(der);
+ if (!parent_extkey.Derive(parent_extkey, entry)) return false;
}
final_extkey = parent_extkey;
if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos);
assert(m_derive != DeriveType::HARDENED);
}
- assert(der);
+ if (!der) return false;
final_info_out = final_info_out_tmp;
key_out = final_extkey.pubkey;
@@ -498,8 +497,8 @@ public:
CExtKey extkey;
CExtKey dummy;
if (!GetDerivedExtKey(arg, extkey, dummy)) return false;
- if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos);
- if (m_derive == DeriveType::HARDENED) extkey.Derive(extkey, pos | 0x80000000UL);
+ if (m_derive == DeriveType::UNHARDENED && !extkey.Derive(extkey, pos)) return false;
+ if (m_derive == DeriveType::HARDENED && !extkey.Derive(extkey, pos | 0x80000000UL)) return false;
key = extkey.key;
return true;
}
@@ -573,7 +572,7 @@ public:
if (pos++) ret += ",";
std::string tmp;
if (!scriptarg->ToStringHelper(arg, tmp, type, cache)) return false;
- ret += std::move(tmp);
+ ret += tmp;
}
return true;
}
@@ -597,7 +596,7 @@ public:
tmp = pubkey->ToString();
break;
}
- ret += std::move(tmp);
+ ret += tmp;
}
std::string subscript;
if (!ToStringSubScriptHelper(arg, subscript, type, cache)) return false;
@@ -613,7 +612,7 @@ public:
return AddChecksum(ret);
}
- bool ToPrivateString(const SigningProvider& arg, std::string& out) const final
+ bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
{
bool ret = ToStringHelper(&arg, out, StringType::PRIVATE);
out = AddChecksum(out);
@@ -645,7 +644,7 @@ public:
assert(outscripts.size() == 1);
subscripts.emplace_back(std::move(outscripts[0]));
}
- out = Merge(std::move(out), std::move(subprovider));
+ out.Merge(std::move(subprovider));
std::vector<CPubKey> pubkeys;
pubkeys.reserve(entries.size());
@@ -699,6 +698,7 @@ public:
return OutputTypeFromDestination(m_destination);
}
bool IsSingleType() const final { return true; }
+ bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }
};
/** A parsed raw(H) descriptor. */
@@ -719,6 +719,7 @@ public:
return OutputTypeFromDestination(dest);
}
bool IsSingleType() const final { return true; }
+ bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }
};
/** A parsed pk(P) descriptor. */
@@ -913,7 +914,7 @@ protected:
}
std::string tmp;
if (!m_subdescriptor_args[pos]->ToStringHelper(arg, tmp, type, cache)) return false;
- ret += std::move(tmp);
+ ret += tmp;
while (!path.empty() && path.back()) {
if (path.size() > 1) ret += '}';
path.pop_back();
@@ -1015,6 +1016,24 @@ public:
bool IsSingleType() const final { return true; }
};
+/** A parsed rawtr(...) descriptor. */
+class RawTRDescriptor final : public DescriptorImpl
+{
+protected:
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts, FlatSigningProvider& out) const override
+ {
+ assert(keys.size() == 1);
+ XOnlyPubKey xpk(keys[0]);
+ if (!xpk.IsFullyValid()) return {};
+ WitnessV1Taproot output{xpk};
+ return Vector(GetScriptForDestination(output));
+ }
+public:
+ RawTRDescriptor(std::unique_ptr<PubkeyProvider> output_key) : DescriptorImpl(Vector(std::move(output_key)), "rawtr") {}
+ std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
+ bool IsSingleType() const final { return true; }
+};
+
////////////////////////////////////////////////////////////////////////////
// Parser //
////////////////////////////////////////////////////////////////////////////
@@ -1453,6 +1472,20 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
error = "Can only have tr at top level";
return nullptr;
}
+ if (ctx == ParseScriptContext::TOP && Func("rawtr", expr)) {
+ auto arg = Expr(expr);
+ if (expr.size()) {
+ error = strprintf("rawtr(): only one key expected.");
+ return nullptr;
+ }
+ auto output_key = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error);
+ if (!output_key) return nullptr;
+ ++key_exp_index;
+ return std::make_unique<RawTRDescriptor>(std::move(output_key));
+ } else if (Func("rawtr", expr)) {
+ error = "Can only have rawtr at top level";
+ return nullptr;
+ }
if (ctx == ParseScriptContext::TOP && Func("raw", expr)) {
std::string str(expr.begin(), expr.end());
if (!IsHex(str)) {
@@ -1626,6 +1659,13 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
}
}
}
+ // If the above doesn't work, construct a rawtr() descriptor with just the encoded x-only pubkey.
+ if (pubkey.IsFullyValid()) {
+ auto key = InferXOnlyPubkey(pubkey, ParseScriptContext::P2TR, provider);
+ if (key) {
+ return std::make_unique<RawTRDescriptor>(std::move(key));
+ }
+ }
}
if (ctx == ParseScriptContext::P2WSH) {
diff --git a/src/script/interpreter.h b/src/script/interpreter.h
index f91578d684..ba910cc945 100644
--- a/src/script/interpreter.h
+++ b/src/script/interpreter.h
@@ -307,10 +307,10 @@ using MutableTransactionSignatureChecker = GenericTransactionSignatureChecker<CM
class DeferringSignatureChecker : public BaseSignatureChecker
{
protected:
- BaseSignatureChecker& m_checker;
+ const BaseSignatureChecker& m_checker;
public:
- DeferringSignatureChecker(BaseSignatureChecker& checker) : m_checker(checker) {}
+ DeferringSignatureChecker(const BaseSignatureChecker& checker) : m_checker(checker) {}
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override
{
diff --git a/src/script/miniscript.h b/src/script/miniscript.h
index f783d1dafc..6faf2624fd 100644
--- a/src/script/miniscript.h
+++ b/src/script/miniscript.h
@@ -13,8 +13,8 @@
#include <string>
#include <vector>
-#include <stdlib.h>
#include <assert.h>
+#include <cstdlib>
#include <policy/policy.h>
#include <primitives/transaction.h>
@@ -276,6 +276,8 @@ struct StackSize {
StackSize(MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : sat(in_sat), dsat(in_dsat) {};
};
+struct NoDupCheck {};
+
} // namespace internal
//! A node in a miniscript expression.
@@ -301,8 +303,13 @@ private:
const Type typ;
//! Cached script length (computed by CalcScriptLen).
const size_t scriptlen;
- //! Whether a public key appears more than once in this node.
- const bool duplicate_key;
+ //! Whether a public key appears more than once in this node. This value is initialized
+ //! by all constructors except the NoDupCheck ones. The NoDupCheck ones skip the
+ //! computation, requiring it to be done manually by invoking DuplicateKeyCheck().
+ //! DuplicateKeyCheck(), or a non-NoDupCheck constructor, will compute has_duplicate_keys
+ //! for all subnodes as well.
+ mutable std::optional<bool> has_duplicate_keys;
+
//! Compute the length of the script for this miniscript (including children).
size_t CalcScriptLen() const {
@@ -654,6 +661,7 @@ public:
return TreeEvalMaybe<std::string>(false, downfn, upfn);
}
+private:
internal::Ops CalcOps() const {
switch (fragment) {
case Fragment::JUST_1: return {0, 0, {}};
@@ -777,11 +785,14 @@ public:
assert(false);
}
- /** Check whether any key is repeated.
+public:
+ /** Update duplicate key information in this Node.
+ *
* This uses a custom key comparator provided by the context in order to still detect duplicates
* for more complicated types.
*/
- template<typename Ctx> bool ContainsDuplicateKey(const Ctx& ctx) const {
+ template<typename Ctx> void DuplicateKeyCheck(const Ctx& ctx) const
+ {
// We cannot use a lambda here, as lambdas are non assignable, and the set operations
// below require moving the comparators around.
struct Comp {
@@ -789,31 +800,55 @@ public:
Comp(const Ctx& ctx) : ctx_ptr(&ctx) {}
bool operator()(const Key& a, const Key& b) const { return ctx_ptr->KeyCompare(a, b); }
};
- using set = std::set<Key, Comp>;
- auto upfn = [this, &ctx](const Node& node, Span<set> subs) -> std::optional<set> {
- if (&node != this && node.duplicate_key) return {};
+ // state in the recursive computation:
+ // - std::nullopt means "this node has duplicates"
+ // - an std::set means "this node has no duplicate keys, and they are: ...".
+ using keyset = std::set<Key, Comp>;
+ using state = std::optional<keyset>;
+
+ auto upfn = [&ctx](const Node& node, Span<state> subs) -> state {
+ // If this node is already known to have duplicates, nothing left to do.
+ if (node.has_duplicate_keys.has_value() && *node.has_duplicate_keys) return {};
+
+ // Check if one of the children is already known to have duplicates.
+ for (auto& sub : subs) {
+ if (!sub.has_value()) {
+ node.has_duplicate_keys = true;
+ return {};
+ }
+ }
+ // Start building the set of keys involved in this node and children.
+ // Start by keys in this node directly.
size_t keys_count = node.keys.size();
- set key_set{node.keys.begin(), node.keys.end(), Comp(ctx)};
- if (key_set.size() != keys_count) return {};
+ keyset key_set{node.keys.begin(), node.keys.end(), Comp(ctx)};
+ if (key_set.size() != keys_count) {
+ // It already has duplicates; bail out.
+ node.has_duplicate_keys = true;
+ return {};
+ }
- for (auto& sub: subs) {
- keys_count += sub.size();
+ // Merge the keys from the children into this set.
+ for (auto& sub : subs) {
+ keys_count += sub->size();
// Small optimization: std::set::merge is linear in the size of the second arg but
// logarithmic in the size of the first.
- if (key_set.size() < sub.size()) std::swap(key_set, sub);
- key_set.merge(sub);
- if (key_set.size() != keys_count) return {};
+ if (key_set.size() < sub->size()) std::swap(key_set, *sub);
+ key_set.merge(*sub);
+ if (key_set.size() != keys_count) {
+ node.has_duplicate_keys = true;
+ return {};
+ }
}
+ node.has_duplicate_keys = false;
return key_set;
};
- return !TreeEvalMaybe<set>(upfn);
+ TreeEval<state>(upfn);
}
-public:
//! Return the size of the script for this expression (faster than ToScript().size()).
size_t ScriptSize() const { return scriptlen; }
@@ -858,7 +893,7 @@ public:
bool CheckTimeLocksMix() const { return GetType() << "k"_mst; }
//! Check whether there is no duplicate key across this fragment and all its sub-fragments.
- bool CheckDuplicateKey() const { return !duplicate_key; }
+ bool CheckDuplicateKey() const { return has_duplicate_keys && !*has_duplicate_keys; }
//! Whether successful non-malleable satisfactions are guaranteed to be valid.
bool ValidSatisfactions() const { return IsValid() && CheckOpsLimit() && CheckStackSize(); }
@@ -872,13 +907,21 @@ public:
//! Equality testing.
bool operator==(const Node<Key>& arg) const { return Compare(*this, arg) == 0; }
- // Constructors with various argument combinations.
- template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {}
- template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {}
- template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {}
- template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {}
- template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : fragment(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {}
- template <typename Ctx> Node(const Ctx& ctx, Fragment nt, uint32_t val = 0) : fragment(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {}
+ // Constructors with various argument combinations, which bypass the duplicate key check.
+ Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
+ Node(internal::NoDupCheck, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
+ Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
+ Node(internal::NoDupCheck, Fragment nt, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
+ Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : fragment(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
+ Node(internal::NoDupCheck, Fragment nt, uint32_t val = 0) : fragment(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
+
+ // Constructors with various argument combinations, which do perform the duplicate key check.
+ template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(sub), std::move(arg), val) { DuplicateKeyCheck(ctx); }
+ template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(arg), val) { DuplicateKeyCheck(ctx);}
+ template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(sub), std::move(key), val) { DuplicateKeyCheck(ctx); }
+ template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(key), val) { DuplicateKeyCheck(ctx); }
+ template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(sub), val) { DuplicateKeyCheck(ctx); }
+ template <typename Ctx> Node(const Ctx& ctx, Fragment nt, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, val) { DuplicateKeyCheck(ctx); }
};
namespace internal {
@@ -965,15 +1008,15 @@ std::optional<std::pair<std::vector<unsigned char>, int>> ParseHexStrEnd(Span<co
}
/** BuildBack pops the last two elements off `constructed` and wraps them in the specified Fragment */
-template<typename Key, typename Ctx>
-void BuildBack(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false)
+template<typename Key>
+void BuildBack(Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false)
{
NodeRef<Key> child = std::move(constructed.back());
constructed.pop_back();
if (reverse) {
- constructed.back() = MakeNodeRef<Key>(ctx, nt, Vector(std::move(child), std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, nt, Vector(std::move(child), std::move(constructed.back())));
} else {
- constructed.back() = MakeNodeRef<Key>(ctx, nt, Vector(std::move(constructed.back()), std::move(child)));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, nt, Vector(std::move(constructed.back()), std::move(child)));
}
}
@@ -987,6 +1030,18 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
{
using namespace spanparsing;
+ // Account for the minimum script size for all parsed fragments so far. It "borrows" 1
+ // script byte from all leaf nodes, counting it instead whenever a space for a recursive
+ // expression is added (through andor, and_*, or_*, thresh). This guarantees that all fragments
+ // increment the script_size by at least one, except for:
+ // - "0", "1": these leafs are only a single byte, so their subtracted-from increment is 0.
+ // This is not an issue however, as "space" for them has to be created by combinators,
+ // which do increment script_size.
+ // - "v:": the v wrapper adds nothing as in some cases it results in no opcode being added
+ // (instead transforming another opcode into its VERIFY form). However, the v: wrapper has
+ // to be interleaved with other fragments to be valid, so this is not a concern.
+ size_t script_size{1};
+
// The two integers are used to hold state for thresh()
std::vector<std::tuple<ParseContext, int64_t, int64_t>> to_parse;
std::vector<NodeRef<Key>> constructed;
@@ -994,14 +1049,16 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
while (!to_parse.empty()) {
+ if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
+
// Get the current context we are decoding within
auto [cur_context, n, k] = to_parse.back();
to_parse.pop_back();
switch (cur_context) {
case ParseContext::WRAPPED_EXPR: {
- int colon_index = -1;
- for (int i = 1; i < (int)in.size(); ++i) {
+ std::optional<size_t> colon_index{};
+ for (size_t i = 1; i < in.size(); ++i) {
if (in[i] == ':') {
colon_index = i;
break;
@@ -1009,106 +1066,131 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
if (in[i] < 'a' || in[i] > 'z') break;
}
// If there is no colon, this loop won't execute
- for (int j = 0; j < colon_index; ++j) {
+ bool last_was_v{false};
+ for (size_t j = 0; colon_index && j < *colon_index; ++j) {
+ if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
if (in[j] == 'a') {
+ script_size += 2;
to_parse.emplace_back(ParseContext::ALT, -1, -1);
} else if (in[j] == 's') {
+ script_size += 1;
to_parse.emplace_back(ParseContext::SWAP, -1, -1);
} else if (in[j] == 'c') {
+ script_size += 1;
to_parse.emplace_back(ParseContext::CHECK, -1, -1);
} else if (in[j] == 'd') {
+ script_size += 3;
to_parse.emplace_back(ParseContext::DUP_IF, -1, -1);
} else if (in[j] == 'j') {
+ script_size += 4;
to_parse.emplace_back(ParseContext::NON_ZERO, -1, -1);
} else if (in[j] == 'n') {
+ script_size += 1;
to_parse.emplace_back(ParseContext::ZERO_NOTEQUAL, -1, -1);
} else if (in[j] == 'v') {
+ // do not permit "...vv...:"; it's not valid, and also doesn't trigger early
+ // failure as script_size isn't incremented.
+ if (last_was_v) return {};
to_parse.emplace_back(ParseContext::VERIFY, -1, -1);
} else if (in[j] == 'u') {
+ script_size += 4;
to_parse.emplace_back(ParseContext::WRAP_U, -1, -1);
} else if (in[j] == 't') {
+ script_size += 1;
to_parse.emplace_back(ParseContext::WRAP_T, -1, -1);
} else if (in[j] == 'l') {
// The l: wrapper is equivalent to or_i(0,X)
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0));
+ script_size += 4;
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0));
to_parse.emplace_back(ParseContext::OR_I, -1, -1);
} else {
return {};
}
+ last_was_v = (in[j] == 'v');
}
to_parse.emplace_back(ParseContext::EXPR, -1, -1);
- in = in.subspan(colon_index + 1);
+ if (colon_index) in = in.subspan(*colon_index + 1);
break;
}
case ParseContext::EXPR: {
if (Const("0", in)) {
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0));
} else if (Const("1", in)) {
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_1));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1));
} else if (Const("pk(", in)) {
auto res = ParseKeyEnd<Key, Ctx>(in, ctx);
if (!res) return {};
auto& [key, key_size] = *res;
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(key))))));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(key))))));
in = in.subspan(key_size + 1);
+ script_size += 34;
} else if (Const("pkh(", in)) {
auto res = ParseKeyEnd<Key>(in, ctx);
if (!res) return {};
auto& [key, key_size] = *res;
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(key))))));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(key))))));
in = in.subspan(key_size + 1);
+ script_size += 24;
} else if (Const("pk_k(", in)) {
auto res = ParseKeyEnd<Key>(in, ctx);
if (!res) return {};
auto& [key, key_size] = *res;
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(key))));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(key))));
in = in.subspan(key_size + 1);
+ script_size += 33;
} else if (Const("pk_h(", in)) {
auto res = ParseKeyEnd<Key>(in, ctx);
if (!res) return {};
auto& [key, key_size] = *res;
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(key))));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(key))));
in = in.subspan(key_size + 1);
+ script_size += 23;
} else if (Const("sha256(", in)) {
auto res = ParseHexStrEnd(in, 32, ctx);
if (!res) return {};
auto& [hash, hash_size] = *res;
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::SHA256, std::move(hash)));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::SHA256, std::move(hash)));
in = in.subspan(hash_size + 1);
+ script_size += 38;
} else if (Const("ripemd160(", in)) {
auto res = ParseHexStrEnd(in, 20, ctx);
if (!res) return {};
auto& [hash, hash_size] = *res;
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::RIPEMD160, std::move(hash)));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::RIPEMD160, std::move(hash)));
in = in.subspan(hash_size + 1);
+ script_size += 26;
} else if (Const("hash256(", in)) {
auto res = ParseHexStrEnd(in, 32, ctx);
if (!res) return {};
auto& [hash, hash_size] = *res;
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH256, std::move(hash)));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH256, std::move(hash)));
in = in.subspan(hash_size + 1);
+ script_size += 38;
} else if (Const("hash160(", in)) {
auto res = ParseHexStrEnd(in, 20, ctx);
if (!res) return {};
auto& [hash, hash_size] = *res;
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH160, std::move(hash)));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH160, std::move(hash)));
in = in.subspan(hash_size + 1);
+ script_size += 26;
} else if (Const("after(", in)) {
int arg_size = FindNextChar(in, ')');
if (arg_size < 1) return {};
int64_t num;
if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {};
if (num < 1 || num >= 0x80000000L) return {};
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::AFTER, num));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AFTER, num));
in = in.subspan(arg_size + 1);
+ script_size += 1 + (num > 16) + (num > 0x7f) + (num > 0x7fff) + (num > 0x7fffff);
} else if (Const("older(", in)) {
int arg_size = FindNextChar(in, ')');
if (arg_size < 1) return {};
int64_t num;
if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {};
if (num < 1 || num >= 0x80000000L) return {};
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::OLDER, num));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OLDER, num));
in = in.subspan(arg_size + 1);
+ script_size += 1 + (num > 16) + (num > 0x7f) + (num > 0x7fff) + (num > 0x7fffff);
} else if (Const("multi(", in)) {
// Get threshold
int next_comma = FindNextChar(in, ',');
@@ -1128,7 +1210,8 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
}
if (keys.size() < 1 || keys.size() > 20) return {};
if (k < 1 || k > (int64_t)keys.size()) return {};
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::MULTI, std::move(keys), k));
+ script_size += 2 + (keys.size() > 16) + (k > 16) + 34 * keys.size();
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::MULTI, std::move(keys), k));
} else if (Const("thresh(", in)) {
int next_comma = FindNextChar(in, ',');
if (next_comma < 1) return {};
@@ -1138,6 +1221,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
// n = 1 here because we read the first WRAPPED_EXPR before reaching THRESH
to_parse.emplace_back(ParseContext::THRESH, 1, k);
to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
+ script_size += 2 + (k > 16) + (k > 0x7f) + (k > 0x7fff) + (k > 0x7fffff);
} else if (Const("andor(", in)) {
to_parse.emplace_back(ParseContext::ANDOR, -1, -1);
to_parse.emplace_back(ParseContext::CLOSE_BRACKET, -1, -1);
@@ -1146,21 +1230,29 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
to_parse.emplace_back(ParseContext::COMMA, -1, -1);
to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
+ script_size += 5;
} else {
if (Const("and_n(", in)) {
to_parse.emplace_back(ParseContext::AND_N, -1, -1);
+ script_size += 5;
} else if (Const("and_b(", in)) {
to_parse.emplace_back(ParseContext::AND_B, -1, -1);
+ script_size += 2;
} else if (Const("and_v(", in)) {
to_parse.emplace_back(ParseContext::AND_V, -1, -1);
+ script_size += 1;
} else if (Const("or_b(", in)) {
to_parse.emplace_back(ParseContext::OR_B, -1, -1);
+ script_size += 2;
} else if (Const("or_c(", in)) {
to_parse.emplace_back(ParseContext::OR_C, -1, -1);
+ script_size += 3;
} else if (Const("or_d(", in)) {
to_parse.emplace_back(ParseContext::OR_D, -1, -1);
+ script_size += 4;
} else if (Const("or_i(", in)) {
to_parse.emplace_back(ParseContext::OR_I, -1, -1);
+ script_size += 4;
} else {
return {};
}
@@ -1172,69 +1264,70 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
break;
}
case ParseContext::ALT: {
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_A, Vector(std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_A, Vector(std::move(constructed.back())));
break;
}
case ParseContext::SWAP: {
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_S, Vector(std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_S, Vector(std::move(constructed.back())));
break;
}
case ParseContext::CHECK: {
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(std::move(constructed.back())));
break;
}
case ParseContext::DUP_IF: {
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_D, Vector(std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_D, Vector(std::move(constructed.back())));
break;
}
case ParseContext::NON_ZERO: {
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_J, Vector(std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_J, Vector(std::move(constructed.back())));
break;
}
case ParseContext::ZERO_NOTEQUAL: {
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_N, Vector(std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_N, Vector(std::move(constructed.back())));
break;
}
case ParseContext::VERIFY: {
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_V, Vector(std::move(constructed.back())));
+ script_size += (constructed.back()->GetType() << "x"_mst);
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_V, Vector(std::move(constructed.back())));
break;
}
case ParseContext::WRAP_U: {
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(ctx, Fragment::JUST_0)));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0)));
break;
}
case ParseContext::WRAP_T: {
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(ctx, Fragment::JUST_1)));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1)));
break;
}
case ParseContext::AND_B: {
- BuildBack(ctx, Fragment::AND_B, constructed);
+ BuildBack(Fragment::AND_B, constructed);
break;
}
case ParseContext::AND_N: {
auto mid = std::move(constructed.back());
constructed.pop_back();
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(ctx, Fragment::JUST_0)));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(ctx, Fragment::JUST_0)));
break;
}
case ParseContext::AND_V: {
- BuildBack(ctx, Fragment::AND_V, constructed);
+ BuildBack(Fragment::AND_V, constructed);
break;
}
case ParseContext::OR_B: {
- BuildBack(ctx, Fragment::OR_B, constructed);
+ BuildBack(Fragment::OR_B, constructed);
break;
}
case ParseContext::OR_C: {
- BuildBack(ctx, Fragment::OR_C, constructed);
+ BuildBack(Fragment::OR_C, constructed);
break;
}
case ParseContext::OR_D: {
- BuildBack(ctx, Fragment::OR_D, constructed);
+ BuildBack(Fragment::OR_D, constructed);
break;
}
case ParseContext::OR_I: {
- BuildBack(ctx, Fragment::OR_I, constructed);
+ BuildBack(Fragment::OR_I, constructed);
break;
}
case ParseContext::ANDOR: {
@@ -1242,7 +1335,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
constructed.pop_back();
auto mid = std::move(constructed.back());
constructed.pop_back();
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right)));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right)));
break;
}
case ParseContext::THRESH: {
@@ -1251,6 +1344,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
in = in.subspan(1);
to_parse.emplace_back(ParseContext::THRESH, n+1, k);
to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1);
+ script_size += 2;
} else if (in[0] == ')') {
if (k > n) return {};
in = in.subspan(1);
@@ -1261,7 +1355,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
constructed.pop_back();
}
std::reverse(subs.begin(), subs.end());
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::THRESH, std::move(subs), k));
} else {
return {};
}
@@ -1282,8 +1376,11 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
// Sanity checks on the produced miniscript
assert(constructed.size() == 1);
+ assert(constructed[0]->ScriptSize() == script_size);
if (in.size() > 0) return {};
- return std::move(constructed.front());
+ const NodeRef<Key> tl_node = std::move(constructed.front());
+ tl_node->DuplicateKeyCheck(ctx);
+ return tl_node;
}
/** Decode a script into opcode/push pairs.
@@ -1394,12 +1491,12 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
// Constants
if (in[0].first == OP_1) {
++in;
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_1));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1));
break;
}
if (in[0].first == OP_0) {
++in;
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0));
break;
}
// Public keys
@@ -1407,14 +1504,14 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
auto key = ctx.FromPKBytes(in[0].second.begin(), in[0].second.end());
if (!key) return {};
++in;
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(*key))));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(*key))));
break;
}
if (last - in >= 5 && in[0].first == OP_VERIFY && in[1].first == OP_EQUAL && in[3].first == OP_HASH160 && in[4].first == OP_DUP && in[2].second.size() == 20) {
auto key = ctx.FromPKHBytes(in[2].second.begin(), in[2].second.end());
if (!key) return {};
in += 5;
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(*key))));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(*key))));
break;
}
// Time locks
@@ -1422,31 +1519,31 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
if (last - in >= 2 && in[0].first == OP_CHECKSEQUENCEVERIFY && (num = ParseScriptNumber(in[1]))) {
in += 2;
if (*num < 1 || *num > 0x7FFFFFFFL) return {};
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::OLDER, *num));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OLDER, *num));
break;
}
if (last - in >= 2 && in[0].first == OP_CHECKLOCKTIMEVERIFY && (num = ParseScriptNumber(in[1]))) {
in += 2;
if (num < 1 || num > 0x7FFFFFFFL) return {};
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::AFTER, *num));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AFTER, *num));
break;
}
// Hashes
if (last - in >= 7 && in[0].first == OP_EQUAL && in[3].first == OP_VERIFY && in[4].first == OP_EQUAL && (num = ParseScriptNumber(in[5])) && num == 32 && in[6].first == OP_SIZE) {
if (in[2].first == OP_SHA256 && in[1].second.size() == 32) {
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::SHA256, in[1].second));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::SHA256, in[1].second));
in += 7;
break;
} else if (in[2].first == OP_RIPEMD160 && in[1].second.size() == 20) {
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::RIPEMD160, in[1].second));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::RIPEMD160, in[1].second));
in += 7;
break;
} else if (in[2].first == OP_HASH256 && in[1].second.size() == 32) {
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH256, in[1].second));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH256, in[1].second));
in += 7;
break;
} else if (in[2].first == OP_HASH160 && in[1].second.size() == 20) {
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH160, in[1].second));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH160, in[1].second));
in += 7;
break;
}
@@ -1467,7 +1564,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
if (!k || *k < 1 || *k > *n) return {};
in += 3 + *n;
std::reverse(keys.begin(), keys.end());
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::MULTI, std::move(keys), *k));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::MULTI, std::move(keys), *k));
break;
}
/** In the following wrappers, we only need to push SINGLE_BKV_EXPR rather
@@ -1562,63 +1659,63 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
case DecodeContext::SWAP: {
if (in >= last || in[0].first != OP_SWAP || constructed.empty()) return {};
++in;
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_S, Vector(std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_S, Vector(std::move(constructed.back())));
break;
}
case DecodeContext::ALT: {
if (in >= last || in[0].first != OP_TOALTSTACK || constructed.empty()) return {};
++in;
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_A, Vector(std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_A, Vector(std::move(constructed.back())));
break;
}
case DecodeContext::CHECK: {
if (constructed.empty()) return {};
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(std::move(constructed.back())));
break;
}
case DecodeContext::DUP_IF: {
if (constructed.empty()) return {};
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_D, Vector(std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_D, Vector(std::move(constructed.back())));
break;
}
case DecodeContext::VERIFY: {
if (constructed.empty()) return {};
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_V, Vector(std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_V, Vector(std::move(constructed.back())));
break;
}
case DecodeContext::NON_ZERO: {
if (constructed.empty()) return {};
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_J, Vector(std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_J, Vector(std::move(constructed.back())));
break;
}
case DecodeContext::ZERO_NOTEQUAL: {
if (constructed.empty()) return {};
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_N, Vector(std::move(constructed.back())));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_N, Vector(std::move(constructed.back())));
break;
}
case DecodeContext::AND_V: {
if (constructed.size() < 2) return {};
- BuildBack(ctx, Fragment::AND_V, constructed, /*reverse=*/true);
+ BuildBack(Fragment::AND_V, constructed, /*reverse=*/true);
break;
}
case DecodeContext::AND_B: {
if (constructed.size() < 2) return {};
- BuildBack(ctx, Fragment::AND_B, constructed, /*reverse=*/true);
+ BuildBack(Fragment::AND_B, constructed, /*reverse=*/true);
break;
}
case DecodeContext::OR_B: {
if (constructed.size() < 2) return {};
- BuildBack(ctx, Fragment::OR_B, constructed, /*reverse=*/true);
+ BuildBack(Fragment::OR_B, constructed, /*reverse=*/true);
break;
}
case DecodeContext::OR_C: {
if (constructed.size() < 2) return {};
- BuildBack(ctx, Fragment::OR_C, constructed, /*reverse=*/true);
+ BuildBack(Fragment::OR_C, constructed, /*reverse=*/true);
break;
}
case DecodeContext::OR_D: {
if (constructed.size() < 2) return {};
- BuildBack(ctx, Fragment::OR_D, constructed, /*reverse=*/true);
+ BuildBack(Fragment::OR_D, constructed, /*reverse=*/true);
break;
}
case DecodeContext::ANDOR: {
@@ -1628,7 +1725,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
NodeRef<Key> right = std::move(constructed.back());
constructed.pop_back();
NodeRef<Key> mid = std::move(constructed.back());
- constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right)));
+ constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right)));
break;
}
case DecodeContext::THRESH_W: {
@@ -1652,7 +1749,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
constructed.pop_back();
subs.push_back(std::move(sub));
}
- constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k));
+ constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::THRESH, std::move(subs), k));
break;
}
case DecodeContext::ENDIF: {
@@ -1702,7 +1799,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
if (in >= last) return {};
if (in[0].first == OP_IF) {
++in;
- BuildBack(ctx, Fragment::OR_I, constructed, /*reverse=*/true);
+ BuildBack(Fragment::OR_I, constructed, /*reverse=*/true);
} else if (in[0].first == OP_NOTIF) {
++in;
to_parse.emplace_back(DecodeContext::ANDOR, -1, -1);
@@ -1717,6 +1814,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
}
if (constructed.size() != 1) return {};
const NodeRef<Key> tl_node = std::move(constructed.front());
+ tl_node->DuplicateKeyCheck(ctx);
// Note that due to how ComputeType works (only assign the type to the node if the
// subs' types are valid) this would fail if any node of tree is badly typed.
if (!tl_node->IsValidTopLevel()) return {};
@@ -1733,6 +1831,8 @@ inline NodeRef<typename Ctx::Key> FromString(const std::string& str, const Ctx&
template<typename Ctx>
inline NodeRef<typename Ctx::Key> FromScript(const CScript& script, const Ctx& ctx) {
using namespace internal;
+ // A too large Script is necessarily invalid, don't bother parsing it.
+ if (script.size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
auto decomposed = DecomposeScript(script);
if (!decomposed) return {};
auto it = decomposed->begin();
diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp
index 4e2acc463a..e507ec7528 100644
--- a/src/script/sigcache.cpp
+++ b/src/script/sigcache.cpp
@@ -14,6 +14,7 @@
#include <algorithm>
#include <mutex>
+#include <optional>
#include <shared_mutex>
#include <vector>
@@ -75,7 +76,7 @@ public:
std::unique_lock<std::shared_mutex> lock(cs_sigcache);
setValid.insert(entry);
}
- uint32_t setup_bytes(size_t n)
+ std::optional<std::pair<uint32_t, size_t>> setup_bytes(size_t n)
{
return setValid.setup_bytes(n);
}
@@ -92,14 +93,15 @@ static CSignatureCache signatureCache;
// To be called once in AppInitMain/BasicTestingSetup to initialize the
// signatureCache.
-void InitSignatureCache()
+bool InitSignatureCache(size_t max_size_bytes)
{
- // nMaxCacheSize is unsigned. If -maxsigcachesize is set to zero,
- // setup_bytes creates the minimum possible cache (2 elements).
- size_t nMaxCacheSize = std::min(std::max((int64_t)0, gArgs.GetIntArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE) / 2), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20);
- size_t nElems = signatureCache.setup_bytes(nMaxCacheSize);
- LogPrintf("Using %zu MiB out of %zu/2 requested for signature cache, able to store %zu elements\n",
- (nElems*sizeof(uint256)) >>20, (nMaxCacheSize*2)>>20, nElems);
+ auto setup_results = signatureCache.setup_bytes(max_size_bytes);
+ if (!setup_results) return false;
+
+ const auto [num_elems, approx_size_bytes] = *setup_results;
+ LogPrintf("Using %zu MiB out of %zu MiB requested for signature cache, able to store %zu elements\n",
+ approx_size_bytes >> 20, max_size_bytes >> 20, num_elems);
+ return true;
}
bool CachingTransactionSignatureChecker::VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& pubkey, const uint256& sighash) const
diff --git a/src/script/sigcache.h b/src/script/sigcache.h
index 1e21d6f340..290955090d 100644
--- a/src/script/sigcache.h
+++ b/src/script/sigcache.h
@@ -10,14 +10,13 @@
#include <span.h>
#include <util/hasher.h>
+#include <optional>
#include <vector>
-// DoS prevention: limit cache size to 32MB (over 1000000 entries on 64-bit
+// DoS prevention: limit cache size to 32MiB (over 1000000 entries on 64-bit
// systems). Due to how we count cache size, actual memory usage is slightly
-// more (~32.25 MB)
-static const unsigned int DEFAULT_MAX_SIG_CACHE_SIZE = 32;
-// Maximum sig cache size allowed
-static const int64_t MAX_MAX_SIG_CACHE_SIZE = 16384;
+// more (~32.25 MiB)
+static constexpr size_t DEFAULT_MAX_SIG_CACHE_BYTES{32 << 20};
class CPubKey;
@@ -33,6 +32,6 @@ public:
bool VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const override;
};
-void InitSignatureCache();
+[[nodiscard]] bool InitSignatureCache(size_t max_size_bytes);
#endif // BITCOIN_SCRIPT_SIGCACHE_H
diff --git a/src/script/sign.cpp b/src/script/sign.cpp
index 3b8071d9d1..5da0d076d8 100644
--- a/src/script/sign.cpp
+++ b/src/script/sign.cpp
@@ -243,6 +243,11 @@ static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCrea
sigdata.taproot_key_path_sig = sig;
}
}
+ if (sigdata.taproot_key_path_sig.size() == 0) {
+ if (creator.CreateSchnorrSig(provider, sig, output, nullptr, nullptr, SigVersion::TAPROOT)) {
+ sigdata.taproot_key_path_sig = sig;
+ }
+ }
if (sigdata.taproot_key_path_sig.size()) {
result = Vector(sigdata.taproot_key_path_sig);
return true;
@@ -591,8 +596,11 @@ public:
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; }
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) const override { return true; }
};
-const DummySignatureChecker DUMMY_CHECKER;
+}
+
+const BaseSignatureChecker& DUMMY_CHECKER = DummySignatureChecker();
+namespace {
class DummySignatureCreator final : public BaseSignatureCreator {
private:
char m_r_len = 32;
@@ -627,25 +635,6 @@ public:
const BaseSignatureCreator& DUMMY_SIGNATURE_CREATOR = DummySignatureCreator(32, 32);
const BaseSignatureCreator& DUMMY_MAXIMUM_SIGNATURE_CREATOR = DummySignatureCreator(33, 32);
-bool IsSolvable(const SigningProvider& provider, const CScript& script)
-{
- // This check is to make sure that the script we created can actually be solved for and signed by us
- // if we were to have the private keys. This is just to make sure that the script is valid and that,
- // if found in a transaction, we would still accept and relay that transaction. In particular,
- // it will reject witness outputs that require signing with an uncompressed public key.
- SignatureData sigs;
- // Make sure that STANDARD_SCRIPT_VERIFY_FLAGS includes SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, the most
- // important property this function is designed to test for.
- static_assert(STANDARD_SCRIPT_VERIFY_FLAGS & SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, "IsSolvable requires standard script flags to include WITNESS_PUBKEYTYPE");
- if (ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, script, sigs)) {
- // VerifyScript check is just defensive, and should never fail.
- bool verified = VerifyScript(sigs.scriptSig, script, &sigs.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, DUMMY_CHECKER);
- assert(verified);
- return true;
- }
- return false;
-}
-
bool IsSegWitOutput(const SigningProvider& provider, const CScript& script)
{
int version;
diff --git a/src/script/sign.h b/src/script/sign.h
index 5e58272154..813dfe04e3 100644
--- a/src/script/sign.h
+++ b/src/script/sign.h
@@ -52,6 +52,8 @@ public:
bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const override;
};
+/** A signature checker that accepts all signatures */
+extern const BaseSignatureChecker& DUMMY_CHECKER;
/** A signature creator that just produces 71-byte empty signatures. */
extern const BaseSignatureCreator& DUMMY_SIGNATURE_CREATOR;
/** A signature creator that just produces 72-byte empty signatures. */
@@ -97,12 +99,6 @@ bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom,
SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout);
void UpdateInput(CTxIn& input, const SignatureData& data);
-/* Check whether we know how to sign for an output like this, assuming we
- * have all private keys. While this function does not need private keys, the passed
- * provider is used to look up public keys and redeemscripts by hash.
- * 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);
diff --git a/src/script/signingprovider.cpp b/src/script/signingprovider.cpp
index c624a17628..a82a8b252a 100644
--- a/src/script/signingprovider.cpp
+++ b/src/script/signingprovider.cpp
@@ -77,20 +77,14 @@ bool FlatSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, Tapro
return LookupHelper(tr_trees, output_key, builder);
}
-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());
- ret.tr_trees = a.tr_trees;
- ret.tr_trees.insert(b.tr_trees.begin(), b.tr_trees.end());
- return ret;
+FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b)
+{
+ scripts.merge(b.scripts);
+ pubkeys.merge(b.pubkeys);
+ keys.merge(b.keys);
+ origins.merge(b.origins);
+ tr_trees.merge(b.tr_trees);
+ return *this;
}
void FillableSigningProvider::ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey)
diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h
index 792cc903f2..2d4234ea0b 100644
--- a/src/script/signingprovider.h
+++ b/src/script/signingprovider.h
@@ -6,6 +6,7 @@
#ifndef BITCOIN_SCRIPT_SIGNINGPROVIDER_H
#define BITCOIN_SCRIPT_SIGNINGPROVIDER_H
+#include <attributes.h>
#include <key.h>
#include <pubkey.h>
#include <script/keyorigin.h>
@@ -85,9 +86,9 @@ struct FlatSigningProvider final : public SigningProvider
bool GetKey(const CKeyID& keyid, CKey& key) const override;
bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override;
bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override;
-};
-FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b);
+ FlatSigningProvider& Merge(FlatSigningProvider&& b) LIFETIMEBOUND;
+};
/** Fillable signing provider that keeps keys in an address->secret map */
class FillableSigningProvider : public SigningProvider
diff --git a/src/script/standard.cpp b/src/script/standard.cpp
index b3f6a1b669..6101738061 100644
--- a/src/script/standard.cpp
+++ b/src/script/standard.cpp
@@ -16,9 +16,6 @@
typedef std::vector<unsigned char> valtype;
-bool fAcceptDatacarrier = DEFAULT_ACCEPT_DATACARRIER;
-unsigned nMaxDatacarrierBytes = MAX_OP_RETURN_RELAY;
-
CScriptID::CScriptID(const CScript& in) : BaseHash(Hash160(in)) {}
CScriptID::CScriptID(const ScriptHash& in) : BaseHash(static_cast<uint160>(in)) {}
diff --git a/src/script/standard.h b/src/script/standard.h
index 448fdff010..966a52b2c7 100644
--- a/src/script/standard.h
+++ b/src/script/standard.h
@@ -33,21 +33,12 @@ public:
};
/**
- * Default setting for nMaxDatacarrierBytes. 80 bytes of data, +1 for OP_RETURN,
+ * Default setting for -datacarriersize. 80 bytes of data, +1 for OP_RETURN,
* +2 for the pushdata opcodes.
*/
static const unsigned int MAX_OP_RETURN_RELAY = 83;
/**
- * A data carrying output is an unspendable output containing data. The script
- * type is designated as TxoutType::NULL_DATA.
- */
-extern bool fAcceptDatacarrier;
-
-/** Maximum size of TxoutType::NULL_DATA scripts that this node considers standard. */
-extern unsigned nMaxDatacarrierBytes;
-
-/**
* Mandatory script verification flags that all new blocks must comply with for
* them to be valid. (but old blocks may not comply with) Currently just P2SH,
* but in the future other flags may be added.
@@ -324,6 +315,8 @@ public:
TaprootSpendData GetSpendData() const;
/** Returns a vector of tuples representing the depth, leaf version, and script */
std::vector<std::tuple<uint8_t, uint8_t, CScript>> GetTreeTuples() const;
+ /** Returns true if there are any tapscripts */
+ bool HasScripts() const { return !m_branch.empty(); }
};
/** Given a TaprootSpendData and the output key, reconstruct its script tree.
diff --git a/src/streams.h b/src/streams.h
index f14d347380..0178df1c49 100644
--- a/src/streams.h
+++ b/src/streams.h
@@ -13,11 +13,11 @@
#include <algorithm>
#include <assert.h>
+#include <cstdio>
#include <ios>
#include <limits>
#include <optional>
#include <stdint.h>
-#include <stdio.h>
#include <string.h>
#include <string>
#include <utility>
@@ -269,7 +269,6 @@ public:
// Stream subset
//
bool eof() const { return size() == 0; }
- CDataStream* rdbuf() { return this; }
int in_avail() const { return size(); }
void SetType(int n) { nType = n; }
@@ -488,12 +487,14 @@ public:
AutoFile(const AutoFile&) = delete;
AutoFile& operator=(const AutoFile&) = delete;
- void fclose()
+ int fclose()
{
+ int retval{0};
if (file) {
- ::fclose(file);
+ retval = ::fclose(file);
file = nullptr;
}
+ return retval;
}
/** Get wrapped FILE* with transfer of ownership.
diff --git a/src/support/cleanse.h b/src/support/cleanse.h
index 8c1210a114..b1227770c7 100644
--- a/src/support/cleanse.h
+++ b/src/support/cleanse.h
@@ -6,7 +6,7 @@
#ifndef BITCOIN_SUPPORT_CLEANSE_H
#define BITCOIN_SUPPORT_CLEANSE_H
-#include <stdlib.h>
+#include <cstdlib>
/** Secure overwrite a buffer (possibly containing secret data) with zero-bytes. The write
* operation will not be optimized out by the compiler. */
diff --git a/src/sync.h b/src/sync.h
index 7ec4b668ac..1f4e191214 100644
--- a/src/sync.h
+++ b/src/sync.h
@@ -111,7 +111,7 @@ public:
return PARENT::try_lock();
}
- using UniqueLock = std::unique_lock<PARENT>;
+ using unique_lock = std::unique_lock<PARENT>;
#ifdef __clang__
//! For negative capabilities in the Clang Thread Safety Analysis.
//! A negative requirement uses the EXCLUSIVE_LOCKS_REQUIRED attribute, in conjunction
@@ -147,11 +147,13 @@ inline void AssertLockNotHeldInline(const char* name, const char* file, int line
inline void AssertLockNotHeldInline(const char* name, const char* file, int line, GlobalMutex* cs) LOCKS_EXCLUDED(cs) { AssertLockNotHeldInternal(name, file, line, cs); }
#define AssertLockNotHeld(cs) AssertLockNotHeldInline(#cs, __FILE__, __LINE__, &cs)
-/** Wrapper around std::unique_lock style lock for Mutex. */
-template <typename Mutex, typename Base = typename Mutex::UniqueLock>
-class SCOPED_LOCKABLE UniqueLock : public Base
+/** Wrapper around std::unique_lock style lock for MutexType. */
+template <typename MutexType>
+class SCOPED_LOCKABLE UniqueLock : public MutexType::unique_lock
{
private:
+ using Base = typename MutexType::unique_lock;
+
void Enter(const char* pszName, const char* pszFile, int nLine)
{
EnterCritical(pszName, pszFile, nLine, Base::mutex());
@@ -165,15 +167,15 @@ private:
bool TryEnter(const char* pszName, const char* pszFile, int nLine)
{
EnterCritical(pszName, pszFile, nLine, Base::mutex(), true);
- Base::try_lock();
- if (!Base::owns_lock()) {
- LeaveCritical();
+ if (Base::try_lock()) {
+ return true;
}
- return Base::owns_lock();
+ LeaveCritical();
+ return false;
}
public:
- UniqueLock(Mutex& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(mutexIn) : Base(mutexIn, std::defer_lock)
+ UniqueLock(MutexType& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(mutexIn) : Base(mutexIn, std::defer_lock)
{
if (fTry)
TryEnter(pszName, pszFile, nLine);
@@ -181,7 +183,7 @@ public:
Enter(pszName, pszFile, nLine);
}
- UniqueLock(Mutex* pmutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(pmutexIn)
+ UniqueLock(MutexType* pmutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(pmutexIn)
{
if (!pmutexIn) return;
@@ -241,29 +243,24 @@ public:
#define REVERSE_LOCK(g) typename std::decay<decltype(g)>::type::reverse_lock UNIQUE_NAME(revlock)(g, #g, __FILE__, __LINE__)
-template<typename MutexArg>
-using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove_pointer<MutexArg>::type>::type>;
-
// When locking a Mutex, require negative capability to ensure the lock
// is not already held
inline Mutex& MaybeCheckNotHeld(Mutex& cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) LOCK_RETURNED(cs) { return cs; }
inline Mutex* MaybeCheckNotHeld(Mutex* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) LOCK_RETURNED(cs) { return cs; }
-// When locking a GlobalMutex, just check it is not locked in the surrounding scope
-inline GlobalMutex& MaybeCheckNotHeld(GlobalMutex& cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; }
-inline GlobalMutex* MaybeCheckNotHeld(GlobalMutex* cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; }
-
-// When locking a RecursiveMutex, it's okay to already hold the lock
-// but check that it is not known to be locked in the surrounding scope anyway
-inline RecursiveMutex& MaybeCheckNotHeld(RecursiveMutex& cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; }
-inline RecursiveMutex* MaybeCheckNotHeld(RecursiveMutex* cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; }
+// When locking a GlobalMutex or RecursiveMutex, just check it is not
+// locked in the surrounding scope.
+template <typename MutexType>
+inline MutexType& MaybeCheckNotHeld(MutexType& m) LOCKS_EXCLUDED(m) LOCK_RETURNED(m) { return m; }
+template <typename MutexType>
+inline MutexType* MaybeCheckNotHeld(MutexType* m) LOCKS_EXCLUDED(m) LOCK_RETURNED(m) { return m; }
-#define LOCK(cs) DebugLock<decltype(cs)> UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
+#define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
#define LOCK2(cs1, cs2) \
- DebugLock<decltype(cs1)> criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \
- DebugLock<decltype(cs2)> criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__);
-#define TRY_LOCK(cs, name) DebugLock<decltype(cs)> name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__, true)
-#define WAIT_LOCK(cs, name) DebugLock<decltype(cs)> name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
+ UniqueLock criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \
+ UniqueLock criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__)
+#define TRY_LOCK(cs, name) UniqueLock name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__, true)
+#define WAIT_LOCK(cs, name) UniqueLock name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
#define ENTER_CRITICAL_SECTION(cs) \
{ \
diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp
index b1372a3e98..b10d32ccec 100644
--- a/src/test/addrman_tests.cpp
+++ b/src/test/addrman_tests.cpp
@@ -225,7 +225,7 @@ BOOST_AUTO_TEST_CASE(addrman_new_multiplicity)
{
auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node));
CAddress addr{CAddress(ResolveService("253.3.3.3", 8333), NODE_NONE)};
- const auto start_time{AdjustedTime()};
+ const auto start_time{Now<NodeSeconds>()};
addr.nTime = start_time;
// test that multiplicity stays at 1 if nTime doesn't increase
@@ -295,15 +295,15 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr)
BOOST_CHECK_EQUAL(vAddr1.size(), 0U);
CAddress addr1 = CAddress(ResolveService("250.250.2.1", 8333), NODE_NONE);
- addr1.nTime = AdjustedTime(); // Set time so isTerrible = false
+ addr1.nTime = Now<NodeSeconds>(); // Set time so isTerrible = false
CAddress addr2 = CAddress(ResolveService("250.251.2.2", 9999), NODE_NONE);
- addr2.nTime = AdjustedTime();
+ addr2.nTime = Now<NodeSeconds>();
CAddress addr3 = CAddress(ResolveService("251.252.2.3", 8333), NODE_NONE);
- addr3.nTime = AdjustedTime();
+ addr3.nTime = Now<NodeSeconds>();
CAddress addr4 = CAddress(ResolveService("252.253.3.4", 8333), NODE_NONE);
- addr4.nTime = AdjustedTime();
+ addr4.nTime = Now<NodeSeconds>();
CAddress addr5 = CAddress(ResolveService("252.254.4.5", 8333), NODE_NONE);
- addr5.nTime = AdjustedTime();
+ addr5.nTime = Now<NodeSeconds>();
CNetAddr source1 = ResolveIP("250.1.2.1");
CNetAddr source2 = ResolveIP("250.2.3.3");
@@ -329,7 +329,7 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr)
CAddress addr = CAddress(ResolveService(strAddr), NODE_NONE);
// Ensure that for all addrs in addrman, isTerrible == false.
- addr.nTime = AdjustedTime();
+ addr.nTime = Now<NodeSeconds>();
addrman->Add({addr}, ResolveIP(strAddr));
if (i % 8 == 0)
addrman->Good(addr);
@@ -822,7 +822,7 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks)
// Ensure test of address fails, so that it is evicted.
// Update entry in tried by setting last good connection in the deep past.
BOOST_CHECK(!addrman->Good(info, NodeSeconds{1s}));
- addrman->Attempt(info, /*fCountFailure=*/false, AdjustedTime() - 61s);
+ addrman->Attempt(info, /*fCountFailure=*/false, Now<NodeSeconds>() - 61s);
// Should swap 36 for 19.
addrman->ResolveCollisions();
@@ -966,7 +966,7 @@ BOOST_AUTO_TEST_CASE(addrman_update_address)
CNetAddr source{ResolveIP("252.2.2.2")};
CAddress addr{CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE)};
- const auto start_time{AdjustedTime() - 10000s};
+ const auto start_time{Now<NodeSeconds>() - 10000s};
addr.nTime = start_time;
BOOST_CHECK(addrman->Add({addr}, source));
BOOST_CHECK_EQUAL(addrman->size(), 1U);
diff --git a/src/test/banman_tests.cpp b/src/test/banman_tests.cpp
index 27ce9ad638..ecf60834ce 100644
--- a/src/test/banman_tests.cpp
+++ b/src/test/banman_tests.cpp
@@ -27,7 +27,7 @@ BOOST_AUTO_TEST_CASE(file)
" { \"version\": 1, \"ban_created\": 0, \"banned_until\": 778, \"address\": \"1.0.0.0/8\" }"
"] }",
};
- assert(WriteBinaryFile(banlist_path + ".json", entries_write));
+ BOOST_REQUIRE(WriteBinaryFile(banlist_path + ".json", entries_write));
{
// The invalid entries will be dropped, but the valid one remains
ASSERT_DEBUG_LOG("Dropping entry with unparseable address or subnet (aaaaaaaaa) from ban list");
@@ -35,7 +35,7 @@ BOOST_AUTO_TEST_CASE(file)
BanMan banman{banlist_path, /*client_interface=*/nullptr, /*default_ban_time=*/0};
banmap_t entries_read;
banman.GetBanned(entries_read);
- assert(entries_read.size() == 1);
+ BOOST_CHECK_EQUAL(entries_read.size(), 1);
}
}
}
diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp
index 00f32ddcee..249f89ae2f 100644
--- a/src/test/base58_tests.cpp
+++ b/src/test/base58_tests.cpp
@@ -25,7 +25,7 @@ BOOST_AUTO_TEST_CASE(base58_EncodeBase58)
{
UniValue tests = read_json(std::string(json_tests::base58_encode_decode, json_tests::base58_encode_decode + sizeof(json_tests::base58_encode_decode)));
for (unsigned int idx = 0; idx < tests.size(); idx++) {
- UniValue test = tests[idx];
+ const UniValue& test = tests[idx];
std::string strTest = test.write();
if (test.size() < 2) // Allow for extra stuff (useful for comments)
{
@@ -47,7 +47,7 @@ BOOST_AUTO_TEST_CASE(base58_DecodeBase58)
std::vector<unsigned char> result;
for (unsigned int idx = 0; idx < tests.size(); idx++) {
- UniValue test = tests[idx];
+ const UniValue& test = tests[idx];
std::string strTest = test.write();
if (test.size() < 2) // Allow for extra stuff (useful for comments)
{
diff --git a/src/test/bip32_tests.cpp b/src/test/bip32_tests.cpp
index 64cc924239..75b29ae0aa 100644
--- a/src/test/bip32_tests.cpp
+++ b/src/test/bip32_tests.cpp
@@ -184,4 +184,22 @@ BOOST_AUTO_TEST_CASE(bip32_test5) {
}
}
+BOOST_AUTO_TEST_CASE(bip32_max_depth) {
+ CExtKey key_parent{DecodeExtKey(test1.vDerive[0].prv)}, key_child;
+ CExtPubKey pubkey_parent{DecodeExtPubKey(test1.vDerive[0].pub)}, pubkey_child;
+
+ // We can derive up to the 255th depth..
+ for (auto i = 0; i++ < 255;) {
+ BOOST_CHECK(key_parent.Derive(key_child, 0));
+ std::swap(key_parent, key_child);
+ BOOST_CHECK(pubkey_parent.Derive(pubkey_child, 0));
+ std::swap(pubkey_parent, pubkey_child);
+ }
+
+ // But trying to derive a non-existent 256th depth will fail!
+ BOOST_CHECK(key_parent.nDepth == 255 && pubkey_parent.nDepth == 255);
+ BOOST_CHECK(!key_parent.Derive(key_child, 0));
+ BOOST_CHECK(!pubkey_parent.Derive(pubkey_child, 0));
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/blockchain_tests.cpp b/src/test/blockchain_tests.cpp
index c8e8cdeeb3..0157e25071 100644
--- a/src/test/blockchain_tests.cpp
+++ b/src/test/blockchain_tests.cpp
@@ -4,13 +4,13 @@
#include <boost/test/unit_test.hpp>
-#include <stdlib.h>
-
#include <chain.h>
#include <rpc/blockchain.h>
#include <test/util/setup_common.h>
#include <util/string.h>
+#include <cstdlib>
+
/* Equality between doubles is imprecise. Comparison should be done
* with a small threshold of tolerance, rather than exact equality.
*/
diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp
index 78b82b9b20..1c13c0a909 100644
--- a/src/test/blockencodings_tests.cpp
+++ b/src/test/blockencodings_tests.cpp
@@ -7,6 +7,7 @@
#include <consensus/merkle.h>
#include <pow.h>
#include <streams.h>
+#include <test/util/txmempool.h>
#include <test/util/setup_common.h>
diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp
index 1a182209b8..2798e998af 100644
--- a/src/test/blockfilter_index_tests.cpp
+++ b/src/test/blockfilter_index_tests.cpp
@@ -101,7 +101,7 @@ bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex,
CBlockHeader header = block->GetBlockHeader();
BlockValidationState state;
- if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({header}, state, &pindex)) {
+ if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({header}, true, state, &pindex)) {
return false;
}
}
@@ -178,7 +178,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
uint256 chainA_last_header = last_header;
for (size_t i = 0; i < 2; i++) {
const auto& block = chainA[i];
- BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr));
+ BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
}
for (size_t i = 0; i < 2; i++) {
const auto& block = chainA[i];
@@ -196,7 +196,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
uint256 chainB_last_header = last_header;
for (size_t i = 0; i < 3; i++) {
const auto& block = chainB[i];
- BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr));
+ BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
}
for (size_t i = 0; i < 3; i++) {
const auto& block = chainB[i];
@@ -227,7 +227,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
// Reorg back to chain A.
for (size_t i = 2; i < 4; i++) {
const auto& block = chainA[i];
- BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr));
+ BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
}
// Check that chain A and B blocks can be retrieved.
diff --git a/src/test/blockfilter_tests.cpp b/src/test/blockfilter_tests.cpp
index 178757e7b4..0831188327 100644
--- a/src/test/blockfilter_tests.cpp
+++ b/src/test/blockfilter_tests.cpp
@@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE(blockfilters_json_test)
const UniValue& tests = json.get_array();
for (unsigned int i = 0; i < tests.size(); i++) {
- UniValue test = tests[i];
+ const UniValue& test = tests[i];
std::string strTest = test.write();
if (test.size() == 1) {
diff --git a/src/test/blockmanager_tests.cpp b/src/test/blockmanager_tests.cpp
new file mode 100644
index 0000000000..dd7c32cc46
--- /dev/null
+++ b/src/test/blockmanager_tests.cpp
@@ -0,0 +1,42 @@
+// Copyright (c) 2022 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 <chainparams.h>
+#include <node/blockstorage.h>
+#include <node/context.h>
+#include <validation.h>
+
+#include <boost/test/unit_test.hpp>
+#include <test/util/setup_common.h>
+
+using node::BlockManager;
+using node::BLOCK_SERIALIZATION_HEADER_SIZE;
+
+// use BasicTestingSetup here for the data directory configuration, setup, and cleanup
+BOOST_FIXTURE_TEST_SUITE(blockmanager_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(blockmanager_find_block_pos)
+{
+ const auto params {CreateChainParams(ArgsManager{}, CBaseChainParams::MAIN)};
+ BlockManager blockman {};
+ CChain chain {};
+ // simulate adding a genesis block normally
+ BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0, chain, *params, nullptr).nPos, BLOCK_SERIALIZATION_HEADER_SIZE);
+ // simulate what happens during reindex
+ // simulate a well-formed genesis block being found at offset 8 in the blk00000.dat file
+ // the block is found at offset 8 because there is an 8 byte serialization header
+ // consisting of 4 magic bytes + 4 length bytes before each block in a well-formed blk file.
+ FlatFilePos pos{0, BLOCK_SERIALIZATION_HEADER_SIZE};
+ BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0, chain, *params, &pos).nPos, BLOCK_SERIALIZATION_HEADER_SIZE);
+ // now simulate what happens after reindex for the first new block processed
+ // the actual block contents don't matter, just that it's a block.
+ // verify that the write position is at offset 0x12d.
+ // this is a check to make sure that https://github.com/bitcoin/bitcoin/issues/21379 does not recur
+ // 8 bytes (for serialization header) + 285 (for serialized genesis block) = 293
+ // add another 8 bytes for the second block's serialization header and we get 293 + 8 = 301
+ FlatFilePos actual{blockman.SaveBlockToDisk(params->GenesisBlock(), 1, chain, *params, nullptr)};
+ BOOST_CHECK_EQUAL(actual.nPos, BLOCK_SERIALIZATION_HEADER_SIZE + ::GetSerializeSize(params->GenesisBlock(), CLIENT_VERSION) + BLOCK_SERIALIZATION_HEADER_SIZE);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/coinstatsindex_tests.cpp b/src/test/coinstatsindex_tests.cpp
index c93d05a93b..8a2b0792fd 100644
--- a/src/test/coinstatsindex_tests.cpp
+++ b/src/test/coinstatsindex_tests.cpp
@@ -5,6 +5,7 @@
#include <chainparams.h>
#include <index/coinstatsindex.h>
#include <interfaces/chain.h>
+#include <kernel/coinstats.h>
#include <test/util/setup_common.h>
#include <test/util/validation.h>
#include <util/time.h>
@@ -38,7 +39,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup)
}
// CoinStatsIndex should not be found before it is started.
- BOOST_CHECK(!coin_stats_index.LookUpStats(block_index));
+ BOOST_CHECK(!coin_stats_index.LookUpStats(*block_index));
// BlockUntilSyncedToCurrentChain should return false before CoinStatsIndex
// is started.
@@ -54,10 +55,10 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup)
LOCK(cs_main);
genesis_block_index = m_node.chainman->ActiveChain().Genesis();
}
- BOOST_CHECK(coin_stats_index.LookUpStats(genesis_block_index));
+ BOOST_CHECK(coin_stats_index.LookUpStats(*genesis_block_index));
// Check that CoinStatsIndex updates with new blocks.
- BOOST_CHECK(coin_stats_index.LookUpStats(block_index));
+ BOOST_CHECK(coin_stats_index.LookUpStats(*block_index));
const CScript script_pub_key{CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG};
std::vector<CMutableTransaction> noTxns;
@@ -71,21 +72,27 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup)
LOCK(cs_main);
new_block_index = m_node.chainman->ActiveChain().Tip();
}
- BOOST_CHECK(coin_stats_index.LookUpStats(new_block_index));
+ BOOST_CHECK(coin_stats_index.LookUpStats(*new_block_index));
BOOST_CHECK(block_index != new_block_index);
+ // It is not safe to stop and destroy the index until it finishes handling
+ // the last BlockConnected notification. The BlockUntilSyncedToCurrentChain()
+ // call above is sufficient to ensure this, but the
+ // SyncWithValidationInterfaceQueue() call below is also needed to ensure
+ // TSAN always sees the test thread waiting for the notification thread, and
+ // avoid potential false positive reports.
+ SyncWithValidationInterfaceQueue();
+
// Shutdown sequence (c.f. Shutdown() in init.cpp)
coin_stats_index.Stop();
-
- // Rest of shutdown sequence and destructors happen in ~TestingSetup()
}
// Test shutdown between BlockConnected and ChainStateFlushed notifications,
// make sure index is not corrupted and is able to reload.
BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup)
{
- CChainState& chainstate = Assert(m_node.chainman)->ActiveChainstate();
+ Chainstate& chainstate = Assert(m_node.chainman)->ActiveChainstate();
const CChainParams& params = Params();
{
CoinStatsIndex index{interfaces::MakeChain(m_node), 1 << 20};
@@ -102,7 +109,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup)
LOCK(cs_main);
BlockValidationState state;
BOOST_CHECK(CheckBlock(block, state, params.GetConsensus()));
- BOOST_CHECK(chainstate.AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr));
+ BOOST_CHECK(chainstate.AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr, true));
CCoinsViewCache view(&chainstate.CoinsTip());
BOOST_CHECK(chainstate.ConnectBlock(block, state, new_block_index, view));
}
diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp
index 66c4605afa..7150698e64 100644
--- a/src/test/denialofservice_tests.cpp
+++ b/src/test/denialofservice_tests.cpp
@@ -45,6 +45,8 @@ BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup)
// work.
BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
{
+ LOCK(NetEventsInterface::g_msgproc_mutex);
+
ConnmanTestMsg& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
// Disable inactivity checks for this test to avoid interference
connman.SetPeerConnectTimeout(99999s);
@@ -68,7 +70,6 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
/*successfully_connected=*/true,
/*remote_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
/*local_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
- /*permission_flags=*/NetPermissionFlags::None,
/*version=*/PROTOCOL_VERSION,
/*relay_txs=*/true);
TestOnlyResetTimeData();
@@ -81,10 +82,8 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
}
// Test starts here
- {
- LOCK(dummyNode1.cs_sendProcessing);
- BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in getheaders
- }
+ BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in getheaders
+
{
LOCK(dummyNode1.cs_vSend);
BOOST_CHECK(dummyNode1.vSendMsg.size() > 0);
@@ -94,20 +93,14 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
int64_t nStartTime = GetTime();
// Wait 21 minutes
SetMockTime(nStartTime+21*60);
- {
- LOCK(dummyNode1.cs_sendProcessing);
- BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in getheaders
- }
+ BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in getheaders
{
LOCK(dummyNode1.cs_vSend);
BOOST_CHECK(dummyNode1.vSendMsg.size() > 0);
}
// Wait 3 more minutes
SetMockTime(nStartTime+24*60);
- {
- LOCK(dummyNode1.cs_sendProcessing);
- BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in disconnect
- }
+ BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in disconnect
BOOST_CHECK(dummyNode1.fDisconnect == true);
peerman.FinalizeNode(dummyNode1);
@@ -275,6 +268,8 @@ BOOST_AUTO_TEST_CASE(block_relay_only_eviction)
BOOST_AUTO_TEST_CASE(peer_discouragement)
{
+ LOCK(NetEventsInterface::g_msgproc_mutex);
+
auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME);
auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman);
auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(),
@@ -309,10 +304,8 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
nodes[0]->fSuccessfullyConnected = true;
connman->AddTestNode(*nodes[0]);
peerLogic->UnitTestMisbehaving(nodes[0]->GetId(), DISCOURAGEMENT_THRESHOLD); // Should be discouraged
- {
- LOCK(nodes[0]->cs_sendProcessing);
- BOOST_CHECK(peerLogic->SendMessages(nodes[0]));
- }
+ BOOST_CHECK(peerLogic->SendMessages(nodes[0]));
+
BOOST_CHECK(banman->IsDiscouraged(addr[0]));
BOOST_CHECK(nodes[0]->fDisconnect);
BOOST_CHECK(!banman->IsDiscouraged(other_addr)); // Different address, not discouraged
@@ -331,10 +324,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
nodes[1]->fSuccessfullyConnected = true;
connman->AddTestNode(*nodes[1]);
peerLogic->UnitTestMisbehaving(nodes[1]->GetId(), DISCOURAGEMENT_THRESHOLD - 1);
- {
- LOCK(nodes[1]->cs_sendProcessing);
- BOOST_CHECK(peerLogic->SendMessages(nodes[1]));
- }
+ BOOST_CHECK(peerLogic->SendMessages(nodes[1]));
// [0] is still discouraged/disconnected.
BOOST_CHECK(banman->IsDiscouraged(addr[0]));
BOOST_CHECK(nodes[0]->fDisconnect);
@@ -342,10 +332,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
BOOST_CHECK(!banman->IsDiscouraged(addr[1]));
BOOST_CHECK(!nodes[1]->fDisconnect);
peerLogic->UnitTestMisbehaving(nodes[1]->GetId(), 1); // [1] reaches discouragement threshold
- {
- LOCK(nodes[1]->cs_sendProcessing);
- BOOST_CHECK(peerLogic->SendMessages(nodes[1]));
- }
+ BOOST_CHECK(peerLogic->SendMessages(nodes[1]));
// Expect both [0] and [1] to be discouraged/disconnected now.
BOOST_CHECK(banman->IsDiscouraged(addr[0]));
BOOST_CHECK(nodes[0]->fDisconnect);
@@ -368,10 +355,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
nodes[2]->fSuccessfullyConnected = true;
connman->AddTestNode(*nodes[2]);
peerLogic->UnitTestMisbehaving(nodes[2]->GetId(), DISCOURAGEMENT_THRESHOLD);
- {
- LOCK(nodes[2]->cs_sendProcessing);
- BOOST_CHECK(peerLogic->SendMessages(nodes[2]));
- }
+ BOOST_CHECK(peerLogic->SendMessages(nodes[2]));
BOOST_CHECK(banman->IsDiscouraged(addr[0]));
BOOST_CHECK(banman->IsDiscouraged(addr[1]));
BOOST_CHECK(banman->IsDiscouraged(addr[2]));
@@ -387,6 +371,8 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
BOOST_AUTO_TEST_CASE(DoS_bantime)
{
+ LOCK(NetEventsInterface::g_msgproc_mutex);
+
auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME);
auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman);
auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(),
@@ -412,10 +398,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime)
dummyNode.fSuccessfullyConnected = true;
peerLogic->UnitTestMisbehaving(dummyNode.GetId(), DISCOURAGEMENT_THRESHOLD);
- {
- LOCK(dummyNode.cs_sendProcessing);
- BOOST_CHECK(peerLogic->SendMessages(&dummyNode));
- }
+ BOOST_CHECK(peerLogic->SendMessages(&dummyNode));
BOOST_CHECK(banman->IsDiscouraged(addr));
peerLogic->FinalizeNode(dummyNode);
diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp
index a8c666079d..6b2ef74e19 100644
--- a/src/test/descriptor_tests.cpp
+++ b/src/test/descriptor_tests.cpp
@@ -126,7 +126,7 @@ std::set<std::pair<CPubKey, KeyOriginInfo>> GetKeyOriginData(const FlatSigningPr
return ret;
}
-void DoCheck(const std::string& prv, const std::string& pub, const std::string& norm_prv, const std::string& norm_pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY,
+void DoCheck(const std::string& prv, const std::string& pub, const std::string& norm_pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::optional<OutputType>& type, 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;
@@ -233,7 +233,7 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string&
for (const auto& xpub_pair : parent_xpub_cache) {
const CExtPubKey& xpub = xpub_pair.second;
CExtPubKey der;
- xpub.Derive(der, i);
+ BOOST_CHECK(xpub.Derive(der, i));
pubkeys.insert(der.pubkey);
}
int count_pks = 0;
@@ -265,7 +265,7 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string&
const CExtPubKey& xpub = xpub_pair.second;
pubkeys.insert(xpub.pubkey);
CExtPubKey der;
- xpub.Derive(der, i);
+ BOOST_CHECK(xpub.Derive(der, i));
pubkeys.insert(der.pubkey);
}
int count_pks = 0;
@@ -302,7 +302,6 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string&
// For each of the produced scripts, verify solvability, and when possible, try to sign a transaction spending it.
for (size_t n = 0; n < spks.size(); ++n) {
BOOST_CHECK_EQUAL(ref[n], HexStr(spks[n]));
- BOOST_CHECK_EQUAL(IsSolvable(Merge(key_provider, script_provider), spks[n]), (flags & UNSOLVABLE) == 0);
if (flags & SIGNABLE) {
CMutableTransaction spend;
@@ -313,7 +312,7 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string&
txdata.Init(spend, std::move(utxos), /*force=*/true);
MutableTransactionSignatureCreator creator{spend, 0, CAmount{0}, &txdata, SIGHASH_DEFAULT};
SignatureData sigdata;
- BOOST_CHECK_MESSAGE(ProduceSignature(Merge(keys_priv, script_provider), creator, spks[n], sigdata), prv);
+ BOOST_CHECK_MESSAGE(ProduceSignature(FlatSigningProvider{keys_priv}.Merge(FlatSigningProvider{script_provider}), creator, spks[n], sigdata), prv);
}
/* Infer a descriptor from the generated script, and verify its solvability and that it roundtrips. */
@@ -324,7 +323,7 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string&
BOOST_CHECK(inferred->Expand(0, provider_inferred, spks_inferred, provider_inferred));
BOOST_CHECK_EQUAL(spks_inferred.size(), 1U);
BOOST_CHECK(spks_inferred[0] == spks[n]);
- BOOST_CHECK_EQUAL(IsSolvable(provider_inferred, spks_inferred[0]), !(flags & UNSOLVABLE));
+ BOOST_CHECK_EQUAL(InferDescriptor(spks_inferred[0], provider_inferred)->IsSolvable(), !(flags & UNSOLVABLE));
BOOST_CHECK(GetKeyOriginData(provider_inferred, flags) == GetKeyOriginData(script_provider, flags));
}
@@ -341,29 +340,29 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string&
BOOST_CHECK_MESSAGE(left_paths.empty(), "Not all expected key paths found: " + prv);
}
-void Check(const std::string& prv, const std::string& pub, const std::string& norm_prv, const std::string& norm_pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY)
+void Check(const std::string& prv, const std::string& pub, const std::string& norm_pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::optional<OutputType>& type, 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, norm_prv, norm_pub, flags, scripts, type, paths);
+ DoCheck(prv, pub, norm_pub, flags, scripts, type, 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, norm_prv, norm_pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */false);
+ DoCheck(prv, pub, norm_pub, flags, scripts, type, 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, norm_prv, norm_pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */false, /*replace_apostrophe_with_h_in_pub = */true);
+ DoCheck(prv, pub, norm_pub, flags, scripts, type, 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, norm_prv, norm_pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */true);
+ DoCheck(prv, pub, norm_pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */true);
}
}
@@ -374,60 +373,61 @@ BOOST_FIXTURE_TEST_SUITE(descriptor_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(descriptor_test)
{
// Basic single-key compressed
- Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, std::nullopt);
- Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}}, std::nullopt);
- Check("pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}, OutputType::LEGACY, {{1,0x80000002UL,3,0x80000004UL}});
- Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}, OutputType::BECH32);
- Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, OutputType::P2SH_SEGWIT);
- Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE | XONLY_KEYS, {{"512077aab6e066f8a7419c5ab714c12c67d25007ed55a43cadcacb4d7a970a093f11"}}, OutputType::BECH32M);
+ Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, std::nullopt);
+ Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}}, std::nullopt);
+ Check("pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}, OutputType::LEGACY, {{1,0x80000002UL,3,0x80000004UL}});
+ Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}, OutputType::BECH32);
+ Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, OutputType::P2SH_SEGWIT);
+ Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE | XONLY_KEYS, {{"512077aab6e066f8a7419c5ab714c12c67d25007ed55a43cadcacb4d7a970a093f11"}}, OutputType::BECH32M);
CheckUnparsable("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY2))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5))", "wpkh(): Pubkey '03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5' is invalid"); // Invalid pubkey
CheckUnparsable("pkh(deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh(deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pkh(): 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)", "pkh(): Multiple ']' characters found for a single pubkey"); // Multiple end brackets in key origin
// Basic single-key uncompressed
- Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)",SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, std::nullopt);
- Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}}, std::nullopt);
- Check("pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, OutputType::LEGACY);
+ Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)",SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, std::nullopt);
+ Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}}, std::nullopt);
+ Check("pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, OutputType::LEGACY);
CheckUnparsable("wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "wpkh(): Uncompressed keys are not allowed"); // No uncompressed keys in witness
CheckUnparsable("wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "pk(): Uncompressed keys are not allowed"); // No uncompressed keys in witness
CheckUnparsable("sh(wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "sh(wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "wpkh(): Uncompressed keys are not allowed"); // No uncompressed keys in witness
// Some unconventional single-key constructions
- Check("sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}}, OutputType::LEGACY);
- Check("sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141a31ad23bf49c247dd531a623c2ef57da3c400c587"}}, OutputType::LEGACY);
- Check("wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa"}}, OutputType::BECH32);
- Check("wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b"}}, OutputType::BECH32);
- Check("sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787"}}, OutputType::P2SH_SEGWIT);
- Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}}, OutputType::P2SH_SEGWIT);
- Check("tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", "tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", "tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", "tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", XONLY_KEYS | SIGNABLE | MISSING_PRIVKEYS, {{"51201497ae16f30dacb88523ed9301bff17773b609e8a90518a3f96ea328a47d1500"}}, OutputType::BECH32M);
+ Check("sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}}, OutputType::LEGACY);
+ Check("sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141a31ad23bf49c247dd531a623c2ef57da3c400c587"}}, OutputType::LEGACY);
+ Check("wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa"}}, OutputType::BECH32);
+ Check("wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b"}}, OutputType::BECH32);
+ Check("sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787"}}, OutputType::P2SH_SEGWIT);
+ Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}}, OutputType::P2SH_SEGWIT);
+ Check("tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", "tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", "tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", XONLY_KEYS | SIGNABLE | MISSING_PRIVKEYS, {{"51201497ae16f30dacb88523ed9301bff17773b609e8a90518a3f96ea328a47d1500"}}, OutputType::BECH32M);
// Versions with BIP32 derivations
- Check("combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}, std::nullopt);
- Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", "pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}, std::nullopt, {{0}});
- Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", "pkh([bd16bee5/2147483647']xprv9vHkqa6XAPwKqSKSEJMcAB3yoCZhaSVsGZbSkFY5L3Lfjjk8sjZucbsbvEw5o3QrSA69nPfZDCgFnNnLhQ2ohpZuwummndnPasDw2Qr6dC2/0)", "pkh([bd16bee5/2147483647']xpub69H7F5dQzmVd3vPuLKtcXJziMEQByuDidnX3YdwgtNsecY5HRGtAAQC5mXTt4dsv9RzyjgDjAQs9VGVV6ydYCHnprc9vvaA5YtqWyL6hyds/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, OutputType::LEGACY, {{0xFFFFFFFFUL,0}});
- Check("wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", "wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}, OutputType::BECH32, {{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/*'))", "sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED | DERIVE_HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}, OutputType::P2SH_SEGWIT, {{10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}});
- Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", "combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, std::nullopt, {{0}, {1}});
- Check("tr(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/0/*,pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/1/*))", "tr(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*))", "tr(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/0/*,pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/1/*))", "tr(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*))", XONLY_KEYS | RANGE, {{"512078bc707124daa551b65af74de2ec128b7525e10f374dc67b64e00ce0ab8b3e12"}, {"512001f0a02a17808c20134b78faab80ef93ffba82261ccef0a2314f5d62b6438f11"}, {"512021024954fcec88237a9386fce80ef2ced5f1e91b422b26c59ccfc174c8d1ad25"}}, OutputType::BECH32M, {{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}});
+ Check("combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}, std::nullopt);
+ Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}, std::nullopt, {{0}});
+ Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", "pkh([bd16bee5/2147483647']xpub69H7F5dQzmVd3vPuLKtcXJziMEQByuDidnX3YdwgtNsecY5HRGtAAQC5mXTt4dsv9RzyjgDjAQs9VGVV6ydYCHnprc9vvaA5YtqWyL6hyds/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, OutputType::LEGACY, {{0xFFFFFFFFUL,0}});
+
+ Check("wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}, OutputType::BECH32, {{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/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED | DERIVE_HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}, OutputType::P2SH_SEGWIT, {{10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}});
+ Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, std::nullopt, {{0}, {1}});
+ Check("tr(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/0/*,pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/1/*))", "tr(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*))", "tr(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*))", XONLY_KEYS | RANGE, {{"512078bc707124daa551b65af74de2ec128b7525e10f374dc67b64e00ce0ab8b3e12"}, {"512001f0a02a17808c20134b78faab80ef93ffba82261ccef0a2314f5d62b6438f11"}, {"512021024954fcec88237a9386fce80ef2ced5f1e91b422b26c59ccfc174c8d1ad25"}}, OutputType::BECH32M, {{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}});
// Mixed xpubs and const pubkeys
- Check("wsh(multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/0,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))","wsh(multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))","wsh(multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/0,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))","wsh(multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", MIXED_PUBKEYS, {{"0020cb155486048b23a6da976d4c6fe071a2dbc8a7b57aaf225b8955f2e2a27b5f00"}},OutputType::BECH32,{{0},{}});
+ Check("wsh(multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/0,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))","wsh(multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))","wsh(multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", MIXED_PUBKEYS, {{"0020cb155486048b23a6da976d4c6fe071a2dbc8a7b57aaf225b8955f2e2a27b5f00"}},OutputType::BECH32,{{0},{}});
// Mixed range xpubs and const pubkeys
- Check("multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)","multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)","multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)","multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", RANGE | MIXED_PUBKEYS, {{"512102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e0762103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"},{"5121032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ec2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"},{"5121035d30b6c66dc1e036c45369da8287518cf7e0d6ed1e2b905171c605708f14ca032103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"}}, std::nullopt,{{2},{1},{0},{}});
+ Check("multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)","multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)","multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", RANGE | MIXED_PUBKEYS, {{"512102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e0762103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"},{"5121032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ec2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"},{"5121035d30b6c66dc1e036c45369da8287518cf7e0d6ed1e2b905171c605708f14ca032103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"}}, std::nullopt,{{2},{1},{0},{}});
CheckUnparsable("combo([012345678]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([012345678]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "combo(): Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long key fingerprint
CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)", "pkh(): Key path value 2147483648 is out of range"); // BIP 32 path element overflow
CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/1aa)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1aa)", "pkh(): Key path value '1aa' is not a valid uint32"); // Path is not valid uint
- Check("pkh([01234567/10/20]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh([01234567/10/20]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", "pkh([01234567/10/20/2147483647']xprv9vHkqa6XAPwKqSKSEJMcAB3yoCZhaSVsGZbSkFY5L3Lfjjk8sjZucbsbvEw5o3QrSA69nPfZDCgFnNnLhQ2ohpZuwummndnPasDw2Qr6dC2/0)", "pkh([01234567/10/20/2147483647']xpub69H7F5dQzmVd3vPuLKtcXJziMEQByuDidnX3YdwgtNsecY5HRGtAAQC5mXTt4dsv9RzyjgDjAQs9VGVV6ydYCHnprc9vvaA5YtqWyL6hyds/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, OutputType::LEGACY, {{10, 20, 0xFFFFFFFFUL, 0}});
+ Check("pkh([01234567/10/20]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh([01234567/10/20]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", "pkh([01234567/10/20/2147483647']xpub69H7F5dQzmVd3vPuLKtcXJziMEQByuDidnX3YdwgtNsecY5HRGtAAQC5mXTt4dsv9RzyjgDjAQs9VGVV6ydYCHnprc9vvaA5YtqWyL6hyds/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, OutputType::LEGACY, {{10, 20, 0xFFFFFFFFUL, 0}});
// Multisig constructions
- Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, std::nullopt);
- Check("sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, std::nullopt);
- Check("sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, std::nullopt);
- Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
- Check("sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", "sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", RANGE, {{"5221025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b62102fbd47cc8034098f0e6a94c6aeee8528abf0a2153a5d8e46d325b7284c046784652ae"}, {"52210264fd4d1f5dea8ded94c61e9641309349b62f27fbffe807291f664e286bfbe6472103f4ece6dfccfa37b211eb3d0af4d0c61dba9ef698622dc17eecdf764beeb005a652ae"}, {"5221022ccabda84c30bad578b13c89eb3b9544ce149787e5b538175b1d1ba259cbb83321024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c52ae"}}, std::nullopt, {{0}, {1}, {2}, {0, 0, 0}, {0, 0, 1}, {0, 0, 2}});
- 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/*'))", "wsh(multi(2,[bd16bee5/2147483647']xprv9vHkqa6XAPwKqSKSEJMcAB3yoCZhaSVsGZbSkFY5L3Lfjjk8sjZucbsbvEw5o3QrSA69nPfZDCgFnNnLhQ2ohpZuwummndnPasDw2Qr6dC2/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[bd16bee5/2147483647']xpub69H7F5dQzmVd3vPuLKtcXJziMEQByuDidnX3YdwgtNsecY5HRGtAAQC5mXTt4dsv9RzyjgDjAQs9VGVV6ydYCHnprc9vvaA5YtqWyL6hyds/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE | DERIVE_HARDENED, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}, OutputType::BECH32, {{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)))", "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"}}, OutputType::P2SH_SEGWIT);
- Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,pk(KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pk(669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))", "tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,pk(KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pk(669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))", SIGNABLE | XONLY_KEYS, {{"512017cf18db381d836d8923b1bdb246cfcd818da1a9f0e6e7907f187f0b2f937754"}}, OutputType::BECH32M);
+ Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, std::nullopt);
+ Check("sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, std::nullopt);
+ Check("sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, std::nullopt);
+ Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
+ Check("sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", RANGE, {{"5221025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b62102fbd47cc8034098f0e6a94c6aeee8528abf0a2153a5d8e46d325b7284c046784652ae"}, {"52210264fd4d1f5dea8ded94c61e9641309349b62f27fbffe807291f664e286bfbe6472103f4ece6dfccfa37b211eb3d0af4d0c61dba9ef698622dc17eecdf764beeb005a652ae"}, {"5221022ccabda84c30bad578b13c89eb3b9544ce149787e5b538175b1d1ba259cbb83321024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c52ae"}}, std::nullopt, {{0}, {1}, {2}, {0, 0, 0}, {0, 0, 1}, {0, 0, 2}});
+ 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/*'))", "wsh(multi(2,[bd16bee5/2147483647']xpub69H7F5dQzmVd3vPuLKtcXJziMEQByuDidnX3YdwgtNsecY5HRGtAAQC5mXTt4dsv9RzyjgDjAQs9VGVV6ydYCHnprc9vvaA5YtqWyL6hyds/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE | DERIVE_HARDENED, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}, OutputType::BECH32, {{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)))", "sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}}, OutputType::P2SH_SEGWIT);
+ Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,pk(KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pk(669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pk(669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))", SIGNABLE | XONLY_KEYS, {{"512017cf18db381d836d8923b1bdb246cfcd818da1a9f0e6e7907f187f0b2f937754"}}, OutputType::BECH32M);
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/*'))", "Multi: 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/*'))", "Multi: Fingerprint 'aaagaaaa' is not hex"); // Non hex fingerprint
@@ -439,8 +439,8 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
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))", "P2SH script is too large, 581 bytes is larger than 520 bytes"); // Cannot have more than 15 keys in a P2SH multisig, or we exceed maximum push size
- Check("wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv))","wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd))", "wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv))","wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd))", SIGNABLE, {{"0020376bd8344b8b6ebe504ff85ef743eaa1aa9272178223bcb6887e9378efb341ac"}}, OutputType::BECH32); // In P2WSH we can have up to 20 keys
-Check("sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv)))","sh(wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd)))", "sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv)))","sh(wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd)))", SIGNABLE, {{"a914c2c9c510e9d7f92fd6131e94803a8d34a8ef675e87"}}, OutputType::P2SH_SEGWIT); // Even if it's wrapped into P2SH
+ Check("wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv))","wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd))", "wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd))", SIGNABLE, {{"0020376bd8344b8b6ebe504ff85ef743eaa1aa9272178223bcb6887e9378efb341ac"}}, OutputType::BECH32); // In P2WSH we can have up to 20 keys
+ Check("sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv)))","sh(wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd)))", "sh(wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd)))", SIGNABLE, {{"a914c2c9c510e9d7f92fd6131e94803a8d34a8ef675e87"}}, OutputType::P2SH_SEGWIT); // Even if it's wrapped into P2SH
// Check for invalid nesting of structures
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))", "Can only have combo() at top level"); // Old must be top level
@@ -451,8 +451,8 @@ Check("sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGN
CheckUnparsable("wsh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "wsh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "Can only have wsh() at top level or inside sh()"); // 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", "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
- Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
+ Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
+ Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}});
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
@@ -465,6 +465,29 @@ Check("sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGN
CheckUnparsable("", "raw(asdf)", "Raw script is not hex"); // Invalid script
CheckUnparsable("", "raw(Ü)#00000000", "Invalid characters in payload"); // Invalid chars
+ Check(
+ "rawtr(xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/86'/1'/0'/1/*)#a5gn3t7k",
+ "rawtr(xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/86'/1'/0'/1/*)#4ur3xhft",
+ "rawtr([5a61ff8e/86'/1'/0']xpub6DtZpc9PRL2B6pwoNGysmHAaBofDmWv5S6KQEKKGPKhf5fV62ywDtSziSApYVK3JnYY5KUSgiCwiXW5wtd8z7LNBxT9Mu5sEro8itdGfTeA/1/*)#llheyd9x",
+ RANGE | HARDENED | XONLY_KEYS,
+ {{"51205172af752f057d543ce8e4a6f8dcf15548ec6be44041bfa93b72e191cfc8c1ee"}, {"51201b66f20b86f700c945ecb9ad9b0ad1662b73084e2bfea48bee02126350b8a5b1"}, {"512063e70f66d815218abcc2306aa930aaca07c5cde73b75127eb27b5e8c16b58a25"}},
+ OutputType::BECH32M,
+ {{0x80000056, 0x80000001, 0x80000000, 1, 0}, {0x80000056, 0x80000001, 0x80000000, 1, 1}, {0x80000056, 0x80000001, 0x80000000, 1, 2}});
+
+
+ Check(
+ "rawtr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)",
+ "rawtr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)",
+ "rawtr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)",
+ SIGNABLE | XONLY_KEYS,
+ {{"5120a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd"}},
+ OutputType::BECH32M);
+
+ CheckUnparsable(
+ "",
+ "rawtr(xpub68FQ9imX6mCWacw6eNRjaa8q8ynnHmUd5i7MVR51ZMPP5JycyfVHSLQVFPHMYiTybWJnSBL2tCBpy6aJTR2DYrshWYfwAxs8SosGXd66d8/*, xpub69Mvq3QMipdvnd9hAyeTnT5jrkcBuLErV212nsGf3qr7JPWysc9HnNhCsazdzj1etSx28hPSE8D7DnceFbNdw4Kg8SyRfjE2HFLv1P8TSGc/*)",
+ "rawtr(): only one key expected.");
+
// A 2of4 but using a direct push rather than OP_2
CScript nonminimalmultisig;
CKey keys[4];
@@ -505,9 +528,9 @@ Check("sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGN
CheckUnparsable("wsh(and_b(and_b(older(1),a:older(100000000)),s:pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))", "wsh(and_b(and_b(older(1),a:older(100000000)),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", "and_b(older(1),a:older(100000000)) is not sane: contains mixes of timelocks expressed in blocks and seconds");
CheckUnparsable("wsh(and_b(or_b(pkh(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),s:pk(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn)),s:pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))", "wsh(and_b(or_b(pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0)),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", "and_b(or_b(pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0)),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)) is not sane: contains duplicate public keys");
// Valid with extended keys.
- Check("wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)))", "wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)))", "wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)))", "wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)))", UNSOLVABLE, {{"0020acf425291b98a1d7e0d4690139442abc289175be32ef1f75945e339924246d73"}}, OutputType::BECH32, {{},{0}});
+ Check("wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)))", "wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)))", "wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)))", UNSOLVABLE, {{"0020acf425291b98a1d7e0d4690139442abc289175be32ef1f75945e339924246d73"}}, OutputType::BECH32, {{},{0}});
// Valid under sh(wsh()) and with a mix of xpubs and raw keys
- Check("sh(wsh(thresh(1,pkh(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),a:and_n(multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0),n:older(2)))))", "sh(wsh(thresh(1,pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),a:and_n(multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0),n:older(2)))))", "sh(wsh(thresh(1,pkh(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),a:and_n(multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0),n:older(2)))))", "sh(wsh(thresh(1,pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),a:and_n(multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0),n:older(2)))))", UNSOLVABLE | MIXED_PUBKEYS, {{"a914767e9119ff3b3ac0cb6dcfe21de1842ccf85f1c487"}}, OutputType::P2SH_SEGWIT, {{},{0}});
+ Check("sh(wsh(thresh(1,pkh(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),a:and_n(multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0),n:older(2)))))", "sh(wsh(thresh(1,pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),a:and_n(multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0),n:older(2)))))", "sh(wsh(thresh(1,pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),a:and_n(multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0),n:older(2)))))", UNSOLVABLE | MIXED_PUBKEYS, {{"a914767e9119ff3b3ac0cb6dcfe21de1842ccf85f1c487"}}, OutputType::P2SH_SEGWIT, {{},{0}});
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/fuzz/bitdeque.cpp b/src/test/fuzz/bitdeque.cpp
new file mode 100644
index 0000000000..634a3de346
--- /dev/null
+++ b/src/test/fuzz/bitdeque.cpp
@@ -0,0 +1,541 @@
+// Copyright (c) 2022 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 <random.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/util.h>
+#include <util/bitdeque.h>
+
+#include <deque>
+#include <vector>
+
+namespace {
+
+constexpr int LEN_BITS = 16;
+constexpr int RANDDATA_BITS = 20;
+
+using bitdeque_type = bitdeque<128>;
+
+//! Deterministic random vector of bools, for begin/end insertions to draw from.
+std::vector<bool> RANDDATA;
+
+void InitRandData()
+{
+ FastRandomContext ctx(true);
+ RANDDATA.clear();
+ for (size_t i = 0; i < (1U << RANDDATA_BITS) + (1U << LEN_BITS); ++i) {
+ RANDDATA.push_back(ctx.randbool());
+ }
+}
+
+} // namespace
+
+FUZZ_TARGET_INIT(bitdeque, InitRandData)
+{
+ FuzzedDataProvider provider(buffer.data(), buffer.size());
+ FastRandomContext ctx(true);
+
+ size_t maxlen = (1U << provider.ConsumeIntegralInRange<size_t>(0, LEN_BITS)) - 1;
+ size_t limitlen = 4 * maxlen;
+
+ std::deque<bool> deq;
+ bitdeque_type bitdeq;
+
+ const auto& cdeq = deq;
+ const auto& cbitdeq = bitdeq;
+
+ size_t initlen = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ while (initlen) {
+ bool val = ctx.randbool();
+ deq.push_back(val);
+ bitdeq.push_back(val);
+ --initlen;
+ }
+
+ LIMITED_WHILE(provider.remaining_bytes() > 0, 900)
+ {
+ {
+ assert(deq.size() == bitdeq.size());
+ auto it = deq.begin();
+ auto bitit = bitdeq.begin();
+ auto itend = deq.end();
+ while (it != itend) {
+ assert(*it == *bitit);
+ ++it;
+ ++bitit;
+ }
+ }
+
+ CallOneOf(provider,
+ [&] {
+ // constructor()
+ deq = std::deque<bool>{};
+ bitdeq = bitdeque_type{};
+ },
+ [&] {
+ // clear()
+ deq.clear();
+ bitdeq.clear();
+ },
+ [&] {
+ // resize()
+ auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ deq.resize(count);
+ bitdeq.resize(count);
+ },
+ [&] {
+ // assign(count, val)
+ auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ bool val = ctx.randbool();
+ deq.assign(count, val);
+ bitdeq.assign(count, val);
+ },
+ [&] {
+ // constructor(count, val)
+ auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ bool val = ctx.randbool();
+ deq = std::deque<bool>(count, val);
+ bitdeq = bitdeque_type(count, val);
+ },
+ [&] {
+ // constructor(count)
+ auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ deq = std::deque<bool>(count);
+ bitdeq = bitdeque_type(count);
+ },
+ [&] {
+ // construct(begin, end)
+ auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS);
+ auto rand_end = rand_begin + count;
+ deq = std::deque<bool>(rand_begin, rand_end);
+ bitdeq = bitdeque_type(rand_begin, rand_end);
+ },
+ [&] {
+ // assign(begin, end)
+ auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS);
+ auto rand_end = rand_begin + count;
+ deq.assign(rand_begin, rand_end);
+ bitdeq.assign(rand_begin, rand_end);
+ },
+ [&] {
+ // construct(initializer_list)
+ std::initializer_list<bool> ilist{ctx.randbool(), ctx.randbool(), ctx.randbool(), ctx.randbool(), ctx.randbool()};
+ deq = std::deque<bool>(ilist);
+ bitdeq = bitdeque_type(ilist);
+ },
+ [&] {
+ // assign(initializer_list)
+ std::initializer_list<bool> ilist{ctx.randbool(), ctx.randbool(), ctx.randbool()};
+ deq.assign(ilist);
+ bitdeq.assign(ilist);
+ },
+ [&] {
+ // operator=(const&)
+ auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ bool val = ctx.randbool();
+ const std::deque<bool> deq2(count, val);
+ deq = deq2;
+ const bitdeque_type bitdeq2(count, val);
+ bitdeq = bitdeq2;
+ },
+ [&] {
+ // operator=(&&)
+ auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ bool val = ctx.randbool();
+ std::deque<bool> deq2(count, val);
+ deq = std::move(deq2);
+ bitdeque_type bitdeq2(count, val);
+ bitdeq = std::move(bitdeq2);
+ },
+ [&] {
+ // deque swap
+ auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS);
+ auto rand_end = rand_begin + count;
+ std::deque<bool> deq2(rand_begin, rand_end);
+ bitdeque_type bitdeq2(rand_begin, rand_end);
+ using std::swap;
+ assert(deq.size() == bitdeq.size());
+ assert(deq2.size() == bitdeq2.size());
+ swap(deq, deq2);
+ swap(bitdeq, bitdeq2);
+ assert(deq.size() == bitdeq.size());
+ assert(deq2.size() == bitdeq2.size());
+ },
+ [&] {
+ // deque.swap
+ auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS);
+ auto rand_end = rand_begin + count;
+ std::deque<bool> deq2(rand_begin, rand_end);
+ bitdeque_type bitdeq2(rand_begin, rand_end);
+ assert(deq.size() == bitdeq.size());
+ assert(deq2.size() == bitdeq2.size());
+ deq.swap(deq2);
+ bitdeq.swap(bitdeq2);
+ assert(deq.size() == bitdeq.size());
+ assert(deq2.size() == bitdeq2.size());
+ },
+ [&] {
+ // operator=(initializer_list)
+ std::initializer_list<bool> ilist{ctx.randbool(), ctx.randbool(), ctx.randbool()};
+ deq = ilist;
+ bitdeq = ilist;
+ },
+ [&] {
+ // iterator arithmetic
+ auto pos1 = provider.ConsumeIntegralInRange<long>(0, cdeq.size());
+ auto pos2 = provider.ConsumeIntegralInRange<long>(0, cdeq.size());
+ auto it = deq.begin() + pos1;
+ auto bitit = bitdeq.begin() + pos1;
+ if ((size_t)pos1 != cdeq.size()) assert(*it == *bitit);
+ assert(it - deq.begin() == pos1);
+ assert(bitit - bitdeq.begin() == pos1);
+ if (provider.ConsumeBool()) {
+ it += pos2 - pos1;
+ bitit += pos2 - pos1;
+ } else {
+ it -= pos1 - pos2;
+ bitit -= pos1 - pos2;
+ }
+ if ((size_t)pos2 != cdeq.size()) assert(*it == *bitit);
+ assert(deq.end() - it == bitdeq.end() - bitit);
+ if (provider.ConsumeBool()) {
+ if ((size_t)pos2 != cdeq.size()) {
+ ++it;
+ ++bitit;
+ }
+ } else {
+ if (pos2 != 0) {
+ --it;
+ --bitit;
+ }
+ }
+ assert(deq.end() - it == bitdeq.end() - bitit);
+ },
+ [&] {
+ // begin() and end()
+ assert(deq.end() - deq.begin() == bitdeq.end() - bitdeq.begin());
+ },
+ [&] {
+ // begin() and end() (const)
+ assert(cdeq.end() - cdeq.begin() == cbitdeq.end() - cbitdeq.begin());
+ },
+ [&] {
+ // rbegin() and rend()
+ assert(deq.rend() - deq.rbegin() == bitdeq.rend() - bitdeq.rbegin());
+ },
+ [&] {
+ // rbegin() and rend() (const)
+ assert(cdeq.rend() - cdeq.rbegin() == cbitdeq.rend() - cbitdeq.rbegin());
+ },
+ [&] {
+ // cbegin() and cend()
+ assert(cdeq.cend() - cdeq.cbegin() == cbitdeq.cend() - cbitdeq.cbegin());
+ },
+ [&] {
+ // crbegin() and crend()
+ assert(cdeq.crend() - cdeq.crbegin() == cbitdeq.crend() - cbitdeq.crbegin());
+ },
+ [&] {
+ // size() and maxsize()
+ assert(cdeq.size() == cbitdeq.size());
+ assert(cbitdeq.size() <= cbitdeq.max_size());
+ },
+ [&] {
+ // empty
+ assert(cdeq.empty() == cbitdeq.empty());
+ },
+ [&] {
+ // at (in range) and flip
+ if (!cdeq.empty()) {
+ size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 1);
+ auto& ref = deq.at(pos);
+ auto bitref = bitdeq.at(pos);
+ assert(ref == bitref);
+ if (ctx.randbool()) {
+ ref = !ref;
+ bitref.flip();
+ }
+ }
+ },
+ [&] {
+ // at (maybe out of range) and bit assign
+ size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() + maxlen);
+ bool newval = ctx.randbool();
+ bool throw_deq{false}, throw_bitdeq{false};
+ bool val_deq{false}, val_bitdeq{false};
+ try {
+ auto& ref = deq.at(pos);
+ val_deq = ref;
+ ref = newval;
+ } catch (const std::out_of_range&) {
+ throw_deq = true;
+ }
+ try {
+ auto ref = bitdeq.at(pos);
+ val_bitdeq = ref;
+ ref = newval;
+ } catch (const std::out_of_range&) {
+ throw_bitdeq = true;
+ }
+ assert(throw_deq == throw_bitdeq);
+ assert(throw_bitdeq == (pos >= cdeq.size()));
+ if (!throw_deq) assert(val_deq == val_bitdeq);
+ },
+ [&] {
+ // at (maybe out of range) (const)
+ size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() + maxlen);
+ bool throw_deq{false}, throw_bitdeq{false};
+ bool val_deq{false}, val_bitdeq{false};
+ try {
+ auto& ref = cdeq.at(pos);
+ val_deq = ref;
+ } catch (const std::out_of_range&) {
+ throw_deq = true;
+ }
+ try {
+ auto ref = cbitdeq.at(pos);
+ val_bitdeq = ref;
+ } catch (const std::out_of_range&) {
+ throw_bitdeq = true;
+ }
+ assert(throw_deq == throw_bitdeq);
+ assert(throw_bitdeq == (pos >= cdeq.size()));
+ if (!throw_deq) assert(val_deq == val_bitdeq);
+ },
+ [&] {
+ // operator[]
+ if (!cdeq.empty()) {
+ size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 1);
+ assert(deq[pos] == bitdeq[pos]);
+ if (ctx.randbool()) {
+ deq[pos] = !deq[pos];
+ bitdeq[pos].flip();
+ }
+ }
+ },
+ [&] {
+ // operator[] const
+ if (!cdeq.empty()) {
+ size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 1);
+ assert(deq[pos] == bitdeq[pos]);
+ }
+ },
+ [&] {
+ // front()
+ if (!cdeq.empty()) {
+ auto& ref = deq.front();
+ auto bitref = bitdeq.front();
+ assert(ref == bitref);
+ if (ctx.randbool()) {
+ ref = !ref;
+ bitref = !bitref;
+ }
+ }
+ },
+ [&] {
+ // front() const
+ if (!cdeq.empty()) {
+ auto& ref = cdeq.front();
+ auto bitref = cbitdeq.front();
+ assert(ref == bitref);
+ }
+ },
+ [&] {
+ // back() and swap(bool, ref)
+ if (!cdeq.empty()) {
+ auto& ref = deq.back();
+ auto bitref = bitdeq.back();
+ assert(ref == bitref);
+ if (ctx.randbool()) {
+ ref = !ref;
+ bitref.flip();
+ }
+ }
+ },
+ [&] {
+ // back() const
+ if (!cdeq.empty()) {
+ const auto& cdeq = deq;
+ const auto& cbitdeq = bitdeq;
+ auto& ref = cdeq.back();
+ auto bitref = cbitdeq.back();
+ assert(ref == bitref);
+ }
+ },
+ [&] {
+ // push_back()
+ if (cdeq.size() < limitlen) {
+ bool val = ctx.randbool();
+ if (cdeq.empty()) {
+ deq.push_back(val);
+ bitdeq.push_back(val);
+ } else {
+ size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 1);
+ auto& ref = deq[pos];
+ auto bitref = bitdeq[pos];
+ assert(ref == bitref);
+ deq.push_back(val);
+ bitdeq.push_back(val);
+ assert(ref == bitref); // references are not invalidated
+ }
+ }
+ },
+ [&] {
+ // push_front()
+ if (cdeq.size() < limitlen) {
+ bool val = ctx.randbool();
+ if (cdeq.empty()) {
+ deq.push_front(val);
+ bitdeq.push_front(val);
+ } else {
+ size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 1);
+ auto& ref = deq[pos];
+ auto bitref = bitdeq[pos];
+ assert(ref == bitref);
+ deq.push_front(val);
+ bitdeq.push_front(val);
+ assert(ref == bitref); // references are not invalidated
+ }
+ }
+ },
+ [&] {
+ // pop_back()
+ if (!cdeq.empty()) {
+ if (cdeq.size() == 1) {
+ deq.pop_back();
+ bitdeq.pop_back();
+ } else {
+ size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 2);
+ auto& ref = deq[pos];
+ auto bitref = bitdeq[pos];
+ assert(ref == bitref);
+ deq.pop_back();
+ bitdeq.pop_back();
+ assert(ref == bitref); // references to other elements are not invalidated
+ }
+ }
+ },
+ [&] {
+ // pop_front()
+ if (!cdeq.empty()) {
+ if (cdeq.size() == 1) {
+ deq.pop_front();
+ bitdeq.pop_front();
+ } else {
+ size_t pos = provider.ConsumeIntegralInRange<size_t>(1, cdeq.size() - 1);
+ auto& ref = deq[pos];
+ auto bitref = bitdeq[pos];
+ assert(ref == bitref);
+ deq.pop_front();
+ bitdeq.pop_front();
+ assert(ref == bitref); // references to other elements are not invalidated
+ }
+ }
+ },
+ [&] {
+ // erase (in middle, single)
+ if (!cdeq.empty()) {
+ size_t before = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 1);
+ size_t after = cdeq.size() - 1 - before;
+ auto it = deq.erase(cdeq.begin() + before);
+ auto bitit = bitdeq.erase(cbitdeq.begin() + before);
+ assert(it == cdeq.begin() + before && it == cdeq.end() - after);
+ assert(bitit == cbitdeq.begin() + before && bitit == cbitdeq.end() - after);
+ }
+ },
+ [&] {
+ // erase (at front, range)
+ size_t count = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size());
+ auto it = deq.erase(cdeq.begin(), cdeq.begin() + count);
+ auto bitit = bitdeq.erase(cbitdeq.begin(), cbitdeq.begin() + count);
+ assert(it == deq.begin());
+ assert(bitit == bitdeq.begin());
+ },
+ [&] {
+ // erase (at back, range)
+ size_t count = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size());
+ auto it = deq.erase(cdeq.end() - count, cdeq.end());
+ auto bitit = bitdeq.erase(cbitdeq.end() - count, cbitdeq.end());
+ assert(it == deq.end());
+ assert(bitit == bitdeq.end());
+ },
+ [&] {
+ // erase (in middle, range)
+ size_t count = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size());
+ size_t before = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - count);
+ size_t after = cdeq.size() - count - before;
+ auto it = deq.erase(cdeq.begin() + before, cdeq.end() - after);
+ auto bitit = bitdeq.erase(cbitdeq.begin() + before, cbitdeq.end() - after);
+ assert(it == cdeq.begin() + before && it == cdeq.end() - after);
+ assert(bitit == cbitdeq.begin() + before && bitit == cbitdeq.end() - after);
+ },
+ [&] {
+ // insert/emplace (in middle, single)
+ if (cdeq.size() < limitlen) {
+ size_t before = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size());
+ bool val = ctx.randbool();
+ bool do_emplace = provider.ConsumeBool();
+ auto it = deq.insert(cdeq.begin() + before, val);
+ auto bitit = do_emplace ? bitdeq.emplace(cbitdeq.begin() + before, val)
+ : bitdeq.insert(cbitdeq.begin() + before, val);
+ assert(it == deq.begin() + before);
+ assert(bitit == bitdeq.begin() + before);
+ }
+ },
+ [&] {
+ // insert (at front, begin/end)
+ if (cdeq.size() < limitlen) {
+ size_t count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS);
+ auto rand_end = rand_begin + count;
+ auto it = deq.insert(cdeq.begin(), rand_begin, rand_end);
+ auto bitit = bitdeq.insert(cbitdeq.begin(), rand_begin, rand_end);
+ assert(it == cdeq.begin());
+ assert(bitit == cbitdeq.begin());
+ }
+ },
+ [&] {
+ // insert (at back, begin/end)
+ if (cdeq.size() < limitlen) {
+ size_t count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS);
+ auto rand_end = rand_begin + count;
+ auto it = deq.insert(cdeq.end(), rand_begin, rand_end);
+ auto bitit = bitdeq.insert(cbitdeq.end(), rand_begin, rand_end);
+ assert(it == cdeq.end() - count);
+ assert(bitit == cbitdeq.end() - count);
+ }
+ },
+ [&] {
+ // insert (in middle, range)
+ if (cdeq.size() < limitlen) {
+ size_t count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ size_t before = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size());
+ bool val = ctx.randbool();
+ auto it = deq.insert(cdeq.begin() + before, count, val);
+ auto bitit = bitdeq.insert(cbitdeq.begin() + before, count, val);
+ assert(it == deq.begin() + before);
+ assert(bitit == bitdeq.begin() + before);
+ }
+ },
+ [&] {
+ // insert (in middle, begin/end)
+ if (cdeq.size() < limitlen) {
+ size_t count = provider.ConsumeIntegralInRange<size_t>(0, maxlen);
+ size_t before = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size());
+ auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS);
+ auto rand_end = rand_begin + count;
+ auto it = deq.insert(cdeq.begin() + before, rand_begin, rand_end);
+ auto bitit = bitdeq.insert(cbitdeq.begin() + before, rand_begin, rand_end);
+ assert(it == deq.begin() + before);
+ assert(bitit == bitdeq.begin() + before);
+ }
+ }
+ );
+ }
+}
diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp
index 72574612a2..f05248ab47 100644
--- a/src/test/fuzz/integer.cpp
+++ b/src/test/fuzz/integer.cpp
@@ -12,6 +12,7 @@
#include <key_io.h>
#include <memusage.h>
#include <netbase.h>
+#include <policy/policy.h>
#include <policy/settings.h>
#include <pow.h>
#include <protocol.h>
@@ -87,9 +88,6 @@ FUZZ_TARGET_INIT(integer, initialize_integer)
}
(void)GetSizeOfCompactSize(u64);
(void)GetSpecialScriptSize(u32);
- if (!MultiplicationOverflow(i64, static_cast<int64_t>(::nBytesPerSigOp)) && !AdditionOverflow(i64 * ::nBytesPerSigOp, static_cast<int64_t>(4))) {
- (void)GetVirtualTransactionSize(i64, i64);
- }
if (!MultiplicationOverflow(i64, static_cast<int64_t>(u32)) && !AdditionOverflow(i64, static_cast<int64_t>(4)) && !AdditionOverflow(i64 * u32, static_cast<int64_t>(4))) {
(void)GetVirtualTransactionSize(i64, i64, u32);
}
diff --git a/src/test/fuzz/key.cpp b/src/test/fuzz/key.cpp
index bfea9778f4..a76901e473 100644
--- a/src/test/fuzz/key.cpp
+++ b/src/test/fuzz/key.cpp
@@ -138,8 +138,6 @@ FUZZ_TARGET_INIT(key, initialize_key)
assert(tx_multisig_script.size() == 37);
FillableSigningProvider fillable_signing_provider;
- assert(IsSolvable(fillable_signing_provider, tx_pubkey_script));
- assert(IsSolvable(fillable_signing_provider, tx_multisig_script));
assert(!IsSegWitOutput(fillable_signing_provider, tx_pubkey_script));
assert(!IsSegWitOutput(fillable_signing_provider, tx_multisig_script));
assert(fillable_signing_provider.GetKeys().size() == 0);
@@ -157,12 +155,12 @@ FUZZ_TARGET_INIT(key, initialize_key)
assert(fillable_signing_provider_pub.HaveKey(pubkey.GetID()));
TxoutType which_type_tx_pubkey;
- const bool is_standard_tx_pubkey = IsStandard(tx_pubkey_script, which_type_tx_pubkey);
+ const bool is_standard_tx_pubkey = IsStandard(tx_pubkey_script, std::nullopt, which_type_tx_pubkey);
assert(is_standard_tx_pubkey);
assert(which_type_tx_pubkey == TxoutType::PUBKEY);
TxoutType which_type_tx_multisig;
- const bool is_standard_tx_multisig = IsStandard(tx_multisig_script, which_type_tx_multisig);
+ const bool is_standard_tx_multisig = IsStandard(tx_multisig_script, std::nullopt, which_type_tx_multisig);
assert(is_standard_tx_multisig);
assert(which_type_tx_multisig == TxoutType::MULTISIG);
diff --git a/src/test/fuzz/mempool_utils.h b/src/test/fuzz/mempool_utils.h
deleted file mode 100644
index bfe12e30ba..0000000000
--- a/src/test/fuzz/mempool_utils.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) 2022 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_TEST_FUZZ_MEMPOOL_UTILS_H
-#define BITCOIN_TEST_FUZZ_MEMPOOL_UTILS_H
-
-#include <validation.h>
-
-class DummyChainState final : public CChainState
-{
-public:
- void SetMempool(CTxMemPool* mempool)
- {
- m_mempool = mempool;
- }
-};
-
-#endif // BITCOIN_TEST_FUZZ_MEMPOOL_UTILS_H
diff --git a/src/test/fuzz/policy_estimator.cpp b/src/test/fuzz/policy_estimator.cpp
index 637ba503c6..a3d57dbdd5 100644
--- a/src/test/fuzz/policy_estimator.cpp
+++ b/src/test/fuzz/policy_estimator.cpp
@@ -8,6 +8,7 @@
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
+#include <test/fuzz/util/mempool.h>
#include <test/util/setup_common.h>
#include <txmempool.h>
diff --git a/src/test/fuzz/pow.cpp b/src/test/fuzz/pow.cpp
index 0004d82d66..eba03da773 100644
--- a/src/test/fuzz/pow.cpp
+++ b/src/test/fuzz/pow.cpp
@@ -83,3 +83,40 @@ FUZZ_TARGET_INIT(pow, initialize_pow)
}
}
}
+
+
+FUZZ_TARGET_INIT(pow_transition, initialize_pow)
+{
+ FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
+ const Consensus::Params& consensus_params{Params().GetConsensus()};
+ std::vector<std::unique_ptr<CBlockIndex>> blocks;
+
+ const uint32_t old_time{fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
+ const uint32_t new_time{fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
+ const int32_t version{fuzzed_data_provider.ConsumeIntegral<int32_t>()};
+ uint32_t nbits{fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
+
+ const arith_uint256 pow_limit = UintToArith256(consensus_params.powLimit);
+ arith_uint256 old_target;
+ old_target.SetCompact(nbits);
+ if (old_target > pow_limit) {
+ nbits = pow_limit.GetCompact();
+ }
+ // Create one difficulty adjustment period worth of headers
+ for (int height = 0; height < consensus_params.DifficultyAdjustmentInterval(); ++height) {
+ CBlockHeader header;
+ header.nVersion = version;
+ header.nTime = old_time;
+ header.nBits = nbits;
+ if (height == consensus_params.DifficultyAdjustmentInterval() - 1) {
+ header.nTime = new_time;
+ }
+ auto current_block{std::make_unique<CBlockIndex>(header)};
+ current_block->pprev = blocks.empty() ? nullptr : blocks.back().get();
+ current_block->nHeight = height;
+ blocks.emplace_back(std::move(current_block));
+ }
+ auto last_block{blocks.back().get()};
+ unsigned int new_nbits{GetNextWorkRequired(last_block, nullptr, consensus_params)};
+ Assert(PermittedDifficultyTransition(consensus_params, last_block->nHeight + 1, last_block->nBits, new_nbits));
+}
diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp
index 272c9e6cdc..5a4df735da 100644
--- a/src/test/fuzz/process_message.cpp
+++ b/src/test/fuzz/process_message.cpp
@@ -73,6 +73,8 @@ void fuzz_target(FuzzBufferType buffer, const std::string& LIMIT_TO_MESSAGE_TYPE
SetMockTime(1610000000); // any time to successfully reset ibd
chainstate.ResetIbd();
+ LOCK(NetEventsInterface::g_msgproc_mutex);
+
const std::string random_message_type{fuzzed_data_provider.ConsumeBytesAsString(CMessageHeader::COMMAND_SIZE).c_str()};
if (!LIMIT_TO_MESSAGE_TYPE.empty() && random_message_type != LIMIT_TO_MESSAGE_TYPE) {
return;
@@ -92,10 +94,7 @@ void fuzz_target(FuzzBufferType buffer, const std::string& LIMIT_TO_MESSAGE_TYPE
GetTime<std::chrono::microseconds>(), std::atomic<bool>{false});
} catch (const std::ios_base::failure&) {
}
- {
- LOCK(p2p_node.cs_sendProcessing);
- g_setup->m_node.peerman->SendMessages(&p2p_node);
- }
+ g_setup->m_node.peerman->SendMessages(&p2p_node);
SyncWithValidationInterfaceQueue();
g_setup->m_node.connman->StopNodes();
}
@@ -131,6 +130,7 @@ FUZZ_TARGET_MSG(pong);
FUZZ_TARGET_MSG(sendaddrv2);
FUZZ_TARGET_MSG(sendcmpct);
FUZZ_TARGET_MSG(sendheaders);
+FUZZ_TARGET_MSG(sendtxrcncl);
FUZZ_TARGET_MSG(tx);
FUZZ_TARGET_MSG(verack);
FUZZ_TARGET_MSG(version);
diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp
index 12e682416c..1df1717ec3 100644
--- a/src/test/fuzz/process_messages.cpp
+++ b/src/test/fuzz/process_messages.cpp
@@ -40,6 +40,8 @@ FUZZ_TARGET_INIT(process_messages, initialize_process_messages)
SetMockTime(1610000000); // any time to successfully reset ibd
chainstate.ResetIbd();
+ LOCK(NetEventsInterface::g_msgproc_mutex);
+
std::vector<CNode*> peers;
const auto num_peers_to_add = fuzzed_data_provider.ConsumeIntegralInRange(1, 3);
for (int i = 0; i < num_peers_to_add; ++i) {
@@ -70,10 +72,7 @@ FUZZ_TARGET_INIT(process_messages, initialize_process_messages)
connman.ProcessMessagesOnce(random_node);
} catch (const std::ios_base::failure&) {
}
- {
- LOCK(random_node.cs_sendProcessing);
- g_setup->m_node.peerman->SendMessages(&random_node);
- }
+ g_setup->m_node.peerman->SendMessages(&random_node);
}
SyncWithValidationInterfaceQueue();
g_setup->m_node.connman->StopNodes();
diff --git a/src/test/fuzz/rbf.cpp b/src/test/fuzz/rbf.cpp
index 4801635791..678fc7a5aa 100644
--- a/src/test/fuzz/rbf.cpp
+++ b/src/test/fuzz/rbf.cpp
@@ -2,14 +2,16 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include <mempool_args.h>
+#include <node/mempool_args.h>
#include <policy/rbf.h>
#include <primitives/transaction.h>
#include <sync.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
+#include <test/fuzz/util/mempool.h>
#include <test/util/setup_common.h>
+#include <test/util/txmempool.h>
#include <txmempool.h>
#include <cstdint>
diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp
index 26913a41d2..f32046e69f 100644
--- a/src/test/fuzz/rpc.cpp
+++ b/src/test/fuzz/rpc.cpp
@@ -151,6 +151,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
"preciousblock",
"pruneblockchain",
"reconsiderblock",
+ "scanblocks",
"scantxoutset",
"sendrawtransaction",
"setmocktime",
diff --git a/src/test/fuzz/script.cpp b/src/test/fuzz/script.cpp
index fdcd0da37d..00d7b7e29a 100644
--- a/src/test/fuzz/script.cpp
+++ b/src/test/fuzz/script.cpp
@@ -55,7 +55,7 @@ FUZZ_TARGET_INIT(script, initialize_script)
}
TxoutType which_type;
- bool is_standard_ret = IsStandard(script, which_type);
+ bool is_standard_ret = IsStandard(script, std::nullopt, which_type);
if (!is_standard_ret) {
assert(which_type == TxoutType::NONSTANDARD ||
which_type == TxoutType::NULL_DATA ||
@@ -89,7 +89,6 @@ FUZZ_TARGET_INIT(script, initialize_script)
const FlatSigningProvider signing_provider;
(void)InferDescriptor(script, signing_provider);
(void)IsSegWitOutput(signing_provider, script);
- (void)IsSolvable(signing_provider, script);
(void)RecursiveDynamicUsage(script);
diff --git a/src/test/fuzz/script_sigcache.cpp b/src/test/fuzz/script_sigcache.cpp
index f7e45d6889..f6af7947df 100644
--- a/src/test/fuzz/script_sigcache.cpp
+++ b/src/test/fuzz/script_sigcache.cpp
@@ -10,18 +10,21 @@
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
+#include <test/util/setup_common.h>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
+namespace {
+const BasicTestingSetup* g_setup;
+} // namespace
+
void initialize_script_sigcache()
{
- static const ECCVerifyHandle ecc_verify_handle;
- ECC_Start();
- SelectParams(CBaseChainParams::REGTEST);
- InitSignatureCache();
+ static const auto testing_setup = MakeNoLogFileContext<>();
+ g_setup = testing_setup.get();
}
FUZZ_TARGET_INIT(script_sigcache, initialize_script_sigcache)
diff --git a/src/test/fuzz/string.cpp b/src/test/fuzz/string.cpp
index 94399faf04..f6373351d8 100644
--- a/src/test/fuzz/string.cpp
+++ b/src/test/fuzz/string.cpp
@@ -4,6 +4,7 @@
#include <blockfilter.h>
#include <clientversion.h>
+#include <common/url.h>
#include <logging.h>
#include <netaddress.h>
#include <netbase.h>
@@ -27,7 +28,6 @@
#include <util/string.h>
#include <util/system.h>
#include <util/translation.h>
-#include <util/url.h>
#include <version.h>
#include <cstdint>
diff --git a/src/test/fuzz/transaction.cpp b/src/test/fuzz/transaction.cpp
index 273aa0dc5c..7fa4523800 100644
--- a/src/test/fuzz/transaction.cpp
+++ b/src/test/fuzz/transaction.cpp
@@ -69,8 +69,8 @@ FUZZ_TARGET_INIT(transaction, initialize_transaction)
const CFeeRate dust_relay_fee{DUST_RELAY_TX_FEE};
std::string reason;
- const bool is_standard_with_permit_bare_multisig = IsStandardTx(tx, /* permit_bare_multisig= */ true, dust_relay_fee, reason);
- const bool is_standard_without_permit_bare_multisig = IsStandardTx(tx, /* permit_bare_multisig= */ false, dust_relay_fee, reason);
+ const bool is_standard_with_permit_bare_multisig = IsStandardTx(tx, std::nullopt, /* permit_bare_multisig= */ true, dust_relay_fee, reason);
+ const bool is_standard_without_permit_bare_multisig = IsStandardTx(tx, std::nullopt, /* permit_bare_multisig= */ false, dust_relay_fee, reason);
if (is_standard_without_permit_bare_multisig) {
assert(is_standard_with_permit_bare_multisig);
}
@@ -92,7 +92,6 @@ FUZZ_TARGET_INIT(transaction, initialize_transaction)
(void)GetTransactionWeight(tx);
(void)GetVirtualTransactionSize(tx);
(void)IsFinalTx(tx, /* nBlockHeight= */ 1024, /* nBlockTime= */ 1024);
- (void)IsStandardTx(tx, reason);
(void)RecursiveDynamicUsage(tx);
(void)SignalsOptInRBF(tx);
diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp
index cfb112879a..46ca8e47e9 100644
--- a/src/test/fuzz/tx_pool.cpp
+++ b/src/test/fuzz/tx_pool.cpp
@@ -3,16 +3,17 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <consensus/validation.h>
-#include <mempool_args.h>
#include <node/context.h>
+#include <node/mempool_args.h>
#include <node/miner.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
-#include <test/fuzz/mempool_utils.h>
#include <test/fuzz/util.h>
+#include <test/fuzz/util/mempool.h>
#include <test/util/mining.h>
#include <test/util/script.h>
#include <test/util/setup_common.h>
+#include <test/util/txmempool.h>
#include <util/rbf.h>
#include <validation.h>
#include <validationinterface.h>
@@ -85,7 +86,7 @@ void SetMempoolConstraints(ArgsManager& args, FuzzedDataProvider& fuzzed_data_pr
ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)));
}
-void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, CChainState& chainstate)
+void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Chainstate& chainstate)
{
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
{
@@ -108,7 +109,7 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, CCh
SyncWithValidationInterfaceQueue();
}
-void MockTime(FuzzedDataProvider& fuzzed_data_provider, const CChainState& chainstate)
+void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
{
const auto time = ConsumeTime(fuzzed_data_provider,
chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
@@ -116,7 +117,7 @@ void MockTime(FuzzedDataProvider& fuzzed_data_provider, const CChainState& chain
SetMockTime(time);
}
-CTxMemPool MakeMempool(const NodeContext& node)
+CTxMemPool MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)
{
// Take the default options for tests...
CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
@@ -124,6 +125,7 @@ CTxMemPool MakeMempool(const NodeContext& node)
// ...override specific options for this specific fuzz suite
mempool_opts.estimator = nullptr;
mempool_opts.check_ratio = 1;
+ mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();
// ...and construct a CTxMemPool from it
return CTxMemPool{mempool_opts};
@@ -150,7 +152,7 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
constexpr CAmount SUPPLY_TOTAL{COINBASE_MATURITY * 50 * COIN};
SetMempoolConstraints(*node.args, fuzzed_data_provider);
- CTxMemPool tx_pool_{MakeMempool(node)};
+ CTxMemPool tx_pool_{MakeMempool(fuzzed_data_provider, node)};
MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_);
chainstate.SetMempool(&tx_pool);
@@ -237,7 +239,6 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
auto txr = std::make_shared<TransactionsDelta>(removed, added);
RegisterSharedValidationInterface(txr);
const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
- ::fRequireStandard = fuzzed_data_provider.ConsumeBool();
// Make sure ProcessNewPackage on one transaction works.
// The result is not guaranteed to be the same as what is returned by ATMP.
@@ -325,7 +326,7 @@ FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool)
}
SetMempoolConstraints(*node.args, fuzzed_data_provider);
- CTxMemPool tx_pool_{MakeMempool(node)};
+ CTxMemPool tx_pool_{MakeMempool(fuzzed_data_provider, node)};
MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_);
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300)
@@ -348,7 +349,6 @@ FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool)
const auto tx = MakeTransactionRef(mut_tx);
const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
- ::fRequireStandard = fuzzed_data_provider.ConsumeBool();
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), bypass_limits, /*test_accept=*/false));
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
if (accepted) {
diff --git a/src/test/fuzz/txorphan.cpp b/src/test/fuzz/txorphan.cpp
index 7580651371..55060f31cf 100644
--- a/src/test/fuzz/txorphan.cpp
+++ b/src/test/fuzz/txorphan.cpp
@@ -19,7 +19,6 @@
#include <util/check.h>
#include <util/time.h>
-#include <algorithm>
#include <cstdint>
#include <memory>
#include <set>
@@ -46,7 +45,7 @@ FUZZ_TARGET_INIT(txorphan, initialize_orphanage)
// if true, allow duplicate input when constructing tx
const bool duplicate_input = fuzzed_data_provider.ConsumeBool();
- LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS)
+ LIMITED_WHILE(outpoints.size() < 200'000 && fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS)
{
// construct transaction
const CTransactionRef tx = [&] {
diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp
index ba1a634e41..d495a6bfe3 100644
--- a/src/test/fuzz/util.cpp
+++ b/src/test/fuzz/util.cpp
@@ -16,7 +16,7 @@
#include <memory>
FuzzedSock::FuzzedSock(FuzzedDataProvider& fuzzed_data_provider)
- : m_fuzzed_data_provider{fuzzed_data_provider}
+ : m_fuzzed_data_provider{fuzzed_data_provider}, m_selectable{fuzzed_data_provider.ConsumeBool()}
{
m_socket = fuzzed_data_provider.ConsumeIntegralInRange<SOCKET>(INVALID_SOCKET - 1, INVALID_SOCKET);
}
@@ -254,6 +254,24 @@ int FuzzedSock::GetSockName(sockaddr* name, socklen_t* name_len) const
return 0;
}
+bool FuzzedSock::SetNonBlocking() const
+{
+ constexpr std::array setnonblocking_errnos{
+ EBADF,
+ EPERM,
+ };
+ if (m_fuzzed_data_provider.ConsumeBool()) {
+ SetFuzzedErrNo(m_fuzzed_data_provider, setnonblocking_errnos);
+ return false;
+ }
+ return true;
+}
+
+bool FuzzedSock::IsSelectable() const
+{
+ return m_selectable;
+}
+
bool FuzzedSock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const
{
constexpr std::array wait_errnos{
@@ -295,7 +313,6 @@ void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman,
/*successfully_connected=*/fuzzed_data_provider.ConsumeBool(),
/*remote_services=*/ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS),
/*local_services=*/ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS),
- /*permission_flags=*/ConsumeWeakEnum(fuzzed_data_provider, ALL_NET_PERMISSION_FLAGS),
/*version=*/fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(MIN_PEER_PROTO_VERSION, std::numeric_limits<int32_t>::max()),
/*relay_txs=*/fuzzed_data_provider.ConsumeBool());
}
@@ -308,8 +325,8 @@ CAmount ConsumeMoney(FuzzedDataProvider& fuzzed_data_provider, const std::option
int64_t ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<int64_t>& min, const std::optional<int64_t>& max) noexcept
{
// Avoid t=0 (1970-01-01T00:00:00Z) since SetMockTime(0) disables mocktime.
- static const int64_t time_min{ParseISO8601DateTime("2000-01-01T00:00:01Z")};
- static const int64_t time_max{ParseISO8601DateTime("2100-12-31T23:59:59Z")};
+ static const int64_t time_min{946684801}; // 2000-01-01T00:00:01Z
+ static const int64_t time_max{4133980799}; // 2100-12-31T23:59:59Z
return fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(min.value_or(time_min), max.value_or(time_max));
}
@@ -479,21 +496,6 @@ CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) no
return tx_destination;
}
-CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept
-{
- // Avoid:
- // policy/feerate.cpp:28:34: runtime error: signed integer overflow: 34873208148477500 * 1000 cannot be represented in type 'long'
- //
- // Reproduce using CFeeRate(348732081484775, 10).GetFeePerK()
- const CAmount fee = std::min<CAmount>(ConsumeMoney(fuzzed_data_provider), std::numeric_limits<CAmount>::max() / static_cast<CAmount>(100000));
- assert(MoneyRange(fee));
- const int64_t time = fuzzed_data_provider.ConsumeIntegral<int64_t>();
- const unsigned int entry_height = fuzzed_data_provider.ConsumeIntegral<unsigned int>();
- const bool spends_coinbase = fuzzed_data_provider.ConsumeBool();
- const unsigned int sig_op_cost = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, MAX_BLOCK_SIGOPS_COST);
- return CTxMemPoolEntry{MakeTransactionRef(tx), fee, time, entry_height, spends_coinbase, sig_op_cost, {}};
-}
-
bool ContainsSpentInput(const CTransaction& tx, const CCoinsViewCache& inputs) noexcept
{
for (const CTxIn& tx_in : tx.vin) {
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index 33d9ab3cc3..dfe4855326 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -23,7 +23,6 @@
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/util/net.h>
-#include <txmempool.h>
#include <uint256.h>
#include <version.h>
@@ -48,6 +47,13 @@ class FuzzedSock : public Sock
*/
mutable std::optional<uint8_t> m_peek_data;
+ /**
+ * Whether to pretend that the socket is select(2)-able. This is randomly set in the
+ * constructor. It should remain constant so that repeated calls to `IsSelectable()`
+ * return the same value.
+ */
+ const bool m_selectable;
+
public:
explicit FuzzedSock(FuzzedDataProvider& fuzzed_data_provider);
@@ -73,6 +79,10 @@ public:
int GetSockName(sockaddr* name, socklen_t* name_len) const override;
+ bool SetNonBlocking() const override;
+
+ bool IsSelectable() const override;
+
bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override;
bool WaitMany(std::chrono::milliseconds timeout, EventsPerSock& events_per_sock) const override;
@@ -213,8 +223,6 @@ template <typename WeakEnumType, size_t size>
return UintToArith256(ConsumeUInt256(fuzzed_data_provider));
}
-[[nodiscard]] CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept;
-
[[nodiscard]] CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) noexcept;
template <typename T>
@@ -301,6 +309,7 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N
const std::string addr_name = fuzzed_data_provider.ConsumeRandomLengthString(64);
const ConnectionType conn_type = fuzzed_data_provider.PickValueInArray(ALL_CONNECTION_TYPES);
const bool inbound_onion{conn_type == ConnectionType::INBOUND ? fuzzed_data_provider.ConsumeBool() : false};
+ NetPermissionFlags permission_flags = ConsumeWeakEnum(fuzzed_data_provider, ALL_NET_PERMISSION_FLAGS);
if constexpr (ReturnUniquePtr) {
return std::make_unique<CNode>(node_id,
sock,
@@ -310,7 +319,8 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N
addr_bind,
addr_name,
conn_type,
- inbound_onion);
+ inbound_onion,
+ CNodeOptions{ .permission_flags = permission_flags });
} else {
return CNode{node_id,
sock,
@@ -320,12 +330,13 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N
addr_bind,
addr_name,
conn_type,
- inbound_onion};
+ inbound_onion,
+ CNodeOptions{ .permission_flags = permission_flags }};
}
}
inline std::unique_ptr<CNode> ConsumeNodeAsUniquePtr(FuzzedDataProvider& fdp, const std::optional<NodeId>& node_id_in = std::nullopt) { return ConsumeNode<true>(fdp, node_id_in); }
-void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman, CNode& node) noexcept;
+void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman, CNode& node) noexcept EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex);
class FuzzedFileProvider
{
diff --git a/src/test/fuzz/util/mempool.cpp b/src/test/fuzz/util/mempool.cpp
new file mode 100644
index 0000000000..d0053f77d2
--- /dev/null
+++ b/src/test/fuzz/util/mempool.cpp
@@ -0,0 +1,27 @@
+// Copyright (c) 2022 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 <consensus/amount.h>
+#include <primitives/transaction.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/util.h>
+#include <test/fuzz/util/mempool.h>
+#include <txmempool.h>
+
+#include <limits>
+
+CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept
+{
+ // Avoid:
+ // policy/feerate.cpp:28:34: runtime error: signed integer overflow: 34873208148477500 * 1000 cannot be represented in type 'long'
+ //
+ // Reproduce using CFeeRate(348732081484775, 10).GetFeePerK()
+ const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/std::numeric_limits<CAmount>::max() / CAmount{100'000})};
+ assert(MoneyRange(fee));
+ const int64_t time = fuzzed_data_provider.ConsumeIntegral<int64_t>();
+ const unsigned int entry_height = fuzzed_data_provider.ConsumeIntegral<unsigned int>();
+ const bool spends_coinbase = fuzzed_data_provider.ConsumeBool();
+ const unsigned int sig_op_cost = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, MAX_BLOCK_SIGOPS_COST);
+ return CTxMemPoolEntry{MakeTransactionRef(tx), fee, time, entry_height, spends_coinbase, sig_op_cost, {}};
+}
diff --git a/src/test/fuzz/util/mempool.h b/src/test/fuzz/util/mempool.h
new file mode 100644
index 0000000000..4304e5294e
--- /dev/null
+++ b/src/test/fuzz/util/mempool.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2022 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_TEST_FUZZ_UTIL_MEMPOOL_H
+#define BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H
+
+#include <primitives/transaction.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <txmempool.h>
+#include <validation.h>
+
+class DummyChainState final : public Chainstate
+{
+public:
+ void SetMempool(CTxMemPool* mempool)
+ {
+ m_mempool = mempool;
+ }
+};
+
+[[nodiscard]] CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept;
+
+#endif // BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H
diff --git a/src/test/fuzz/utxo_snapshot.cpp b/src/test/fuzz/utxo_snapshot.cpp
index 0b596492be..8abb943266 100644
--- a/src/test/fuzz/utxo_snapshot.cpp
+++ b/src/test/fuzz/utxo_snapshot.cpp
@@ -58,7 +58,7 @@ FUZZ_TARGET_INIT(utxo_snapshot, initialize_chain)
if (fuzzed_data_provider.ConsumeBool()) {
for (const auto& block : *g_chain) {
BlockValidationState dummy;
- bool processed{chainman.ProcessNewBlockHeaders({*block}, dummy)};
+ bool processed{chainman.ProcessNewBlockHeaders({*block}, true, dummy)};
Assert(processed);
const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
Assert(index);
diff --git a/src/test/fuzz/validation_load_mempool.cpp b/src/test/fuzz/validation_load_mempool.cpp
index 90c1a71d9f..9a90de8911 100644
--- a/src/test/fuzz/validation_load_mempool.cpp
+++ b/src/test/fuzz/validation_load_mempool.cpp
@@ -5,13 +5,14 @@
#include <kernel/mempool_persist.h>
#include <chainparamsbase.h>
-#include <mempool_args.h>
+#include <node/mempool_args.h>
#include <node/mempool_persist_args.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
-#include <test/fuzz/mempool_utils.h>
#include <test/fuzz/util.h>
+#include <test/fuzz/util/mempool.h>
#include <test/util/setup_common.h>
+#include <test/util/txmempool.h>
#include <txmempool.h>
#include <util/time.h>
#include <validation.h>
diff --git a/src/test/headers_sync_chainwork_tests.cpp b/src/test/headers_sync_chainwork_tests.cpp
new file mode 100644
index 0000000000..41241ebee2
--- /dev/null
+++ b/src/test/headers_sync_chainwork_tests.cpp
@@ -0,0 +1,146 @@
+// Copyright (c) 2022 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 <chain.h>
+#include <chainparams.h>
+#include <consensus/params.h>
+#include <headerssync.h>
+#include <pow.h>
+#include <test/util/setup_common.h>
+#include <validation.h>
+#include <vector>
+
+#include <boost/test/unit_test.hpp>
+
+struct HeadersGeneratorSetup : public RegTestingSetup {
+ /** Search for a nonce to meet (regtest) proof of work */
+ void FindProofOfWork(CBlockHeader& starting_header);
+ /**
+ * Generate headers in a chain that build off a given starting hash, using
+ * the given nVersion, advancing time by 1 second from the starting
+ * prev_time, and with a fixed merkle root hash.
+ */
+ void GenerateHeaders(std::vector<CBlockHeader>& headers, size_t count,
+ const uint256& starting_hash, const int nVersion, int prev_time,
+ const uint256& merkle_root, const uint32_t nBits);
+};
+
+void HeadersGeneratorSetup::FindProofOfWork(CBlockHeader& starting_header)
+{
+ while (!CheckProofOfWork(starting_header.GetHash(), starting_header.nBits, Params().GetConsensus())) {
+ ++(starting_header.nNonce);
+ }
+}
+
+void HeadersGeneratorSetup::GenerateHeaders(std::vector<CBlockHeader>& headers,
+ size_t count, const uint256& starting_hash, const int nVersion, int prev_time,
+ const uint256& merkle_root, const uint32_t nBits)
+{
+ uint256 prev_hash = starting_hash;
+
+ while (headers.size() < count) {
+ headers.push_back(CBlockHeader());
+ CBlockHeader& next_header = headers.back();;
+ next_header.nVersion = nVersion;
+ next_header.hashPrevBlock = prev_hash;
+ next_header.hashMerkleRoot = merkle_root;
+ next_header.nTime = prev_time+1;
+ next_header.nBits = nBits;
+
+ FindProofOfWork(next_header);
+ prev_hash = next_header.GetHash();
+ prev_time = next_header.nTime;
+ }
+ return;
+}
+
+BOOST_FIXTURE_TEST_SUITE(headers_sync_chainwork_tests, HeadersGeneratorSetup)
+
+// In this test, we construct two sets of headers from genesis, one with
+// sufficient proof of work and one without.
+// 1. We deliver the first set of headers and verify that the headers sync state
+// updates to the REDOWNLOAD phase successfully.
+// 2. Then we deliver the second set of headers and verify that they fail
+// processing (presumably due to commitments not matching).
+// 3. Finally, we verify that repeating with the first set of headers in both
+// phases is successful.
+BOOST_AUTO_TEST_CASE(headers_sync_state)
+{
+ std::vector<CBlockHeader> first_chain;
+ std::vector<CBlockHeader> second_chain;
+
+ std::unique_ptr<HeadersSyncState> hss;
+
+ const int target_blocks = 15000;
+ arith_uint256 chain_work = target_blocks*2;
+
+ // Generate headers for two different chains (using differing merkle roots
+ // to ensure the headers are different).
+ GenerateHeaders(first_chain, target_blocks-1, Params().GenesisBlock().GetHash(),
+ Params().GenesisBlock().nVersion, Params().GenesisBlock().nTime,
+ ArithToUint256(0), Params().GenesisBlock().nBits);
+
+ GenerateHeaders(second_chain, target_blocks-2, Params().GenesisBlock().GetHash(),
+ Params().GenesisBlock().nVersion, Params().GenesisBlock().nTime,
+ ArithToUint256(1), Params().GenesisBlock().nBits);
+
+ const CBlockIndex* chain_start = WITH_LOCK(::cs_main, return m_node.chainman->m_blockman.LookupBlockIndex(Params().GenesisBlock().GetHash()));
+ std::vector<CBlockHeader> headers_batch;
+
+ // Feed the first chain to HeadersSyncState, by delivering 1 header
+ // initially and then the rest.
+ headers_batch.insert(headers_batch.end(), std::next(first_chain.begin()), first_chain.end());
+
+ hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start, chain_work));
+ (void)hss->ProcessNextHeaders({first_chain.front()}, true);
+ // Pretend the first header is still "full", so we don't abort.
+ auto result = hss->ProcessNextHeaders(headers_batch, true);
+
+ // This chain should look valid, and we should have met the proof-of-work
+ // requirement.
+ BOOST_CHECK(result.success);
+ BOOST_CHECK(result.request_more);
+ BOOST_CHECK(hss->GetState() == HeadersSyncState::State::REDOWNLOAD);
+
+ // Try to sneakily feed back the second chain.
+ result = hss->ProcessNextHeaders(second_chain, true);
+ BOOST_CHECK(!result.success); // foiled!
+ BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL);
+
+ // Now try again, this time feeding the first chain twice.
+ hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start, chain_work));
+ (void)hss->ProcessNextHeaders(first_chain, true);
+ BOOST_CHECK(hss->GetState() == HeadersSyncState::State::REDOWNLOAD);
+
+ result = hss->ProcessNextHeaders(first_chain, true);
+ BOOST_CHECK(result.success);
+ BOOST_CHECK(!result.request_more);
+ // All headers should be ready for acceptance:
+ BOOST_CHECK(result.pow_validated_headers.size() == first_chain.size());
+ // Nothing left for the sync logic to do:
+ BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL);
+
+ // Finally, verify that just trying to process the second chain would not
+ // succeed (too little work)
+ hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start, chain_work));
+ BOOST_CHECK(hss->GetState() == HeadersSyncState::State::PRESYNC);
+ // Pretend just the first message is "full", so we don't abort.
+ (void)hss->ProcessNextHeaders({second_chain.front()}, true);
+ BOOST_CHECK(hss->GetState() == HeadersSyncState::State::PRESYNC);
+
+ headers_batch.clear();
+ headers_batch.insert(headers_batch.end(), std::next(second_chain.begin(), 1), second_chain.end());
+ // Tell the sync logic that the headers message was not full, implying no
+ // more headers can be requested. For a low-work-chain, this should causes
+ // the sync to end with no headers for acceptance.
+ result = hss->ProcessNextHeaders(headers_batch, false);
+ BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL);
+ BOOST_CHECK(result.pow_validated_headers.empty());
+ BOOST_CHECK(!result.request_more);
+ // Nevertheless, no validation errors should have been detected with the
+ // chain:
+ BOOST_CHECK(result.success);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/i2p_tests.cpp b/src/test/i2p_tests.cpp
index bd9ba4b8f7..9da1ee11f9 100644
--- a/src/test/i2p_tests.cpp
+++ b/src/test/i2p_tests.cpp
@@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <i2p.h>
+#include <logging.h>
#include <netaddress.h>
#include <test/util/logging.h>
#include <test/util/net.h>
@@ -19,6 +20,8 @@ BOOST_FIXTURE_TEST_SUITE(i2p_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(unlimited_recv)
{
+ const auto prev_log_level{LogInstance().LogLevel()};
+ LogInstance().SetLogLevel(BCLog::Level::Trace);
auto CreateSockOrig = CreateSock;
// Mock CreateSock() to create MockSock.
@@ -30,7 +33,7 @@ BOOST_AUTO_TEST_CASE(unlimited_recv)
i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key", CService{}, &interrupt);
{
- ASSERT_DEBUG_LOG("Creating SAM session");
+ ASSERT_DEBUG_LOG("Creating persistent SAM session");
ASSERT_DEBUG_LOG("too many bytes without a terminator");
i2p::Connection conn;
@@ -39,6 +42,7 @@ BOOST_AUTO_TEST_CASE(unlimited_recv)
}
CreateSock = CreateSockOrig;
+ LogInstance().SetLogLevel(prev_log_level);
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/interfaces_tests.cpp b/src/test/interfaces_tests.cpp
index 49b7d2003b..cb901b2259 100644
--- a/src/test/interfaces_tests.cpp
+++ b/src/test/interfaces_tests.cpp
@@ -17,6 +17,7 @@ BOOST_FIXTURE_TEST_SUITE(interfaces_tests, TestChain100Setup)
BOOST_AUTO_TEST_CASE(findBlock)
{
+ LOCK(Assert(m_node.chainman)->GetMutex());
auto& chain = m_node.chain;
const CChain& active = Assert(m_node.chainman)->ActiveChain();
@@ -61,6 +62,7 @@ BOOST_AUTO_TEST_CASE(findBlock)
BOOST_AUTO_TEST_CASE(findFirstBlockWithTimeAndHeight)
{
+ LOCK(Assert(m_node.chainman)->GetMutex());
auto& chain = m_node.chain;
const CChain& active = Assert(m_node.chainman)->ActiveChain();
uint256 hash;
@@ -73,6 +75,7 @@ BOOST_AUTO_TEST_CASE(findFirstBlockWithTimeAndHeight)
BOOST_AUTO_TEST_CASE(findAncestorByHeight)
{
+ LOCK(Assert(m_node.chainman)->GetMutex());
auto& chain = m_node.chain;
const CChain& active = Assert(m_node.chainman)->ActiveChain();
uint256 hash;
@@ -83,6 +86,7 @@ BOOST_AUTO_TEST_CASE(findAncestorByHeight)
BOOST_AUTO_TEST_CASE(findAncestorByHash)
{
+ LOCK(Assert(m_node.chainman)->GetMutex());
auto& chain = m_node.chain;
const CChain& active = Assert(m_node.chainman)->ActiveChain();
int height = -1;
@@ -94,7 +98,7 @@ BOOST_AUTO_TEST_CASE(findAncestorByHash)
BOOST_AUTO_TEST_CASE(findCommonAncestor)
{
auto& chain = m_node.chain;
- const CChain& active = Assert(m_node.chainman)->ActiveChain();
+ const CChain& active = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return Assert(m_node.chainman)->ActiveChain());
auto* orig_tip = active.Tip();
for (int i = 0; i < 10; ++i) {
BlockValidationState state;
diff --git a/src/test/key_io_tests.cpp b/src/test/key_io_tests.cpp
index e70b8b3dfd..1eac68de14 100644
--- a/src/test/key_io_tests.cpp
+++ b/src/test/key_io_tests.cpp
@@ -28,7 +28,7 @@ BOOST_AUTO_TEST_CASE(key_io_valid_parse)
SelectParams(CBaseChainParams::MAIN);
for (unsigned int idx = 0; idx < tests.size(); idx++) {
- UniValue test = tests[idx];
+ const UniValue& test = tests[idx];
std::string strTest = test.write();
if (test.size() < 3) { // Allow for extra stuff (useful for comments)
BOOST_ERROR("Bad test: " << strTest);
@@ -86,7 +86,7 @@ BOOST_AUTO_TEST_CASE(key_io_valid_gen)
UniValue tests = read_json(std::string(json_tests::key_io_valid, json_tests::key_io_valid + sizeof(json_tests::key_io_valid)));
for (unsigned int idx = 0; idx < tests.size(); idx++) {
- UniValue test = tests[idx];
+ const UniValue& test = tests[idx];
std::string strTest = test.write();
if (test.size() < 3) // Allow for extra stuff (useful for comments)
{
@@ -126,7 +126,7 @@ BOOST_AUTO_TEST_CASE(key_io_invalid)
CTxDestination destination;
for (unsigned int idx = 0; idx < tests.size(); idx++) {
- UniValue test = tests[idx];
+ const UniValue& test = tests[idx];
std::string strTest = test.write();
if (test.size() < 1) // Allow for extra stuff (useful for comments)
{
diff --git a/src/test/logging_tests.cpp b/src/test/logging_tests.cpp
index 5a5e3b3f1f..a6f3a62c71 100644
--- a/src/test/logging_tests.cpp
+++ b/src/test/logging_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 <init/common.h>
#include <logging.h>
#include <logging/timer.h>
#include <test/util/setup_common.h>
@@ -10,6 +11,7 @@
#include <chrono>
#include <fstream>
#include <iostream>
+#include <unordered_map>
#include <utility>
#include <vector>
@@ -17,6 +19,12 @@
BOOST_FIXTURE_TEST_SUITE(logging_tests, BasicTestingSetup)
+static void ResetLogger()
+{
+ LogInstance().SetLogLevel(BCLog::DEFAULT_LOG_LEVEL);
+ LogInstance().SetCategoryLogLevel({});
+}
+
struct LogSetup : public BasicTestingSetup {
fs::path prev_log_path;
fs::path tmp_log_path;
@@ -25,6 +33,8 @@ struct LogSetup : public BasicTestingSetup {
bool prev_log_timestamps;
bool prev_log_threadnames;
bool prev_log_sourcelocations;
+ std::unordered_map<BCLog::LogFlags, BCLog::Level> prev_category_levels;
+ BCLog::Level prev_log_level;
LogSetup() : prev_log_path{LogInstance().m_file_path},
tmp_log_path{m_args.GetDataDirBase() / "tmp_debug.log"},
@@ -32,14 +42,21 @@ struct LogSetup : public BasicTestingSetup {
prev_print_to_file{LogInstance().m_print_to_file},
prev_log_timestamps{LogInstance().m_log_timestamps},
prev_log_threadnames{LogInstance().m_log_threadnames},
- prev_log_sourcelocations{LogInstance().m_log_sourcelocations}
+ prev_log_sourcelocations{LogInstance().m_log_sourcelocations},
+ prev_category_levels{LogInstance().CategoryLevels()},
+ prev_log_level{LogInstance().LogLevel()}
{
LogInstance().m_file_path = tmp_log_path;
LogInstance().m_reopen_file = true;
LogInstance().m_print_to_file = true;
LogInstance().m_log_timestamps = false;
LogInstance().m_log_threadnames = false;
- LogInstance().m_log_sourcelocations = true;
+
+ // Prevent tests from failing when the line number of the logs changes.
+ LogInstance().m_log_sourcelocations = false;
+
+ LogInstance().SetLogLevel(BCLog::Level::Debug);
+ LogInstance().SetCategoryLogLevel({});
}
~LogSetup()
@@ -51,6 +68,8 @@ struct LogSetup : public BasicTestingSetup {
LogInstance().m_log_timestamps = prev_log_timestamps;
LogInstance().m_log_threadnames = prev_log_threadnames;
LogInstance().m_log_sourcelocations = prev_log_sourcelocations;
+ LogInstance().SetLogLevel(prev_log_level);
+ LogInstance().SetCategoryLogLevel(prev_category_levels);
}
};
@@ -74,6 +93,7 @@ BOOST_AUTO_TEST_CASE(logging_timer)
BOOST_FIXTURE_TEST_CASE(logging_LogPrintf_, LogSetup)
{
+ LogInstance().m_log_sourcelocations = true;
LogPrintf_("fn1", "src1", 1, BCLog::LogFlags::NET, BCLog::Level::Debug, "foo1: %s", "bar1\n");
LogPrintf_("fn2", "src2", 2, BCLog::LogFlags::NET, BCLog::Level::None, "foo2: %s", "bar2\n");
LogPrintf_("fn3", "src3", 3, BCLog::LogFlags::NONE, BCLog::Level::Debug, "foo3: %s", "bar3\n");
@@ -94,9 +114,6 @@ BOOST_FIXTURE_TEST_CASE(logging_LogPrintf_, LogSetup)
BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros, LogSetup)
{
- // Prevent tests from failing when the line number of the following log calls changes.
- LogInstance().m_log_sourcelocations = false;
-
LogPrintf("foo5: %s\n", "bar5");
LogPrint(BCLog::NET, "foo6: %s\n", "bar6");
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "foo7: %s\n", "bar7");
@@ -123,16 +140,14 @@ BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros, LogSetup)
BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros_CategoryName, LogSetup)
{
- // Prevent tests from failing when the line number of the following log calls changes.
- LogInstance().m_log_sourcelocations = false;
LogInstance().EnableCategory(BCLog::LogFlags::ALL);
- const auto concated_categery_names = LogInstance().LogCategoriesString();
+ const auto concatenated_category_names = LogInstance().LogCategoriesString();
std::vector<std::pair<BCLog::LogFlags, std::string>> expected_category_names;
- const auto category_names = SplitString(concated_categery_names, ',');
+ const auto category_names = SplitString(concatenated_category_names, ',');
for (const auto& category_name : category_names) {
- BCLog::LogFlags category = BCLog::NONE;
+ BCLog::LogFlags category;
const auto trimmed_category_name = TrimString(category_name);
- BOOST_TEST(GetLogCategory(category, trimmed_category_name));
+ BOOST_REQUIRE(GetLogCategory(category, trimmed_category_name));
expected_category_names.emplace_back(category, trimmed_category_name);
}
@@ -153,4 +168,92 @@ BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros_CategoryName, LogSetup)
BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end());
}
+BOOST_FIXTURE_TEST_CASE(logging_SeverityLevels, LogSetup)
+{
+ LogInstance().EnableCategory(BCLog::LogFlags::ALL);
+
+ LogInstance().SetLogLevel(BCLog::Level::Debug);
+ LogInstance().SetCategoryLogLevel(/*category_str=*/"net", /*level_str=*/"info");
+
+ // Global log level
+ LogPrintLevel(BCLog::HTTP, BCLog::Level::Info, "foo1: %s\n", "bar1");
+ LogPrintLevel(BCLog::MEMPOOL, BCLog::Level::Trace, "foo2: %s. This log level is lower than the global one.\n", "bar2");
+ LogPrintLevel(BCLog::VALIDATION, BCLog::Level::Warning, "foo3: %s\n", "bar3");
+ LogPrintLevel(BCLog::RPC, BCLog::Level::Error, "foo4: %s\n", "bar4");
+
+ // Category-specific log level
+ LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "foo5: %s\n", "bar5");
+ LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "foo6: %s. This log level is the same as the global one but lower than the category-specific one, which takes precedence. \n", "bar6");
+ LogPrintLevel(BCLog::NET, BCLog::Level::Error, "foo7: %s\n", "bar7");
+
+ std::vector<std::string> expected = {
+ "[http:info] foo1: bar1",
+ "[validation:warning] foo3: bar3",
+ "[rpc:error] foo4: bar4",
+ "[net:warning] foo5: bar5",
+ "[net:error] foo7: bar7",
+ };
+ std::ifstream file{tmp_log_path};
+ std::vector<std::string> log_lines;
+ for (std::string log; std::getline(file, log);) {
+ log_lines.push_back(log);
+ }
+ BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end());
+}
+
+BOOST_FIXTURE_TEST_CASE(logging_Conf, LogSetup)
+{
+ // Set global log level
+ {
+ ResetLogger();
+ ArgsManager args;
+ args.AddArg("-loglevel", "...", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
+ const char* argv_test[] = {"bitcoind", "-loglevel=debug"};
+ std::string err;
+ BOOST_REQUIRE(args.ParseParameters(2, argv_test, err));
+ init::SetLoggingLevel(args);
+ BOOST_CHECK_EQUAL(LogInstance().LogLevel(), BCLog::Level::Debug);
+ }
+
+ // Set category-specific log level
+ {
+ ResetLogger();
+ ArgsManager args;
+ args.AddArg("-loglevel", "...", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
+ const char* argv_test[] = {"bitcoind", "-loglevel=net:trace"};
+ std::string err;
+ BOOST_REQUIRE(args.ParseParameters(2, argv_test, err));
+ init::SetLoggingLevel(args);
+ BOOST_CHECK_EQUAL(LogInstance().LogLevel(), BCLog::DEFAULT_LOG_LEVEL);
+
+ const auto& category_levels{LogInstance().CategoryLevels()};
+ const auto net_it{category_levels.find(BCLog::LogFlags::NET)};
+ BOOST_REQUIRE(net_it != category_levels.end());
+ BOOST_CHECK_EQUAL(net_it->second, BCLog::Level::Trace);
+ }
+
+ // Set both global log level and category-specific log level
+ {
+ ResetLogger();
+ ArgsManager args;
+ args.AddArg("-loglevel", "...", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
+ const char* argv_test[] = {"bitcoind", "-loglevel=debug", "-loglevel=net:trace", "-loglevel=http:info"};
+ std::string err;
+ BOOST_REQUIRE(args.ParseParameters(4, argv_test, err));
+ init::SetLoggingLevel(args);
+ BOOST_CHECK_EQUAL(LogInstance().LogLevel(), BCLog::Level::Debug);
+
+ const auto& category_levels{LogInstance().CategoryLevels()};
+ BOOST_CHECK_EQUAL(category_levels.size(), 2);
+
+ const auto net_it{category_levels.find(BCLog::LogFlags::NET)};
+ BOOST_CHECK(net_it != category_levels.end());
+ BOOST_CHECK_EQUAL(net_it->second, BCLog::Level::Trace);
+
+ const auto http_it{category_levels.find(BCLog::LogFlags::HTTP)};
+ BOOST_CHECK(http_it != category_levels.end());
+ BOOST_CHECK_EQUAL(http_it->second, BCLog::Level::Info);
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp
index 8c745b07b9..393311e4e4 100644
--- a/src/test/mempool_tests.cpp
+++ b/src/test/mempool_tests.cpp
@@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <policy/policy.h>
+#include <test/util/txmempool.h>
#include <txmempool.h>
#include <util/system.h>
#include <util/time.h>
@@ -203,7 +204,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
CTxMemPool::setEntries setAncestorsCalculated;
std::string dummy;
- BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), setAncestorsCalculated, 100, 1000000, 1000, 1000000, dummy), true);
+ BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(2'000'000LL).FromTx(tx7), setAncestorsCalculated, CTxMemPool::Limits::NoLimits(), dummy), true);
BOOST_CHECK(setAncestorsCalculated == setAncestors);
pool.addUnchecked(entry.FromTx(tx7), setAncestors);
@@ -261,7 +262,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
tx10.vout[0].nValue = 10 * COIN;
setAncestorsCalculated.clear();
- BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(4).FromTx(tx10), setAncestorsCalculated, 100, 1000000, 1000, 1000000, dummy), true);
+ BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(200'000LL).Time(4).FromTx(tx10), setAncestorsCalculated, CTxMemPool::Limits::NoLimits(), dummy), true);
BOOST_CHECK(setAncestorsCalculated == setAncestors);
pool.addUnchecked(entry.FromTx(tx10), setAncestors);
diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp
index 20d670c1e1..9bc29e3599 100644
--- a/src/test/miner_tests.cpp
+++ b/src/test/miner_tests.cpp
@@ -2,7 +2,6 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include <chainparams.h>
#include <coins.h>
#include <consensus/consensus.h>
#include <consensus/merkle.h>
@@ -10,6 +9,7 @@
#include <node/miner.h>
#include <policy/policy.h>
#include <script/standard.h>
+#include <test/util/txmempool.h>
#include <timedata.h>
#include <txmempool.h>
#include <uint256.h>
@@ -30,15 +30,24 @@ using node::CBlockTemplate;
namespace miner_tests {
struct MinerTestingSetup : public TestingSetup {
- void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs);
- void TestBasicMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs);
- void TestPrioritisedMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs);
- bool TestSequenceLocks(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs)
+ void TestPackageSelection(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ void TestBasicMining(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ void TestPrioritisedMining(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ bool TestSequenceLocks(const CTransaction& tx, CTxMemPool& tx_mempool) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
- CCoinsViewMemPool view_mempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool);
+ CCoinsViewMemPool view_mempool{&m_node.chainman->ActiveChainstate().CoinsTip(), tx_mempool};
return CheckSequenceLocksAtTip(m_node.chainman->ActiveChain().Tip(), view_mempool, tx);
}
- BlockAssembler AssemblerForTest(const CChainParams& params);
+ CTxMemPool& MakeMempool()
+ {
+ // Delete the previous mempool to ensure with valgrind that the old
+ // pointer is not accessed, when the new one should be accessed
+ // instead.
+ m_node.mempool.reset();
+ m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(m_node));
+ return *m_node.mempool;
+ }
+ BlockAssembler AssemblerForTest(CTxMemPool& tx_mempool);
};
} // namespace miner_tests
@@ -46,13 +55,13 @@ BOOST_FIXTURE_TEST_SUITE(miner_tests, MinerTestingSetup)
static CFeeRate blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE);
-BlockAssembler MinerTestingSetup::AssemblerForTest(const CChainParams& params)
+BlockAssembler MinerTestingSetup::AssemblerForTest(CTxMemPool& tx_mempool)
{
BlockAssembler::Options options;
options.nBlockMaxWeight = MAX_BLOCK_WEIGHT;
options.blockMinFeeRate = blockMinFeeRate;
- return BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options};
+ return BlockAssembler{m_node.chainman->ActiveChainstate(), &tx_mempool, options};
}
constexpr static struct {
@@ -89,8 +98,10 @@ static CBlockIndex CreateBlockIndex(int nHeight, CBlockIndex* active_chain_tip)
// Test suite for ancestor feerate transaction selection.
// Implemented as an additional function, rather than a separate test case,
// to allow reusing the blockchain created in CreateNewBlock_validity.
-void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst)
+void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst)
{
+ CTxMemPool& tx_mempool{MakeMempool()};
+ LOCK(tx_mempool.cs);
// Test the ancestor feerate transaction selection.
TestMemPoolEntryHelper entry;
@@ -105,21 +116,21 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co
tx.vout[0].nValue = 5000000000LL - 1000;
// This tx has a low fee: 1000 satoshis
uint256 hashParentTx = tx.GetHash(); // save this txid for later use
- m_node.mempool->addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
+ tx_mempool.addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
// This tx has a medium fee: 10000 satoshis
tx.vin[0].prevout.hash = txFirst[1]->GetHash();
tx.vout[0].nValue = 5000000000LL - 10000;
uint256 hashMediumFeeTx = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
+ tx_mempool.addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
// This tx has a high fee, but depends on the first transaction
tx.vin[0].prevout.hash = hashParentTx;
tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 50k satoshi fee
uint256 hashHighFeeTx = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(50000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
+ tx_mempool.addUnchecked(entry.Fee(50000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
- std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
+ std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 4U);
BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashParentTx);
BOOST_CHECK(pblocktemplate->block.vtx[2]->GetHash() == hashHighFeeTx);
@@ -129,7 +140,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co
tx.vin[0].prevout.hash = hashHighFeeTx;
tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 0 fee
uint256 hashFreeTx = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(0).FromTx(tx));
+ tx_mempool.addUnchecked(entry.Fee(0).FromTx(tx));
size_t freeTxSize = ::GetSerializeSize(tx, PROTOCOL_VERSION);
// Calculate a fee on child transaction that will put the package just
@@ -139,8 +150,8 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co
tx.vin[0].prevout.hash = hashFreeTx;
tx.vout[0].nValue = 5000000000LL - 1000 - 50000 - feeToUse;
uint256 hashLowFeeTx = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(feeToUse).FromTx(tx));
- pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
+ tx_mempool.addUnchecked(entry.Fee(feeToUse).FromTx(tx));
+ pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
// Verify that the free tx and the low fee tx didn't get selected
for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) {
BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashFreeTx);
@@ -150,11 +161,11 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co
// 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
- m_node.mempool->removeRecursive(CTransaction(tx), MemPoolRemovalReason::REPLACED);
+ 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();
- m_node.mempool->addUnchecked(entry.Fee(feeToUse+2).FromTx(tx));
- pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
+ tx_mempool.addUnchecked(entry.Fee(feeToUse + 2).FromTx(tx));
+ pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U);
BOOST_CHECK(pblocktemplate->block.vtx[4]->GetHash() == hashFreeTx);
BOOST_CHECK(pblocktemplate->block.vtx[5]->GetHash() == hashLowFeeTx);
@@ -167,7 +178,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co
tx.vout[0].nValue = 5000000000LL - 100000000;
tx.vout[1].nValue = 100000000; // 1BTC output
uint256 hashFreeTx2 = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx));
+ tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx));
// This tx can't be mined by itself
tx.vin[0].prevout.hash = hashFreeTx2;
@@ -175,8 +186,8 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co
feeToUse = blockMinFeeRate.GetFee(freeTxSize);
tx.vout[0].nValue = 5000000000LL - 100000000 - feeToUse;
uint256 hashLowFeeTx2 = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx));
- pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
+ tx_mempool.addUnchecked(entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx));
+ pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
// Verify that this tx isn't selected.
for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) {
@@ -188,13 +199,13 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co
// as well.
tx.vin[0].prevout.n = 1;
tx.vout[0].nValue = 100000000 - 10000; // 10k satoshi fee
- m_node.mempool->addUnchecked(entry.Fee(10000).FromTx(tx));
- pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
+ tx_mempool.addUnchecked(entry.Fee(10000).FromTx(tx));
+ pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 9U);
BOOST_CHECK(pblocktemplate->block.vtx[8]->GetHash() == hashLowFeeTx2);
}
-void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight)
+void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight)
{
uint256 hash;
CMutableTransaction tx;
@@ -202,172 +213,205 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C
entry.nFee = 11;
entry.nHeight = 11;
- // Just to make sure we can still make simple blocks
- auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
- BOOST_CHECK(pblocktemplate);
-
- const CAmount BLOCKSUBSIDY = 50*COIN;
+ const CAmount BLOCKSUBSIDY = 50 * COIN;
const CAmount LOWFEE = CENT;
const CAmount HIGHFEE = COIN;
- const CAmount HIGHERFEE = 4*COIN;
+ const CAmount HIGHERFEE = 4 * COIN;
- // block sigops > limit: 1000 CHECKMULTISIG + 1
- tx.vin.resize(1);
- // NOTE: OP_NOP is used to force 20 SigOps for the CHECKMULTISIG
- tx.vin[0].scriptSig = CScript() << OP_0 << OP_0 << OP_0 << OP_NOP << OP_CHECKMULTISIG << OP_1;
- tx.vin[0].prevout.hash = txFirst[0]->GetHash();
- tx.vin[0].prevout.n = 0;
- tx.vout.resize(1);
- tx.vout[0].nValue = BLOCKSUBSIDY;
- for (unsigned int i = 0; i < 1001; ++i)
{
- tx.vout[0].nValue -= LOWFEE;
- hash = tx.GetHash();
- bool spendsCoinbase = i == 0; // only first tx spends coinbase
- // If we don't set the # of sig ops in the CTxMemPoolEntry, template creation fails
- m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx));
- tx.vin[0].prevout.hash = hash;
+ CTxMemPool& tx_mempool{MakeMempool()};
+ LOCK(tx_mempool.cs);
+
+ // Just to make sure we can still make simple blocks
+ auto pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
+ BOOST_CHECK(pblocktemplate);
+
+ // block sigops > limit: 1000 CHECKMULTISIG + 1
+ tx.vin.resize(1);
+ // NOTE: OP_NOP is used to force 20 SigOps for the CHECKMULTISIG
+ tx.vin[0].scriptSig = CScript() << OP_0 << OP_0 << OP_0 << OP_NOP << OP_CHECKMULTISIG << OP_1;
+ tx.vin[0].prevout.hash = txFirst[0]->GetHash();
+ tx.vin[0].prevout.n = 0;
+ tx.vout.resize(1);
+ tx.vout[0].nValue = BLOCKSUBSIDY;
+ for (unsigned int i = 0; i < 1001; ++i) {
+ tx.vout[0].nValue -= LOWFEE;
+ hash = tx.GetHash();
+ bool spendsCoinbase = i == 0; // only first tx spends coinbase
+ // If we don't set the # of sig ops in the CTxMemPoolEntry, template creation fails
+ tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx));
+ tx.vin[0].prevout.hash = hash;
+ }
+
+ BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-blk-sigops"));
}
- BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-blk-sigops"));
- m_node.mempool->clear();
+ {
+ CTxMemPool& tx_mempool{MakeMempool()};
+ LOCK(tx_mempool.cs);
+
+ tx.vin[0].prevout.hash = txFirst[0]->GetHash();
+ tx.vout[0].nValue = BLOCKSUBSIDY;
+ for (unsigned int i = 0; i < 1001; ++i) {
+ tx.vout[0].nValue -= LOWFEE;
+ hash = tx.GetHash();
+ bool spendsCoinbase = i == 0; // only first tx spends coinbase
+ // If we do set the # of sig ops in the CTxMemPoolEntry, template creation passes
+ tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).SigOpsCost(80).FromTx(tx));
+ tx.vin[0].prevout.hash = hash;
+ }
+ BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey));
+ }
- tx.vin[0].prevout.hash = txFirst[0]->GetHash();
- tx.vout[0].nValue = BLOCKSUBSIDY;
- for (unsigned int i = 0; i < 1001; ++i)
{
- tx.vout[0].nValue -= LOWFEE;
- hash = tx.GetHash();
- bool spendsCoinbase = i == 0; // only first tx spends coinbase
- // If we do set the # of sig ops in the CTxMemPoolEntry, template creation passes
- m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).SigOpsCost(80).FromTx(tx));
- tx.vin[0].prevout.hash = hash;
+ CTxMemPool& tx_mempool{MakeMempool()};
+ LOCK(tx_mempool.cs);
+
+ // block size > limit
+ tx.vin[0].scriptSig = CScript();
+ // 18 * (520char + DROP) + OP_1 = 9433 bytes
+ std::vector<unsigned char> vchData(520);
+ for (unsigned int i = 0; i < 18; ++i) {
+ tx.vin[0].scriptSig << vchData << OP_DROP;
+ }
+ tx.vin[0].scriptSig << OP_1;
+ tx.vin[0].prevout.hash = txFirst[0]->GetHash();
+ tx.vout[0].nValue = BLOCKSUBSIDY;
+ for (unsigned int i = 0; i < 128; ++i) {
+ tx.vout[0].nValue -= LOWFEE;
+ hash = tx.GetHash();
+ bool spendsCoinbase = i == 0; // only first tx spends coinbase
+ tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx));
+ tx.vin[0].prevout.hash = hash;
+ }
+ BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey));
}
- BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
- m_node.mempool->clear();
-
- // block size > limit
- tx.vin[0].scriptSig = CScript();
- // 18 * (520char + DROP) + OP_1 = 9433 bytes
- std::vector<unsigned char> vchData(520);
- for (unsigned int i = 0; i < 18; ++i)
- tx.vin[0].scriptSig << vchData << OP_DROP;
- tx.vin[0].scriptSig << OP_1;
- tx.vin[0].prevout.hash = txFirst[0]->GetHash();
- tx.vout[0].nValue = BLOCKSUBSIDY;
- for (unsigned int i = 0; i < 128; ++i)
+
{
- tx.vout[0].nValue -= LOWFEE;
+ CTxMemPool& tx_mempool{MakeMempool()};
+ LOCK(tx_mempool.cs);
+
+ // orphan in tx_mempool, template creation fails
hash = tx.GetHash();
- bool spendsCoinbase = i == 0; // only first tx spends coinbase
- m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx));
- tx.vin[0].prevout.hash = hash;
+ tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).FromTx(tx));
+ BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
}
- BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
- m_node.mempool->clear();
-
- // orphan in *m_node.mempool, template creation fails
- hash = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).FromTx(tx));
- BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
- m_node.mempool->clear();
- // child with higher feerate than parent
- tx.vin[0].scriptSig = CScript() << OP_1;
- tx.vin[0].prevout.hash = txFirst[1]->GetHash();
- tx.vout[0].nValue = BLOCKSUBSIDY-HIGHFEE;
- hash = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
- tx.vin[0].prevout.hash = hash;
- tx.vin.resize(2);
- tx.vin[1].scriptSig = CScript() << OP_1;
- tx.vin[1].prevout.hash = txFirst[0]->GetHash();
- tx.vin[1].prevout.n = 0;
- tx.vout[0].nValue = tx.vout[0].nValue+BLOCKSUBSIDY-HIGHERFEE; //First txn output + fresh coinbase - new txn fee
- hash = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
- BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
- m_node.mempool->clear();
+ {
+ CTxMemPool& tx_mempool{MakeMempool()};
+ LOCK(tx_mempool.cs);
- // coinbase in *m_node.mempool, template creation fails
- tx.vin.resize(1);
- tx.vin[0].prevout.SetNull();
- tx.vin[0].scriptSig = CScript() << OP_0 << OP_1;
- tx.vout[0].nValue = 0;
- hash = tx.GetHash();
- // give it a fee so it'll get mined
- m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
- // Should throw bad-cb-multiple
- BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-cb-multiple"));
- m_node.mempool->clear();
+ // child with higher feerate than parent
+ tx.vin[0].scriptSig = CScript() << OP_1;
+ tx.vin[0].prevout.hash = txFirst[1]->GetHash();
+ tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE;
+ hash = tx.GetHash();
+ tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
+ tx.vin[0].prevout.hash = hash;
+ tx.vin.resize(2);
+ tx.vin[1].scriptSig = CScript() << OP_1;
+ tx.vin[1].prevout.hash = txFirst[0]->GetHash();
+ tx.vin[1].prevout.n = 0;
+ tx.vout[0].nValue = tx.vout[0].nValue + BLOCKSUBSIDY - HIGHERFEE; // First txn output + fresh coinbase - new txn fee
+ hash = tx.GetHash();
+ tx_mempool.addUnchecked(entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
+ BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey));
+ }
- // double spend txn pair in *m_node.mempool, template creation fails
- tx.vin[0].prevout.hash = txFirst[0]->GetHash();
- tx.vin[0].scriptSig = CScript() << OP_1;
- tx.vout[0].nValue = BLOCKSUBSIDY-HIGHFEE;
- tx.vout[0].scriptPubKey = CScript() << OP_1;
- hash = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
- tx.vout[0].scriptPubKey = CScript() << OP_2;
- hash = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
- BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
- m_node.mempool->clear();
-
- // subsidy changing
- int nHeight = m_node.chainman->ActiveChain().Height();
- // Create an actual 209999-long block chain (without valid blocks).
- while (m_node.chainman->ActiveChain().Tip()->nHeight < 209999) {
- CBlockIndex* prev = m_node.chainman->ActiveChain().Tip();
- CBlockIndex* next = new CBlockIndex();
- next->phashBlock = new uint256(InsecureRand256());
- m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash());
- next->pprev = prev;
- next->nHeight = prev->nHeight + 1;
- next->BuildSkip();
- m_node.chainman->ActiveChain().SetTip(next);
+ {
+ CTxMemPool& tx_mempool{MakeMempool()};
+ LOCK(tx_mempool.cs);
+
+ // coinbase in tx_mempool, template creation fails
+ tx.vin.resize(1);
+ tx.vin[0].prevout.SetNull();
+ tx.vin[0].scriptSig = CScript() << OP_0 << OP_1;
+ tx.vout[0].nValue = 0;
+ hash = tx.GetHash();
+ // give it a fee so it'll get mined
+ tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
+ // Should throw bad-cb-multiple
+ BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-cb-multiple"));
}
- BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
- // Extend to a 210000-long block chain.
- while (m_node.chainman->ActiveChain().Tip()->nHeight < 210000) {
- CBlockIndex* prev = m_node.chainman->ActiveChain().Tip();
- CBlockIndex* next = new CBlockIndex();
- next->phashBlock = new uint256(InsecureRand256());
- m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash());
- next->pprev = prev;
- next->nHeight = prev->nHeight + 1;
- next->BuildSkip();
- m_node.chainman->ActiveChain().SetTip(next);
+
+ {
+ CTxMemPool& tx_mempool{MakeMempool()};
+ LOCK(tx_mempool.cs);
+
+ // double spend txn pair in tx_mempool, template creation fails
+ tx.vin[0].prevout.hash = txFirst[0]->GetHash();
+ tx.vin[0].scriptSig = CScript() << OP_1;
+ tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE;
+ tx.vout[0].scriptPubKey = CScript() << OP_1;
+ hash = tx.GetHash();
+ tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
+ tx.vout[0].scriptPubKey = CScript() << OP_2;
+ hash = tx.GetHash();
+ tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
+ BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
}
- BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
- // invalid p2sh txn in *m_node.mempool, template creation fails
- tx.vin[0].prevout.hash = txFirst[0]->GetHash();
- tx.vin[0].prevout.n = 0;
- tx.vin[0].scriptSig = CScript() << OP_1;
- tx.vout[0].nValue = BLOCKSUBSIDY-LOWFEE;
- CScript script = CScript() << OP_0;
- tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script));
- hash = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
- tx.vin[0].prevout.hash = hash;
- tx.vin[0].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end());
- tx.vout[0].nValue -= LOWFEE;
- hash = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
- // Should throw block-validation-failed
- BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("block-validation-failed"));
- m_node.mempool->clear();
-
- // Delete the dummy blocks again.
- while (m_node.chainman->ActiveChain().Tip()->nHeight > nHeight) {
- CBlockIndex* del = m_node.chainman->ActiveChain().Tip();
- m_node.chainman->ActiveChain().SetTip(del->pprev);
- m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(del->pprev->GetBlockHash());
- delete del->phashBlock;
- delete del;
+ {
+ CTxMemPool& tx_mempool{MakeMempool()};
+ LOCK(tx_mempool.cs);
+
+ // subsidy changing
+ int nHeight = m_node.chainman->ActiveChain().Height();
+ // Create an actual 209999-long block chain (without valid blocks).
+ while (m_node.chainman->ActiveChain().Tip()->nHeight < 209999) {
+ CBlockIndex* prev = m_node.chainman->ActiveChain().Tip();
+ CBlockIndex* next = new CBlockIndex();
+ next->phashBlock = new uint256(InsecureRand256());
+ m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash());
+ next->pprev = prev;
+ next->nHeight = prev->nHeight + 1;
+ next->BuildSkip();
+ m_node.chainman->ActiveChain().SetTip(*next);
+ }
+ BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey));
+ // Extend to a 210000-long block chain.
+ while (m_node.chainman->ActiveChain().Tip()->nHeight < 210000) {
+ CBlockIndex* prev = m_node.chainman->ActiveChain().Tip();
+ CBlockIndex* next = new CBlockIndex();
+ next->phashBlock = new uint256(InsecureRand256());
+ m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash());
+ next->pprev = prev;
+ next->nHeight = prev->nHeight + 1;
+ next->BuildSkip();
+ m_node.chainman->ActiveChain().SetTip(*next);
+ }
+ BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey));
+
+ // invalid p2sh txn in tx_mempool, template creation fails
+ tx.vin[0].prevout.hash = txFirst[0]->GetHash();
+ tx.vin[0].prevout.n = 0;
+ tx.vin[0].scriptSig = CScript() << OP_1;
+ tx.vout[0].nValue = BLOCKSUBSIDY - LOWFEE;
+ CScript script = CScript() << OP_0;
+ tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script));
+ hash = tx.GetHash();
+ tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
+ tx.vin[0].prevout.hash = hash;
+ tx.vin[0].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end());
+ tx.vout[0].nValue -= LOWFEE;
+ hash = tx.GetHash();
+ tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
+ // Should throw block-validation-failed
+ BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("block-validation-failed"));
+
+ // Delete the dummy blocks again.
+ while (m_node.chainman->ActiveChain().Tip()->nHeight > nHeight) {
+ CBlockIndex* del = m_node.chainman->ActiveChain().Tip();
+ m_node.chainman->ActiveChain().SetTip(*Assert(del->pprev));
+ m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(del->pprev->GetBlockHash());
+ delete del->phashBlock;
+ delete del;
+ }
}
+ CTxMemPool& tx_mempool{MakeMempool()};
+ LOCK(tx_mempool.cs);
+
// non-final txs in mempool
SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1);
const int flags{LOCKTIME_VERIFY_SEQUENCE};
@@ -388,9 +432,9 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C
tx.vout[0].scriptPubKey = CScript() << OP_1;
tx.nLockTime = 0;
hash = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
- BOOST_CHECK(CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime passes
- BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail
+ tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
+ BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes
+ BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail
{
CBlockIndex* active_chain_tip = m_node.chainman->ActiveChain().Tip();
@@ -402,9 +446,9 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C
tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | (((m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1-m_node.chainman->ActiveChain()[1]->GetMedianTimePast()) >> CTxIn::SEQUENCE_LOCKTIME_GRANULARITY) + 1); // txFirst[1] is the 3rd block
prevheights[0] = baseheight + 2;
hash = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx));
- BOOST_CHECK(CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime passes
- BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail
+ tx_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx));
+ BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes
+ BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail
const int SEQUENCE_LOCK_TIME = 512; // Sequence locks pass 512 seconds later
for (int i = 0; i < CBlockIndex::nMedianTimeSpan; ++i)
@@ -425,9 +469,9 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C
prevheights[0] = baseheight + 3;
tx.nLockTime = m_node.chainman->ActiveChain().Tip()->nHeight + 1;
hash = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx));
- BOOST_CHECK(!CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime fails
- BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass
+ tx_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx));
+ BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails
+ BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass
BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast())); // Locktime passes on 2nd block
// absolute time locked
@@ -436,9 +480,9 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C
prevheights.resize(1);
prevheights[0] = baseheight + 4;
hash = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx));
- BOOST_CHECK(!CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime fails
- BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass
+ tx_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx));
+ BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails
+ BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass
BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1)); // Locktime passes 1 second later
// mempool-dependent transactions (not added)
@@ -446,16 +490,17 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C
prevheights[0] = m_node.chainman->ActiveChain().Tip()->nHeight + 1;
tx.nLockTime = 0;
tx.vin[0].nSequence = 0;
- BOOST_CHECK(CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime passes
- BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass
+ BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes
+ BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass
tx.vin[0].nSequence = 1;
- BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail
+ BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail
tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG;
- BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass
+ BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass
tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | 1;
- BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail
+ BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail
- BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
+ auto pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
+ BOOST_CHECK(pblocktemplate);
// None of the of the absolute height/time locked tx should have made
// it into the template because we still check IsFinalTx in CreateNewBlock,
@@ -470,12 +515,15 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C
m_node.chainman->ActiveChain().Tip()->nHeight++;
SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1);
- BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
+ BOOST_CHECK(pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey));
BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 5U);
}
-void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst)
+void MinerTestingSetup::TestPrioritisedMining(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst)
{
+ CTxMemPool& tx_mempool{MakeMempool()};
+ LOCK(tx_mempool.cs);
+
TestMemPoolEntryHelper entry;
// Test that a tx below min fee but prioritised is included
@@ -487,29 +535,29 @@ void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, c
tx.vout.resize(1);
tx.vout[0].nValue = 5000000000LL; // 0 fee
uint256 hashFreePrioritisedTx = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(0).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
- m_node.mempool->PrioritiseTransaction(hashFreePrioritisedTx, 5 * COIN);
+ tx_mempool.addUnchecked(entry.Fee(0).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
+ tx_mempool.PrioritiseTransaction(hashFreePrioritisedTx, 5 * COIN);
tx.vin[0].prevout.hash = txFirst[1]->GetHash();
tx.vin[0].prevout.n = 0;
tx.vout[0].nValue = 5000000000LL - 1000;
// This tx has a low fee: 1000 satoshis
uint256 hashParentTx = tx.GetHash(); // save this txid for later use
- m_node.mempool->addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
+ tx_mempool.addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
// This tx has a medium fee: 10000 satoshis
tx.vin[0].prevout.hash = txFirst[2]->GetHash();
tx.vout[0].nValue = 5000000000LL - 10000;
uint256 hashMediumFeeTx = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
- m_node.mempool->PrioritiseTransaction(hashMediumFeeTx, -5 * COIN);
+ tx_mempool.addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
+ tx_mempool.PrioritiseTransaction(hashMediumFeeTx, -5 * COIN);
// This tx also has a low fee, but is prioritised
tx.vin[0].prevout.hash = hashParentTx;
tx.vout[0].nValue = 5000000000LL - 1000 - 1000; // 1000 satoshi fee
uint256 hashPrioritsedChild = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
- m_node.mempool->PrioritiseTransaction(hashPrioritsedChild, 2 * COIN);
+ tx_mempool.addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
+ tx_mempool.PrioritiseTransaction(hashPrioritsedChild, 2 * COIN);
// Test that transaction selection properly updates ancestor fee calculations as prioritised
// parents get included in a block. Create a transaction with two prioritised ancestors, each
@@ -520,21 +568,21 @@ void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, c
tx.vin[0].prevout.hash = txFirst[3]->GetHash();
tx.vout[0].nValue = 5000000000LL; // 0 fee
uint256 hashFreeParent = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx));
- m_node.mempool->PrioritiseTransaction(hashFreeParent, 10 * COIN);
+ tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx));
+ tx_mempool.PrioritiseTransaction(hashFreeParent, 10 * COIN);
tx.vin[0].prevout.hash = hashFreeParent;
tx.vout[0].nValue = 5000000000LL; // 0 fee
uint256 hashFreeChild = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
- m_node.mempool->PrioritiseTransaction(hashFreeChild, 1 * COIN);
+ tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
+ tx_mempool.PrioritiseTransaction(hashFreeChild, 1 * COIN);
tx.vin[0].prevout.hash = hashFreeChild;
tx.vout[0].nValue = 5000000000LL; // 0 fee
uint256 hashFreeGrandchild = tx.GetHash();
- m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
+ tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
- auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
+ auto pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U);
BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashFreeParent);
BOOST_CHECK(pblocktemplate->block.vtx[2]->GetHash() == hashFreePrioritisedTx);
@@ -553,15 +601,12 @@ void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, c
BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
{
// Note that by default, these tests run with size accounting enabled.
- const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
- const CChainParams& chainparams = *chainParams;
CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG;
std::unique_ptr<CBlockTemplate> pblocktemplate;
- fCheckpointsEnabled = false;
-
+ CTxMemPool& tx_mempool{*m_node.mempool};
// Simple block creation, nothing special yet:
- BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
+ BOOST_CHECK(pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey));
// We can't make transactions until we have inputs
// Therefore, load 110 blocks :)
@@ -588,28 +633,23 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
pblock->nNonce = bi.nonce;
}
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
- BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, nullptr));
+ BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, true, nullptr));
pblock->hashPrevBlock = pblock->GetHash();
}
LOCK(cs_main);
- LOCK(m_node.mempool->cs);
- TestBasicMining(chainparams, scriptPubKey, txFirst, baseheight);
+ TestBasicMining(scriptPubKey, txFirst, baseheight);
m_node.chainman->ActiveChain().Tip()->nHeight--;
SetMockTime(0);
- m_node.mempool->clear();
- TestPackageSelection(chainparams, scriptPubKey, txFirst);
+ TestPackageSelection(scriptPubKey, txFirst);
m_node.chainman->ActiveChain().Tip()->nHeight--;
SetMockTime(0);
- m_node.mempool->clear();
-
- TestPrioritisedMining(chainparams, scriptPubKey, txFirst);
- fCheckpointsEnabled = true;
+ TestPrioritisedMining(scriptPubKey, txFirst);
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/miniscript_tests.cpp b/src/test/miniscript_tests.cpp
index 95e8476b77..9387c01e73 100644
--- a/src/test/miniscript_tests.cpp
+++ b/src/test/miniscript_tests.cpp
@@ -296,6 +296,9 @@ BOOST_AUTO_TEST_CASE(fixed_tests)
// Same when the duplicates are on different levels in the tree
const auto ms_dup4 = miniscript::FromString("thresh(2,pkh(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),s:pk(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),a:and_b(dv:older(1),s:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)))", CONVERTER);
BOOST_CHECK(ms_dup4 && !ms_dup4->IsSane() && !ms_dup4->CheckDuplicateKey());
+ // Sanity check the opposite is true, too. An otherwise sane Miniscript with no duplicate keys is sane.
+ const auto ms_nondup = miniscript::FromString("pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", CONVERTER);
+ BOOST_CHECK(ms_nondup && ms_nondup->CheckDuplicateKey() && ms_nondup->IsSane());
// Test we find the first insane sub closer to be a leaf node. This fragment is insane for two reasons:
// 1. It can be spent without a signature
// 2. It contains timelock mixes
diff --git a/src/test/minisketch_tests.cpp b/src/test/minisketch_tests.cpp
index 9c53ace633..81f2aad623 100644
--- a/src/test/minisketch_tests.cpp
+++ b/src/test/minisketch_tests.cpp
@@ -40,7 +40,7 @@ BOOST_AUTO_TEST_CASE(minisketch_test)
Minisketch sketch_c = std::move(sketch_ar);
sketch_c.Merge(sketch_br);
auto dec = sketch_c.Decode(errors);
- BOOST_CHECK(dec.has_value());
+ BOOST_REQUIRE(dec.has_value());
auto sols = std::move(*dec);
std::sort(sols.begin(), sols.end());
for (uint32_t i = 0; i < a_not_b; ++i) BOOST_CHECK_EQUAL(sols[i], start_a + i);
diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp
index dccc7ce795..ce23d6013d 100644
--- a/src/test/multisig_tests.cpp
+++ b/src/test/multisig_tests.cpp
@@ -141,23 +141,30 @@ BOOST_AUTO_TEST_CASE(multisig_IsStandard)
for (int i = 0; i < 4; i++)
key[i].MakeNewKey(true);
- TxoutType whichType;
+ const auto is_standard{[](const CScript& spk) {
+ TxoutType type;
+ bool res{::IsStandard(spk, std::nullopt, type)};
+ if (res) {
+ BOOST_CHECK_EQUAL(type, TxoutType::MULTISIG);
+ }
+ return res;
+ }};
CScript a_and_b;
a_and_b << OP_2 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2 << OP_CHECKMULTISIG;
- BOOST_CHECK(::IsStandard(a_and_b, whichType));
+ BOOST_CHECK(is_standard(a_and_b));
CScript a_or_b;
a_or_b << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2 << OP_CHECKMULTISIG;
- BOOST_CHECK(::IsStandard(a_or_b, whichType));
+ BOOST_CHECK(is_standard(a_or_b));
CScript escrow;
escrow << OP_2 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << ToByteVector(key[2].GetPubKey()) << OP_3 << OP_CHECKMULTISIG;
- BOOST_CHECK(::IsStandard(escrow, whichType));
+ BOOST_CHECK(is_standard(escrow));
CScript one_of_four;
one_of_four << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << ToByteVector(key[2].GetPubKey()) << ToByteVector(key[3].GetPubKey()) << OP_4 << OP_CHECKMULTISIG;
- BOOST_CHECK(!::IsStandard(one_of_four, whichType));
+ BOOST_CHECK(!is_standard(one_of_four));
CScript malformed[6];
malformed[0] << OP_3 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2 << OP_CHECKMULTISIG;
@@ -167,8 +174,9 @@ BOOST_AUTO_TEST_CASE(multisig_IsStandard)
malformed[4] << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_CHECKMULTISIG;
malformed[5] << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey());
- for (int i = 0; i < 6; i++)
- BOOST_CHECK(!::IsStandard(malformed[i], whichType));
+ for (int i = 0; i < 6; i++) {
+ BOOST_CHECK(!is_standard(malformed[i]));
+ }
}
BOOST_AUTO_TEST_CASE(multisig_Sign)
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index f6642d3218..f24509dd97 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -805,6 +805,8 @@ BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle)
BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message)
{
+ LOCK(NetEventsInterface::g_msgproc_mutex);
+
// Tests the following scenario:
// * -bind=3.4.5.6:20001 is specified
// * we make an outbound connection to a peer
@@ -840,7 +842,7 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message)
const int64_t time{0};
const CNetMsgMaker msg_maker{PROTOCOL_VERSION};
- // Force CChainState::IsInitialBlockDownload() to return false.
+ // Force Chainstate::IsInitialBlockDownload() to return false.
// Otherwise PushAddress() isn't called by PeerManager::ProcessMessage().
TestChainState& chainstate =
*static_cast<TestChainState*>(&m_node.chainman->ActiveChainstate());
@@ -889,10 +891,7 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message)
}
};
- {
- LOCK(peer.cs_sendProcessing);
- m_node.peerman->SendMessages(&peer);
- }
+ m_node.peerman->SendMessages(&peer);
BOOST_CHECK(sent);
diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp
index c2d2fa37b4..0e1e9ae211 100644
--- a/src/test/netbase_tests.cpp
+++ b/src/test/netbase_tests.cpp
@@ -84,12 +84,12 @@ BOOST_AUTO_TEST_CASE(netbase_properties)
}
-bool static TestSplitHost(const std::string& test, const std::string& host, uint16_t port)
+bool static TestSplitHost(const std::string& test, const std::string& host, uint16_t port, bool validPort=true)
{
std::string hostOut;
uint16_t portOut{0};
- SplitHostPort(test, portOut, hostOut);
- return hostOut == host && port == portOut;
+ bool validPortOut = SplitHostPort(test, portOut, hostOut);
+ return hostOut == host && portOut == port && validPortOut == validPort;
}
BOOST_AUTO_TEST_CASE(netbase_splithost)
@@ -109,6 +109,23 @@ BOOST_AUTO_TEST_CASE(netbase_splithost)
BOOST_CHECK(TestSplitHost(":8333", "", 8333));
BOOST_CHECK(TestSplitHost("[]:8333", "", 8333));
BOOST_CHECK(TestSplitHost("", "", 0));
+ BOOST_CHECK(TestSplitHost(":65535", "", 65535));
+ BOOST_CHECK(TestSplitHost(":65536", ":65536", 0, false));
+ BOOST_CHECK(TestSplitHost(":-1", ":-1", 0, false));
+ BOOST_CHECK(TestSplitHost("[]:70001", "[]:70001", 0, false));
+ BOOST_CHECK(TestSplitHost("[]:-1", "[]:-1", 0, false));
+ BOOST_CHECK(TestSplitHost("[]:-0", "[]:-0", 0, false));
+ BOOST_CHECK(TestSplitHost("[]:0", "", 0, false));
+ BOOST_CHECK(TestSplitHost("[]:1/2", "[]:1/2", 0, false));
+ BOOST_CHECK(TestSplitHost("[]:1E2", "[]:1E2", 0, false));
+ BOOST_CHECK(TestSplitHost("127.0.0.1:65536", "127.0.0.1:65536", 0, false));
+ BOOST_CHECK(TestSplitHost("127.0.0.1:0", "127.0.0.1", 0, false));
+ BOOST_CHECK(TestSplitHost("127.0.0.1:", "127.0.0.1:", 0, false));
+ BOOST_CHECK(TestSplitHost("127.0.0.1:1/2", "127.0.0.1:1/2", 0, false));
+ BOOST_CHECK(TestSplitHost("127.0.0.1:1E2", "127.0.0.1:1E2", 0, false));
+ BOOST_CHECK(TestSplitHost("www.bitcoincore.org:65536", "www.bitcoincore.org:65536", 0, false));
+ BOOST_CHECK(TestSplitHost("www.bitcoincore.org:0", "www.bitcoincore.org", 0, false));
+ BOOST_CHECK(TestSplitHost("www.bitcoincore.org:", "www.bitcoincore.org:", 0, false));
}
bool static TestParse(std::string src, std::string canon)
diff --git a/src/test/policyestimator_tests.cpp b/src/test/policyestimator_tests.cpp
index 3f66a8fc46..b652dc44c0 100644
--- a/src/test/policyestimator_tests.cpp
+++ b/src/test/policyestimator_tests.cpp
@@ -4,6 +4,7 @@
#include <policy/fees.h>
#include <policy/policy.h>
+#include <test/util/txmempool.h>
#include <txmempool.h>
#include <uint256.h>
#include <util/time.h>
diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp
index 2f43ae52f7..3695ea9d16 100644
--- a/src/test/pow_tests.cpp
+++ b/src/test/pow_tests.cpp
@@ -20,7 +20,14 @@ BOOST_AUTO_TEST_CASE(get_next_work)
pindexLast.nHeight = 32255;
pindexLast.nTime = 1262152739; // Block #32255
pindexLast.nBits = 0x1d00ffff;
- BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00d86aU);
+
+ // Here (and below): expected_nbits is calculated in
+ // CalculateNextWorkRequired(); redoing the calculation here would be just
+ // reimplementing the same code that is written in pow.cpp. Rather than
+ // copy that code, we just hardcode the expected result.
+ unsigned int expected_nbits = 0x1d00d86aU;
+ BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), expected_nbits);
+ BOOST_CHECK(PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, expected_nbits));
}
/* Test the constraint on the upper bound for next work */
@@ -32,7 +39,9 @@ BOOST_AUTO_TEST_CASE(get_next_work_pow_limit)
pindexLast.nHeight = 2015;
pindexLast.nTime = 1233061996; // Block #2015
pindexLast.nBits = 0x1d00ffff;
- BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00ffffU);
+ unsigned int expected_nbits = 0x1d00ffffU;
+ BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), expected_nbits);
+ BOOST_CHECK(PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, expected_nbits));
}
/* Test the constraint on the lower bound for actual time taken */
@@ -44,7 +53,12 @@ BOOST_AUTO_TEST_CASE(get_next_work_lower_limit_actual)
pindexLast.nHeight = 68543;
pindexLast.nTime = 1279297671; // Block #68543
pindexLast.nBits = 0x1c05a3f4;
- BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1c0168fdU);
+ unsigned int expected_nbits = 0x1c0168fdU;
+ BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), expected_nbits);
+ BOOST_CHECK(PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, expected_nbits));
+ // Test that reducing nbits further would not be a PermittedDifficultyTransition.
+ unsigned int invalid_nbits = expected_nbits-1;
+ BOOST_CHECK(!PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, invalid_nbits));
}
/* Test the constraint on the upper bound for actual time taken */
@@ -56,7 +70,12 @@ BOOST_AUTO_TEST_CASE(get_next_work_upper_limit_actual)
pindexLast.nHeight = 46367;
pindexLast.nTime = 1269211443; // Block #46367
pindexLast.nBits = 0x1c387f6f;
- BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00e1fdU);
+ unsigned int expected_nbits = 0x1d00e1fdU;
+ BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), expected_nbits);
+ BOOST_CHECK(PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, expected_nbits));
+ // Test that increasing nbits further would not be a PermittedDifficultyTransition.
+ unsigned int invalid_nbits = expected_nbits+1;
+ BOOST_CHECK(!PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, invalid_nbits));
}
BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_negative_target)
diff --git a/src/test/raii_event_tests.cpp b/src/test/raii_event_tests.cpp
index c489ac04e9..d4487cd941 100644
--- a/src/test/raii_event_tests.cpp
+++ b/src/test/raii_event_tests.cpp
@@ -4,8 +4,8 @@
#include <event2/event.h>
+#include <cstdlib>
#include <map>
-#include <stdlib.h>
#include <support/events.h>
diff --git a/src/test/rbf_tests.cpp b/src/test/rbf_tests.cpp
index e597081afd..d362c85560 100644
--- a/src/test/rbf_tests.cpp
+++ b/src/test/rbf_tests.cpp
@@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <policy/rbf.h>
#include <random.h>
+#include <test/util/txmempool.h>
#include <txmempool.h>
#include <util/system.h>
#include <util/time.h>
@@ -76,7 +77,7 @@ BOOST_FIXTURE_TEST_CASE(rbf_helper_functions, TestChain100Setup)
// Create a parent tx5 and child tx6 where both have very low fees
const auto tx5 = make_tx(/*inputs=*/ {m_coinbase_txns[2]}, /*output_values=*/ {1099 * CENT});
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5));
- const auto tx6 = make_tx(/*inputs=*/ {tx3}, /*output_values=*/ {1098 * CENT});
+ const auto tx6 = make_tx(/*inputs=*/ {tx5}, /*output_values=*/ {1098 * CENT});
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx6));
// Make tx6's modified fee much higher than its base fee. This should cause it to pass
// the fee-related checks despite being low-feerate.
diff --git a/src/test/result_tests.cpp b/src/test/result_tests.cpp
new file mode 100644
index 0000000000..6a23a7b895
--- /dev/null
+++ b/src/test/result_tests.cpp
@@ -0,0 +1,96 @@
+// Copyright (c) 2022 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/result.h>
+
+#include <boost/test/unit_test.hpp>
+
+inline bool operator==(const bilingual_str& a, const bilingual_str& b)
+{
+ return a.original == b.original && a.translated == b.translated;
+}
+
+inline std::ostream& operator<<(std::ostream& os, const bilingual_str& s)
+{
+ return os << "bilingual_str('" << s.original << "' , '" << s.translated << "')";
+}
+
+BOOST_AUTO_TEST_SUITE(result_tests)
+
+struct NoCopy {
+ NoCopy(int n) : m_n{std::make_unique<int>(n)} {}
+ std::unique_ptr<int> m_n;
+};
+
+bool operator==(const NoCopy& a, const NoCopy& b)
+{
+ return *a.m_n == *b.m_n;
+}
+
+std::ostream& operator<<(std::ostream& os, const NoCopy& o)
+{
+ return os << "NoCopy(" << *o.m_n << ")";
+}
+
+util::Result<int> IntFn(int i, bool success)
+{
+ if (success) return i;
+ return util::Error{Untranslated(strprintf("int %i error.", i))};
+}
+
+util::Result<bilingual_str> StrFn(bilingual_str s, bool success)
+{
+ if (success) return s;
+ return util::Error{strprintf(Untranslated("str %s error."), s.original)};
+}
+
+util::Result<NoCopy> NoCopyFn(int i, bool success)
+{
+ if (success) return {i};
+ return util::Error{Untranslated(strprintf("nocopy %i error.", i))};
+}
+
+template <typename T>
+void ExpectResult(const util::Result<T>& result, bool success, const bilingual_str& str)
+{
+ BOOST_CHECK_EQUAL(bool(result), success);
+ BOOST_CHECK_EQUAL(util::ErrorString(result), str);
+}
+
+template <typename T, typename... Args>
+void ExpectSuccess(const util::Result<T>& result, const bilingual_str& str, Args&&... args)
+{
+ ExpectResult(result, true, str);
+ BOOST_CHECK_EQUAL(result.has_value(), true);
+ BOOST_CHECK_EQUAL(result.value(), T{std::forward<Args>(args)...});
+ BOOST_CHECK_EQUAL(&result.value(), &*result);
+}
+
+template <typename T, typename... Args>
+void ExpectFail(const util::Result<T>& result, const bilingual_str& str)
+{
+ ExpectResult(result, false, str);
+}
+
+BOOST_AUTO_TEST_CASE(check_returned)
+{
+ ExpectSuccess(IntFn(5, true), {}, 5);
+ ExpectFail(IntFn(5, false), Untranslated("int 5 error."));
+ ExpectSuccess(NoCopyFn(5, true), {}, 5);
+ ExpectFail(NoCopyFn(5, false), Untranslated("nocopy 5 error."));
+ ExpectSuccess(StrFn(Untranslated("S"), true), {}, Untranslated("S"));
+ ExpectFail(StrFn(Untranslated("S"), false), Untranslated("str S error."));
+}
+
+BOOST_AUTO_TEST_CASE(check_value_or)
+{
+ BOOST_CHECK_EQUAL(IntFn(10, true).value_or(20), 10);
+ BOOST_CHECK_EQUAL(IntFn(10, false).value_or(20), 20);
+ BOOST_CHECK_EQUAL(NoCopyFn(10, true).value_or(20), 10);
+ BOOST_CHECK_EQUAL(NoCopyFn(10, false).value_or(20), 20);
+ BOOST_CHECK_EQUAL(StrFn(Untranslated("A"), true).value_or(Untranslated("B")), Untranslated("A"));
+ BOOST_CHECK_EQUAL(StrFn(Untranslated("A"), false).value_or(Untranslated("B")), Untranslated("B"));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/script_p2sh_tests.cpp b/src/test/script_p2sh_tests.cpp
index a221e02d2f..a02d51eecc 100644
--- a/src/test/script_p2sh_tests.cpp
+++ b/src/test/script_p2sh_tests.cpp
@@ -18,15 +18,18 @@
#include <boost/test/unit_test.hpp>
// Helpers:
-static std::vector<unsigned char>
-Serialize(const CScript& s)
+static bool IsStandardTx(const CTransaction& tx, std::string& reason)
+{
+ return IsStandardTx(tx, std::nullopt, DEFAULT_PERMIT_BAREMULTISIG, CFeeRate{DUST_RELAY_TX_FEE}, reason);
+}
+
+static std::vector<unsigned char> Serialize(const CScript& s)
{
std::vector<unsigned char> sSerialized(s.begin(), s.end());
return sSerialized;
}
-static bool
-Verify(const CScript& scriptSig, const CScript& scriptPubKey, bool fStrict, ScriptError& err)
+static bool Verify(const CScript& scriptSig, const CScript& scriptPubKey, bool fStrict, ScriptError& err)
{
// Create dummy to/from transactions:
CMutableTransaction txFrom;
@@ -49,7 +52,6 @@ BOOST_FIXTURE_TEST_SUITE(script_p2sh_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(sign)
{
- LOCK(cs_main);
// Pay-to-script-hash looks like this:
// scriptSig: <sig> <sig...> <serialized_script>
// scriptPubKey: HASH160 <hash> EQUAL
@@ -149,7 +151,6 @@ BOOST_AUTO_TEST_CASE(norecurse)
BOOST_AUTO_TEST_CASE(set)
{
- LOCK(cs_main);
// Test the CScript::Set* methods
FillableSigningProvider keystore;
CKey key[4];
@@ -263,7 +264,6 @@ BOOST_AUTO_TEST_CASE(switchover)
BOOST_AUTO_TEST_CASE(AreInputsStandard)
{
- LOCK(cs_main);
CCoinsView coinsDummy;
CCoinsViewCache coins(&coinsDummy);
FillableSigningProvider keystore;
diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp
index 9e7a376d6b..935194057c 100644
--- a/src/test/script_tests.cpp
+++ b/src/test/script_tests.cpp
@@ -942,7 +942,7 @@ BOOST_AUTO_TEST_CASE(script_json_test)
UniValue tests = read_json(std::string(json_tests::script_tests, json_tests::script_tests + sizeof(json_tests::script_tests)));
for (unsigned int idx = 0; idx < tests.size(); idx++) {
- UniValue test = tests[idx];
+ const UniValue& test = tests[idx];
std::string strTest = test.write();
CScriptWitness witness;
CAmount nValue = 0;
diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp
index 7376f2ff5c..514798d8fa 100644
--- a/src/test/sighash_tests.cpp
+++ b/src/test/sighash_tests.cpp
@@ -165,7 +165,7 @@ BOOST_AUTO_TEST_CASE(sighash_from_data)
UniValue tests = read_json(std::string(json_tests::sighash, json_tests::sighash + sizeof(json_tests::sighash)));
for (unsigned int idx = 0; idx < tests.size(); idx++) {
- UniValue test = tests[idx];
+ const UniValue& test = tests[idx];
std::string strTest = test.write();
if (test.size() < 1) // Allow for extra stuff (useful for comments)
{
diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp
index 55486db6b2..d78f62972f 100644
--- a/src/test/sigopcount_tests.cpp
+++ b/src/test/sigopcount_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 <coins.h>
#include <consensus/consensus.h>
#include <consensus/tx_verify.h>
#include <key.h>
diff --git a/src/test/skiplist_tests.cpp b/src/test/skiplist_tests.cpp
index 6dadf09176..9f5e3ab7ae 100644
--- a/src/test/skiplist_tests.cpp
+++ b/src/test/skiplist_tests.cpp
@@ -72,13 +72,13 @@ BOOST_AUTO_TEST_CASE(getlocator_test)
// Build a CChain for the main branch.
CChain chain;
- chain.SetTip(&vBlocksMain.back());
+ chain.SetTip(vBlocksMain.back());
// Test 100 random starting points for locators.
for (int n=0; n<100; n++) {
int r = InsecureRandRange(150000);
CBlockIndex* tip = (r < 100000) ? &vBlocksMain[r] : &vBlocksSide[r - 100000];
- CBlockLocator locator = chain.GetLocator(tip);
+ CBlockLocator locator = GetLocator(tip);
// The first result must be the block itself, the last one must be genesis.
BOOST_CHECK(locator.vHave.front() == tip->GetBlockHash());
@@ -128,7 +128,7 @@ BOOST_AUTO_TEST_CASE(findearliestatleast_test)
// Build a CChain for the main branch.
CChain chain;
- chain.SetTip(&vBlocksMain.back());
+ chain.SetTip(vBlocksMain.back());
// Verify that FindEarliestAtLeast is correct.
for (unsigned int i=0; i<10000; ++i) {
@@ -155,7 +155,7 @@ BOOST_AUTO_TEST_CASE(findearliestatleast_edge_test)
}
CChain chain;
- chain.SetTip(&blocks.back());
+ chain.SetTip(blocks.back());
BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(50, 0)->nHeight, 0);
BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(100, 0)->nHeight, 0);
diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp
index f160bb08a5..d5b65b9c08 100644
--- a/src/test/system_tests.cpp
+++ b/src/test/system_tests.cpp
@@ -3,7 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
#include <test/util/setup_common.h>
-#include <util/system.h>
+#include <common/run_command.h>
#include <univalue.h>
#ifdef ENABLE_EXTERNAL_SIGNER
@@ -51,15 +51,9 @@ BOOST_AUTO_TEST_CASE(run_command)
}
{
// An invalid command is handled by Boost
-#ifdef WIN32
- const std::string expected{"The system cannot find the file specified."};
-#else
- const std::string expected{"No such file or directory"};
-#endif
BOOST_CHECK_EXCEPTION(RunCommandParseJSON("invalid_command"), boost::process::process_error, [&](const boost::process::process_error& e) {
- const std::string what(e.what());
- BOOST_CHECK(what.find("RunCommandParseJSON error:") == std::string::npos);
- BOOST_CHECK(what.find(expected) != std::string::npos);
+ BOOST_CHECK(std::string(e.what()).find("RunCommandParseJSON error:") == std::string::npos);
+ BOOST_CHECK_EQUAL(e.code().value(), 2);
return true;
});
}
diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp
index 4e6c223ccc..952598f745 100644
--- a/src/test/transaction_tests.cpp
+++ b/src/test/transaction_tests.cpp
@@ -40,6 +40,9 @@ typedef std::vector<unsigned char> valtype;
// In script_tests.cpp
UniValue read_json(const std::string& jsondata);
+static CFeeRate g_dust{DUST_RELAY_TX_FEE};
+static bool g_bare_multi{DEFAULT_PERMIT_BAREMULTISIG};
+
static std::map<std::string, unsigned int> mapFlagNames = {
{std::string("P2SH"), (unsigned int)SCRIPT_VERIFY_P2SH},
{std::string("STRICTENC"), (unsigned int)SCRIPT_VERIFY_STRICTENC},
@@ -191,7 +194,7 @@ BOOST_AUTO_TEST_CASE(tx_valid)
UniValue tests = read_json(std::string(json_tests::tx_valid, json_tests::tx_valid + sizeof(json_tests::tx_valid)));
for (unsigned int idx = 0; idx < tests.size(); idx++) {
- UniValue test = tests[idx];
+ const UniValue& test = tests[idx];
std::string strTest = test.write();
if (test[0].isArray())
{
@@ -211,7 +214,7 @@ BOOST_AUTO_TEST_CASE(tx_valid)
fValid = false;
break;
}
- UniValue vinput = input.get_array();
+ const UniValue& vinput = input.get_array();
if (vinput.size() < 3 || vinput.size() > 4)
{
fValid = false;
@@ -279,7 +282,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid)
UniValue tests = read_json(std::string(json_tests::tx_invalid, json_tests::tx_invalid + sizeof(json_tests::tx_invalid)));
for (unsigned int idx = 0; idx < tests.size(); idx++) {
- UniValue test = tests[idx];
+ const UniValue& test = tests[idx];
std::string strTest = test.write();
if (test[0].isArray())
{
@@ -299,7 +302,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid)
fValid = false;
break;
}
- UniValue vinput = input.get_array();
+ const UniValue& vinput = input.get_array();
if (vinput.size() < 3 || vinput.size() > 4)
{
fValid = false;
@@ -745,7 +748,6 @@ BOOST_AUTO_TEST_CASE(test_witness)
BOOST_AUTO_TEST_CASE(test_IsStandard)
{
- LOCK(cs_main);
FillableSigningProvider keystore;
CCoinsView coinsDummy;
CCoinsViewCache coins(&coinsDummy);
@@ -765,19 +767,19 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
constexpr auto CheckIsStandard = [](const auto& t) {
std::string reason;
- BOOST_CHECK(IsStandardTx(CTransaction(t), reason));
+ BOOST_CHECK(IsStandardTx(CTransaction{t}, MAX_OP_RETURN_RELAY, g_bare_multi, g_dust, reason));
BOOST_CHECK(reason.empty());
};
constexpr auto CheckIsNotStandard = [](const auto& t, const std::string& reason_in) {
std::string reason;
- BOOST_CHECK(!IsStandardTx(CTransaction(t), reason));
+ BOOST_CHECK(!IsStandardTx(CTransaction{t}, MAX_OP_RETURN_RELAY, g_bare_multi, g_dust, reason));
BOOST_CHECK_EQUAL(reason_in, reason);
};
CheckIsStandard(t);
// Check dust with default relay fee:
- CAmount nDustThreshold = 182 * dustRelayFee.GetFeePerK() / 1000;
+ CAmount nDustThreshold = 182 * g_dust.GetFeePerK() / 1000;
BOOST_CHECK_EQUAL(nDustThreshold, 546);
// dust:
t.vout[0].nValue = nDustThreshold - 1;
@@ -805,14 +807,14 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
// Check dust with odd relay fee to verify rounding:
// nDustThreshold = 182 * 3702 / 1000
- dustRelayFee = CFeeRate(3702);
+ g_dust = CFeeRate(3702);
// dust:
t.vout[0].nValue = 674 - 1;
CheckIsNotStandard(t, "dust");
// not dust:
t.vout[0].nValue = 674;
CheckIsStandard(t);
- dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE);
+ g_dust = CFeeRate{DUST_RELAY_TX_FEE};
t.vout[0].scriptPubKey = CScript() << OP_1;
CheckIsNotStandard(t, "scriptpubkey");
@@ -924,16 +926,16 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
BOOST_CHECK_EQUAL(GetTransactionWeight(CTransaction(t)), 400004);
CheckIsNotStandard(t, "tx-size");
- // Check bare multisig (standard if policy flag fIsBareMultisigStd is set)
- fIsBareMultisigStd = true;
+ // Check bare multisig (standard if policy flag g_bare_multi is set)
+ g_bare_multi = true;
t.vout[0].scriptPubKey = GetScriptForMultisig(1, {key.GetPubKey()}); // simple 1-of-1
t.vin.resize(1);
t.vin[0].scriptSig = CScript() << std::vector<unsigned char>(65, 0);
CheckIsStandard(t);
- fIsBareMultisigStd = false;
+ g_bare_multi = false;
CheckIsNotStandard(t, "bare-multisig");
- fIsBareMultisigStd = DEFAULT_PERMIT_BAREMULTISIG;
+ g_bare_multi = DEFAULT_PERMIT_BAREMULTISIG;
// Check P2WPKH outputs dust threshold
t.vout[0].scriptPubKey = CScript() << OP_0 << ParseHex("ffffffffffffffffffffffffffffffffffffffff");
diff --git a/src/test/txindex_tests.cpp b/src/test/txindex_tests.cpp
index 62c7ddb673..643d9221fe 100644
--- a/src/test/txindex_tests.cpp
+++ b/src/test/txindex_tests.cpp
@@ -69,11 +69,16 @@ BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup)
}
}
+ // It is not safe to stop and destroy the index until it finishes handling
+ // the last BlockConnected notification. The BlockUntilSyncedToCurrentChain()
+ // call above is sufficient to ensure this, but the
+ // SyncWithValidationInterfaceQueue() call below is also needed to ensure
+ // TSAN always sees the test thread waiting for the notification thread, and
+ // avoid potential false positive reports.
+ SyncWithValidationInterfaceQueue();
+
// shutdown sequence (c.f. Shutdown() in init.cpp)
txindex.Stop();
-
- // Let scheduler events finish running to avoid accessing any memory related to txindex after it is destructed
- SyncWithValidationInterfaceQueue();
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/txreconciliation_tests.cpp b/src/test/txreconciliation_tests.cpp
new file mode 100644
index 0000000000..bd74998002
--- /dev/null
+++ b/src/test/txreconciliation_tests.cpp
@@ -0,0 +1,86 @@
+// Copyright (c) 2021 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/txreconciliation.h>
+
+#include <test/util/setup_common.h>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(txreconciliation_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(RegisterPeerTest)
+{
+ TxReconciliationTracker tracker(1);
+ const uint64_t salt = 0;
+
+ // Prepare a peer for reconciliation.
+ tracker.PreRegisterPeer(0);
+
+ // Both roles are false, don't register.
+ BOOST_CHECK(tracker.RegisterPeer(/*peer_id=*/0, /*is_peer_inbound=*/true,
+ /*is_peer_recon_initiator=*/false,
+ /*is_peer_recon_responder=*/false,
+ /*peer_recon_version=*/1, salt) ==
+ ReconciliationRegisterResult::PROTOCOL_VIOLATION);
+
+ // Invalid roles for the given connection direction.
+ BOOST_CHECK(tracker.RegisterPeer(0, true, false, true, 1, salt) == ReconciliationRegisterResult::PROTOCOL_VIOLATION);
+ BOOST_CHECK(tracker.RegisterPeer(0, false, true, false, 1, salt) == ReconciliationRegisterResult::PROTOCOL_VIOLATION);
+
+ // Invalid version.
+ BOOST_CHECK(tracker.RegisterPeer(0, true, true, false, 0, salt) == ReconciliationRegisterResult::PROTOCOL_VIOLATION);
+
+ // Valid registration.
+ BOOST_REQUIRE(!tracker.IsPeerRegistered(0));
+ BOOST_REQUIRE(tracker.RegisterPeer(0, true, true, false, 1, salt) == ReconciliationRegisterResult::SUCCESS);
+ BOOST_CHECK(tracker.IsPeerRegistered(0));
+
+ // Reconciliation version is higher than ours, should be able to register.
+ BOOST_REQUIRE(!tracker.IsPeerRegistered(1));
+ tracker.PreRegisterPeer(1);
+ BOOST_REQUIRE(tracker.RegisterPeer(1, true, true, false, 2, salt) == ReconciliationRegisterResult::SUCCESS);
+ BOOST_CHECK(tracker.IsPeerRegistered(1));
+
+ // Do not register if there were no pre-registration for the peer.
+ BOOST_REQUIRE(tracker.RegisterPeer(100, true, true, false, 1, salt) == ReconciliationRegisterResult::NOT_FOUND);
+ BOOST_CHECK(!tracker.IsPeerRegistered(100));
+}
+
+BOOST_AUTO_TEST_CASE(ForgetPeerTest)
+{
+ TxReconciliationTracker tracker(1);
+ NodeId peer_id0 = 0;
+
+ // Removing peer after pre-registring works and does not let to register the peer.
+ tracker.PreRegisterPeer(peer_id0);
+ tracker.ForgetPeer(peer_id0);
+ BOOST_CHECK(tracker.RegisterPeer(peer_id0, true, true, false, 1, 1) == ReconciliationRegisterResult::NOT_FOUND);
+
+ // Removing peer after it is registered works.
+ tracker.PreRegisterPeer(peer_id0);
+ BOOST_REQUIRE(!tracker.IsPeerRegistered(peer_id0));
+ BOOST_REQUIRE(tracker.RegisterPeer(peer_id0, true, true, false, 1, 1) == ReconciliationRegisterResult::SUCCESS);
+ BOOST_CHECK(tracker.IsPeerRegistered(peer_id0));
+ tracker.ForgetPeer(peer_id0);
+ BOOST_CHECK(!tracker.IsPeerRegistered(peer_id0));
+}
+
+BOOST_AUTO_TEST_CASE(IsPeerRegisteredTest)
+{
+ TxReconciliationTracker tracker(1);
+ NodeId peer_id0 = 0;
+
+ BOOST_REQUIRE(!tracker.IsPeerRegistered(peer_id0));
+ tracker.PreRegisterPeer(peer_id0);
+ BOOST_REQUIRE(!tracker.IsPeerRegistered(peer_id0));
+
+ BOOST_REQUIRE(tracker.RegisterPeer(peer_id0, true, true, false, 1, 1) == ReconciliationRegisterResult::SUCCESS);
+ BOOST_CHECK(tracker.IsPeerRegistered(peer_id0));
+
+ tracker.ForgetPeer(peer_id0);
+ BOOST_CHECK(!tracker.IsPeerRegistered(peer_id0));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp
index dd4bc5af75..633f75ff4f 100644
--- a/src/test/txvalidationcache_tests.cpp
+++ b/src/test/txvalidationcache_tests.cpp
@@ -161,11 +161,6 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)
{
// Test that passing CheckInputScripts with one set of script flags doesn't imply
// that we would pass again with a different set of flags.
- {
- LOCK(cs_main);
- InitScriptExecutionCache();
- }
-
CScript p2pk_scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
CScript p2sh_scriptPubKey = GetScriptForDestination(ScriptHash(p2pk_scriptPubKey));
CScript p2pkh_scriptPubKey = GetScriptForDestination(PKHash(coinbaseKey.GetPubKey()));
diff --git a/src/test/util/chainstate.h b/src/test/util/chainstate.h
index 13e0e684b8..0ca63810f3 100644
--- a/src/test/util/chainstate.h
+++ b/src/test/util/chainstate.h
@@ -7,25 +7,38 @@
#include <clientversion.h>
#include <fs.h>
+#include <logging.h>
#include <node/context.h>
#include <node/utxo_snapshot.h>
#include <rpc/blockchain.h>
+#include <test/util/setup_common.h>
#include <validation.h>
#include <univalue.h>
-#include <boost/test/unit_test.hpp>
-
const auto NoMalleation = [](AutoFile& file, node::SnapshotMetadata& meta){};
/**
* Create and activate a UTXO snapshot, optionally providing a function to
* malleate the snapshot.
+ *
+ * If `reset_chainstate` is true, reset the original chainstate back to the genesis
+ * block. This allows us to simulate more realistic conditions in which a snapshot is
+ * loaded into an otherwise mostly-uninitialized datadir. It also allows us to test
+ * conditions that would otherwise cause shutdowns based on the IBD chainstate going
+ * past the snapshot it generated.
*/
template<typename F = decltype(NoMalleation)>
static bool
-CreateAndActivateUTXOSnapshot(node::NodeContext& node, const fs::path root, F malleation = NoMalleation)
+CreateAndActivateUTXOSnapshot(
+ TestingSetup* fixture,
+ F malleation = NoMalleation,
+ bool reset_chainstate = false,
+ bool in_memory_chainstate = false)
{
+ node::NodeContext& node = fixture->m_node;
+ fs::path root = fixture->m_path_root;
+
// Write out a snapshot to the test's tempdir.
//
int height;
@@ -36,8 +49,8 @@ CreateAndActivateUTXOSnapshot(node::NodeContext& node, const fs::path root, F ma
UniValue result = CreateUTXOSnapshot(
node, node.chainman->ActiveChainstate(), auto_outfile, snapshot_path, snapshot_path);
- BOOST_TEST_MESSAGE(
- "Wrote UTXO snapshot to " << fs::PathToString(snapshot_path.make_preferred()) << ": " << result.write());
+ LogPrintf(
+ "Wrote UTXO snapshot to %s: %s", fs::PathToString(snapshot_path.make_preferred()), result.write());
// Read the written snapshot in and then activate it.
//
@@ -48,7 +61,38 @@ CreateAndActivateUTXOSnapshot(node::NodeContext& node, const fs::path root, F ma
malleation(auto_infile, metadata);
- return node.chainman->ActivateSnapshot(auto_infile, metadata, /*in_memory=*/true);
+ if (reset_chainstate) {
+ {
+ // What follows is code to selectively reset chainstate data without
+ // disturbing the existing BlockManager instance, which is needed to
+ // recognize the headers chain previously generated by the chainstate we're
+ // removing. Without those headers, we can't activate the snapshot below.
+ //
+ // This is a stripped-down version of node::LoadChainstate which
+ // preserves the block index.
+ LOCK(::cs_main);
+ uint256 gen_hash = node.chainman->ActiveChainstate().m_chain[0]->GetBlockHash();
+ node.chainman->ResetChainstates();
+ node.chainman->InitializeChainstate(node.mempool.get());
+ Chainstate& chain = node.chainman->ActiveChainstate();
+ Assert(chain.LoadGenesisBlock());
+ // These cache values will be corrected shortly in `MaybeRebalanceCaches`.
+ chain.InitCoinsDB(1 << 20, true, false, "");
+ chain.InitCoinsCache(1 << 20);
+ chain.CoinsTip().SetBestBlock(gen_hash);
+ chain.setBlockIndexCandidates.insert(node.chainman->m_blockman.LookupBlockIndex(gen_hash));
+ chain.LoadChainTip();
+ node.chainman->MaybeRebalanceCaches();
+ }
+ BlockValidationState state;
+ if (!node.chainman->ActiveChainstate().ActivateBestChain(state)) {
+ throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString()));
+ }
+ Assert(
+ 0 == WITH_LOCK(node.chainman->GetMutex(), return node.chainman->ActiveHeight()));
+ }
+
+ return node.chainman->ActivateSnapshot(auto_infile, metadata, in_memory_chainstate);
}
diff --git a/src/test/util/mining.cpp b/src/test/util/mining.cpp
index 88cf9647e7..faa0b2878c 100644
--- a/src/test/util/mining.cpp
+++ b/src/test/util/mining.cpp
@@ -68,7 +68,7 @@ CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)
assert(block->nNonce);
}
- bool processed{Assert(node.chainman)->ProcessNewBlock(block, true, nullptr)};
+ bool processed{Assert(node.chainman)->ProcessNewBlock(block, true, true, nullptr)};
assert(processed);
return CTxIn{block->vtx[0]->GetHash(), 0};
diff --git a/src/test/util/net.cpp b/src/test/util/net.cpp
index 223db16c6c..2e3e16e681 100644
--- a/src/test/util/net.cpp
+++ b/src/test/util/net.cpp
@@ -17,7 +17,6 @@ void ConnmanTestMsg::Handshake(CNode& node,
bool successfully_connected,
ServiceFlags remote_services,
ServiceFlags local_services,
- NetPermissionFlags permission_flags,
int32_t version,
bool relay_txs)
{
@@ -45,10 +44,7 @@ void ConnmanTestMsg::Handshake(CNode& node,
(void)connman.ReceiveMsgFrom(node, msg_version);
node.fPauseSend = false;
connman.ProcessMessagesOnce(node);
- {
- LOCK(node.cs_sendProcessing);
- peerman.SendMessages(&node);
- }
+ peerman.SendMessages(&node);
if (node.fDisconnect) return;
assert(node.nVersion == version);
assert(node.GetCommonVersion() == std::min(version, PROTOCOL_VERSION));
@@ -56,16 +52,12 @@ void ConnmanTestMsg::Handshake(CNode& node,
assert(peerman.GetNodeStateStats(node.GetId(), statestats));
assert(statestats.m_relay_txs == (relay_txs && !node.IsBlockOnlyConn()));
assert(statestats.their_services == remote_services);
- node.m_permissionFlags = permission_flags;
if (successfully_connected) {
CSerializedNetMsg msg_verack{mm.Make(NetMsgType::VERACK)};
(void)connman.ReceiveMsgFrom(node, msg_verack);
node.fPauseSend = false;
connman.ProcessMessagesOnce(node);
- {
- LOCK(node.cs_sendProcessing);
- peerman.SendMessages(&node);
- }
+ peerman.SendMessages(&node);
assert(node.fSuccessfullyConnected == true);
}
}
diff --git a/src/test/util/net.h b/src/test/util/net.h
index ec6b4e6e88..9ae7981980 100644
--- a/src/test/util/net.h
+++ b/src/test/util/net.h
@@ -43,11 +43,11 @@ struct ConnmanTestMsg : public CConnman {
bool successfully_connected,
ServiceFlags remote_services,
ServiceFlags local_services,
- NetPermissionFlags permission_flags,
int32_t version,
- bool relay_txs);
+ bool relay_txs)
+ EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex);
- void ProcessMessagesOnce(CNode& node) { m_msgproc->ProcessMessages(&node, flagInterruptMsgProc); }
+ void ProcessMessagesOnce(CNode& node) EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex) { m_msgproc->ProcessMessages(&node, flagInterruptMsgProc); }
void NodeReceiveMsgBytes(CNode& node, Span<const uint8_t> msg_bytes, bool& complete) const;
@@ -166,6 +166,10 @@ public:
return 0;
}
+ bool SetNonBlocking() const override { return true; }
+
+ bool IsSelectable() const override { return true; }
+
bool Wait(std::chrono::milliseconds timeout,
Event requested,
Event* occurred = nullptr) const override
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index 67984721a3..bdcff1076b 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -4,9 +4,12 @@
#include <test/util/setup_common.h>
+#include <kernel/validation_cache_sizes.h>
+
#include <addrman.h>
#include <banman.h>
#include <chainparams.h>
+#include <common/url.h>
#include <consensus/consensus.h>
#include <consensus/params.h>
#include <consensus/validation.h>
@@ -14,13 +17,14 @@
#include <init.h>
#include <init/common.h>
#include <interfaces/chain.h>
-#include <mempool_args.h>
#include <net.h>
#include <net_processing.h>
#include <node/blockstorage.h>
#include <node/chainstate.h>
#include <node/context.h>
+#include <node/mempool_args.h>
#include <node/miner.h>
+#include <node/validation_cache_args.h>
#include <noui.h>
#include <policy/fees.h>
#include <policy/fees_args.h>
@@ -33,6 +37,7 @@
#include <shutdown.h>
#include <streams.h>
#include <test/util/net.h>
+#include <test/util/txmempool.h>
#include <timedata.h>
#include <txdb.h>
#include <txmempool.h>
@@ -42,7 +47,6 @@
#include <util/threadnames.h>
#include <util/time.h>
#include <util/translation.h>
-#include <util/url.h>
#include <util/vector.h>
#include <validation.h>
#include <validationinterface.h>
@@ -52,10 +56,11 @@
#include <functional>
#include <stdexcept>
+using kernel::ValidationCacheSizes;
+using node::ApplyArgsManOptions;
using node::BlockAssembler;
using node::CalculateCacheSizes;
using node::LoadChainstate;
-using node::NodeContext;
using node::RegenerateCommitments;
using node::VerifyLoadedChainstate;
@@ -103,6 +108,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve
"-logsourcelocations",
"-logtimemicros",
"-logthreadnames",
+ "-loglevel=trace",
"-debug",
"-debugexclude=libevent",
"-debugexclude=leveldb",
@@ -133,10 +139,13 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve
m_node.kernel = std::make_unique<kernel::Context>();
SetupEnvironment();
SetupNetworking();
- InitSignatureCache();
- InitScriptExecutionCache();
+
+ ValidationCacheSizes validation_cache_sizes{};
+ ApplyArgsManOptions(*m_node.args, validation_cache_sizes);
+ Assert(InitSignatureCache(validation_cache_sizes.signature_cache_bytes));
+ Assert(InitScriptExecutionCache(validation_cache_sizes.script_execution_cache_bytes));
+
m_node.chain = interfaces::MakeChain(m_node);
- fCheckBlockIndex = true;
static bool noui_connected = false;
if (!noui_connected) {
noui_connect();
@@ -152,18 +161,6 @@ BasicTestingSetup::~BasicTestingSetup()
gArgs.ClearArgs();
}
-CTxMemPool::Options MemPoolOptionsForTest(const NodeContext& node)
-{
- CTxMemPool::Options mempool_opts{
- .estimator = node.fee_estimator.get(),
- // Default to always checking mempool regardless of
- // chainparams.DefaultConsistencyChecks for tests
- .check_ratio = 1,
- };
- ApplyArgsManOptions(*node.args, mempool_opts);
- return mempool_opts;
-}
-
ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::vector<const char*>& extra_args)
: BasicTestingSetup(chainName, extra_args)
{
@@ -183,14 +180,13 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve
const ChainstateManager::Options chainman_opts{
.chainparams = chainparams,
.adjusted_time_callback = GetAdjustedTime,
+ .check_block_index = true,
};
m_node.chainman = std::make_unique<ChainstateManager>(chainman_opts);
m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<CBlockTreeDB>(m_cache_sizes.block_tree_db, true);
- // Start script-checking threads. Set g_parallel_script_checks to true so they are used.
constexpr int script_check_threads = 2;
StartScriptCheckWorkerThreads(script_check_threads);
- g_parallel_script_checks = true;
}
ChainTestingSetup::~ChainTestingSetup()
@@ -209,17 +205,12 @@ ChainTestingSetup::~ChainTestingSetup()
m_node.chainman.reset();
}
-TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const char*>& extra_args)
- : ChainTestingSetup(chainName, extra_args)
+void TestingSetup::LoadVerifyActivateChainstate()
{
- // 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);
-
node::ChainstateLoadOptions options;
options.mempool = Assert(m_node.mempool.get());
- options.block_tree_db_in_memory = true;
- options.coins_db_in_memory = true;
+ options.block_tree_db_in_memory = m_block_tree_db_in_memory;
+ options.coins_db_in_memory = m_coins_db_in_memory;
options.reindex = node::fReindex;
options.reindex_chainstate = m_args.GetBoolArg("-reindex-chainstate", false);
options.prune = node::fPruneMode;
@@ -235,6 +226,22 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
if (!m_node.chainman->ActiveChainstate().ActivateBestChain(state)) {
throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString()));
}
+}
+
+TestingSetup::TestingSetup(
+ const std::string& chainName,
+ const std::vector<const char*>& extra_args,
+ const bool coins_db_in_memory,
+ const bool block_tree_db_in_memory)
+ : ChainTestingSetup(chainName, extra_args),
+ m_coins_db_in_memory(coins_db_in_memory),
+ m_block_tree_db_in_memory(block_tree_db_in_memory)
+{
+ // 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);
+
+ LoadVerifyActivateChainstate();
m_node.netgroupman = std::make_unique<NetGroupManager>(/*asmap=*/std::vector<bool>());
m_node.addrman = std::make_unique<AddrMan>(*m_node.netgroupman,
@@ -252,8 +259,12 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
}
}
-TestChain100Setup::TestChain100Setup(const std::string& chain_name, const std::vector<const char*>& extra_args)
- : TestingSetup{chain_name, extra_args}
+TestChain100Setup::TestChain100Setup(
+ const std::string& chain_name,
+ const std::vector<const char*>& extra_args,
+ const bool coins_db_in_memory,
+ const bool block_tree_db_in_memory)
+ : TestingSetup{CBaseChainParams::REGTEST, extra_args, coins_db_in_memory, block_tree_db_in_memory}
{
SetMockTime(1598887952);
constexpr std::array<unsigned char, 32> vchKey = {
@@ -285,7 +296,7 @@ void TestChain100Setup::mineBlocks(int num_blocks)
CBlock TestChain100Setup::CreateBlock(
const std::vector<CMutableTransaction>& txns,
const CScript& scriptPubKey,
- CChainState& chainstate)
+ Chainstate& chainstate)
{
CBlock block = BlockAssembler{chainstate, nullptr}.CreateNewBlock(scriptPubKey)->block;
@@ -303,7 +314,7 @@ CBlock TestChain100Setup::CreateBlock(
CBlock TestChain100Setup::CreateAndProcessBlock(
const std::vector<CMutableTransaction>& txns,
const CScript& scriptPubKey,
- CChainState* chainstate)
+ Chainstate* chainstate)
{
if (!chainstate) {
chainstate = &Assert(m_node.chainman)->ActiveChainstate();
@@ -311,7 +322,7 @@ CBlock TestChain100Setup::CreateAndProcessBlock(
const CBlock block = this->CreateBlock(txns, scriptPubKey, *chainstate);
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
- Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, nullptr);
+ Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, true, nullptr);
return block;
}
@@ -412,17 +423,6 @@ std::vector<CTransactionRef> TestChain100Setup::PopulateMempool(FastRandomContex
return mempool_transactions;
}
-CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction& tx) const
-{
- return FromTx(MakeTransactionRef(tx));
-}
-
-CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransactionRef& tx) const
-{
- return CTxMemPoolEntry(tx, nFee, nTime, nHeight,
- spendsCoinbase, sigOpCost, lp);
-}
-
/**
* @returns a real block (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af)
* with 9 txs.
diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h
index ed2c5db7e6..dfa36039a2 100644
--- a/src/test/util/setup_common.h
+++ b/src/test/util/setup_common.h
@@ -8,21 +8,23 @@
#include <chainparamsbase.h>
#include <fs.h>
#include <key.h>
-#include <util/system.h>
#include <node/caches.h>
#include <node/context.h>
+#include <primitives/transaction.h>
#include <pubkey.h>
#include <random.h>
#include <stdexcept>
-#include <txmempool.h>
#include <util/check.h>
#include <util/string.h>
+#include <util/system.h>
#include <util/vector.h>
#include <functional>
#include <type_traits>
#include <vector>
+class Chainstate;
+
/** This is connected to the logger. Can be used to redirect logs to any other log */
extern const std::function<void(const std::string&)> G_TEST_LOG_FUN;
@@ -90,9 +92,6 @@ struct BasicTestingSetup {
ArgsManager m_args;
};
-
-CTxMemPool::Options MemPoolOptionsForTest(const node::NodeContext& node);
-
/** Testing setup that performs all steps up until right before
* ChainstateManager gets initialized. Meant for testing ChainstateManager
* initialization behaviour.
@@ -107,7 +106,16 @@ struct ChainTestingSetup : public BasicTestingSetup {
/** Testing setup that configures a complete environment.
*/
struct TestingSetup : public ChainTestingSetup {
- explicit TestingSetup(const std::string& chainName = CBaseChainParams::MAIN, const std::vector<const char*>& extra_args = {});
+ bool m_coins_db_in_memory{true};
+ bool m_block_tree_db_in_memory{true};
+
+ void LoadVerifyActivateChainstate();
+
+ explicit TestingSetup(
+ const std::string& chainName = CBaseChainParams::MAIN,
+ const std::vector<const char*>& extra_args = {},
+ const bool coins_db_in_memory = true,
+ const bool block_tree_db_in_memory = true);
};
/** Identical to TestingSetup, but chain set to regtest */
@@ -124,8 +132,11 @@ class CScript;
* Testing fixture that pre-creates a 100-block REGTEST-mode block chain
*/
struct TestChain100Setup : public TestingSetup {
- TestChain100Setup(const std::string& chain_name = CBaseChainParams::REGTEST,
- const std::vector<const char*>& extra_args = {});
+ TestChain100Setup(
+ const std::string& chain_name = CBaseChainParams::REGTEST,
+ const std::vector<const char*>& extra_args = {},
+ const bool coins_db_in_memory = true,
+ const bool block_tree_db_in_memory = true);
/**
* Create a new block with just given transactions, coinbase paying to
@@ -134,7 +145,7 @@ struct TestChain100Setup : public TestingSetup {
*/
CBlock CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns,
const CScript& scriptPubKey,
- CChainState* chainstate = nullptr);
+ Chainstate* chainstate = nullptr);
/**
* Create a new block with just given transactions, coinbase paying to
@@ -143,7 +154,7 @@ struct TestChain100Setup : public TestingSetup {
CBlock CreateBlock(
const std::vector<CMutableTransaction>& txns,
const CScript& scriptPubKey,
- CChainState& chainstate);
+ Chainstate& chainstate);
//! Mine a series of new blocks on the active chain.
void mineBlocks(int num_blocks);
@@ -201,33 +212,6 @@ std::unique_ptr<T> MakeNoLogFileContext(const std::string& chain_name = CBaseCha
return std::make_unique<T>(chain_name, arguments);
}
-class CTxMemPoolEntry;
-
-struct TestMemPoolEntryHelper
-{
- // Default values
- CAmount nFee;
- int64_t nTime;
- unsigned int nHeight;
- bool spendsCoinbase;
- unsigned int sigOpCost;
- LockPoints lp;
-
- TestMemPoolEntryHelper() :
- nFee(0), nTime(0), nHeight(1),
- spendsCoinbase(false), sigOpCost(4) { }
-
- CTxMemPoolEntry FromTx(const CMutableTransaction& tx) const;
- CTxMemPoolEntry FromTx(const CTransactionRef& tx) const;
-
- // Change the default value
- TestMemPoolEntryHelper &Fee(CAmount _fee) { nFee = _fee; return *this; }
- TestMemPoolEntryHelper &Time(int64_t _time) { nTime = _time; return *this; }
- TestMemPoolEntryHelper &Height(unsigned int _height) { nHeight = _height; return *this; }
- TestMemPoolEntryHelper &SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return *this; }
- TestMemPoolEntryHelper &SigOpsCost(unsigned int _sigopsCost) { sigOpCost = _sigopsCost; return *this; }
-};
-
CBlock getBlock13b8a();
// define an implicit conversion here so that uint256 may be used directly in BOOST_CHECK_*
diff --git a/src/test/util/txmempool.cpp b/src/test/util/txmempool.cpp
new file mode 100644
index 0000000000..12cc1a4a3d
--- /dev/null
+++ b/src/test/util/txmempool.cpp
@@ -0,0 +1,39 @@
+// Copyright (c) 2022 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 <test/util/txmempool.h>
+
+#include <chainparams.h>
+#include <node/context.h>
+#include <node/mempool_args.h>
+#include <txmempool.h>
+#include <util/check.h>
+#include <util/time.h>
+#include <util/translation.h>
+
+using node::NodeContext;
+
+CTxMemPool::Options MemPoolOptionsForTest(const NodeContext& node)
+{
+ CTxMemPool::Options mempool_opts{
+ .estimator = node.fee_estimator.get(),
+ // Default to always checking mempool regardless of
+ // chainparams.DefaultConsistencyChecks for tests
+ .check_ratio = 1,
+ };
+ const auto err{ApplyArgsManOptions(*node.args, ::Params(), mempool_opts)};
+ Assert(!err);
+ return mempool_opts;
+}
+
+CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction& tx) const
+{
+ return FromTx(MakeTransactionRef(tx));
+}
+
+CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransactionRef& tx) const
+{
+ return CTxMemPoolEntry(tx, nFee, nTime, nHeight,
+ spendsCoinbase, sigOpCost, lp);
+}
diff --git a/src/test/util/txmempool.h b/src/test/util/txmempool.h
new file mode 100644
index 0000000000..70b9ed88db
--- /dev/null
+++ b/src/test/util/txmempool.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2022 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_TEST_UTIL_TXMEMPOOL_H
+#define BITCOIN_TEST_UTIL_TXMEMPOOL_H
+
+#include <txmempool.h>
+
+namespace node {
+struct NodeContext;
+}
+
+CTxMemPool::Options MemPoolOptionsForTest(const node::NodeContext& node);
+
+struct TestMemPoolEntryHelper
+{
+ // Default values
+ CAmount nFee{0};
+ int64_t nTime{0};
+ unsigned int nHeight{1};
+ bool spendsCoinbase{false};
+ unsigned int sigOpCost{4};
+ LockPoints lp;
+
+ CTxMemPoolEntry FromTx(const CMutableTransaction& tx) const;
+ CTxMemPoolEntry FromTx(const CTransactionRef& tx) const;
+
+ // Change the default value
+ TestMemPoolEntryHelper &Fee(CAmount _fee) { nFee = _fee; return *this; }
+ TestMemPoolEntryHelper &Time(int64_t _time) { nTime = _time; return *this; }
+ TestMemPoolEntryHelper &Height(unsigned int _height) { nHeight = _height; return *this; }
+ TestMemPoolEntryHelper &SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return *this; }
+ TestMemPoolEntryHelper &SigOpsCost(unsigned int _sigopsCost) { sigOpCost = _sigopsCost; return *this; }
+};
+
+#endif // BITCOIN_TEST_UTIL_TXMEMPOOL_H
diff --git a/src/test/util/validation.h b/src/test/util/validation.h
index b0bc717b6c..cbe7745b81 100644
--- a/src/test/util/validation.h
+++ b/src/test/util/validation.h
@@ -9,7 +9,7 @@
class CValidationInterface;
-struct TestChainState : public CChainState {
+struct TestChainState : public Chainstate {
/** Reset the ibd cache to its initial state */
void ResetIbd();
/** Toggle IsInitialBlockDownload from true to false */
diff --git a/src/test/util/wallet.cpp b/src/test/util/wallet.cpp
index 7a00ac9e1f..2dadffafb4 100644
--- a/src/test/util/wallet.cpp
+++ b/src/test/util/wallet.cpp
@@ -8,6 +8,7 @@
#include <outputtype.h>
#include <script/standard.h>
#ifdef ENABLE_WALLET
+#include <util/check.h>
#include <util/translation.h>
#include <wallet/wallet.h>
#endif
@@ -20,10 +21,12 @@ const std::string ADDRESS_BCRT1_UNSPENDABLE = "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqq
std::string getnewaddress(CWallet& w)
{
constexpr auto output_type = OutputType::BECH32;
- auto op_dest = w.GetNewDestination(output_type, "");
- assert(op_dest.HasRes());
+ return EncodeDestination(getNewDestination(w, output_type));
+}
- return EncodeDestination(op_dest.GetObj());
+CTxDestination getNewDestination(CWallet& w, OutputType output_type)
+{
+ return *Assert(w.GetNewDestination(output_type, ""));
}
#endif // ENABLE_WALLET
diff --git a/src/test/util/wallet.h b/src/test/util/wallet.h
index 31281bf70e..d8f1db3fd7 100644
--- a/src/test/util/wallet.h
+++ b/src/test/util/wallet.h
@@ -5,6 +5,7 @@
#ifndef BITCOIN_TEST_UTIL_WALLET_H
#define BITCOIN_TEST_UTIL_WALLET_H
+#include <outputtype.h>
#include <string>
namespace wallet {
@@ -19,8 +20,10 @@ extern const std::string ADDRESS_BCRT1_UNSPENDABLE;
/** Import the address to the wallet */
void importaddress(wallet::CWallet& wallet, const std::string& address);
-/** Returns a new address from the wallet */
+/** Returns a new encoded destination from the wallet (hardcoded to BECH32) */
std::string getnewaddress(wallet::CWallet& w);
+/** Returns a new destination, of an specific type, from the wallet */
+CTxDestination getNewDestination(wallet::CWallet& w, OutputType output_type);
#endif // BITCOIN_TEST_UTIL_WALLET_H
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index 5766fff92d..327fc7f567 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -23,8 +23,10 @@
#include <util/string.h>
#include <util/time.h>
#include <util/vector.h>
+#include <util/bitdeque.h>
#include <array>
+#include <cmath>
#include <fstream>
#include <limits>
#include <map>
@@ -243,15 +245,31 @@ BOOST_AUTO_TEST_CASE(span_write_bytes)
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");
+ BOOST_CHECK_EQUAL(Join(std::vector<std::string>{}, ", "), "");
+ BOOST_CHECK_EQUAL(Join(std::vector<std::string>{"foo"}, ", "), "foo");
+ BOOST_CHECK_EQUAL(Join(std::vector<std::string>{"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_CHECK_EQUAL(Join(std::list<std::string>{}, ", ", op_upper), "");
+ BOOST_CHECK_EQUAL(Join(std::list<std::string>{"foo"}, ", ", op_upper), "FOO");
+ BOOST_CHECK_EQUAL(Join(std::list<std::string>{"foo", "bar"}, ", ", op_upper), "FOO, BAR");
+}
+
+BOOST_AUTO_TEST_CASE(util_ReplaceAll)
+{
+ const std::string original("A test \"%s\" string '%s'.");
+ auto test_replaceall = [&original](const std::string& search, const std::string& substitute, const std::string& expected) {
+ auto test = original;
+ ReplaceAll(test, search, substitute);
+ BOOST_CHECK_EQUAL(test, expected);
+ };
+
+ test_replaceall("", "foo", original);
+ test_replaceall(original, "foo", "foo");
+ test_replaceall("%s", "foo", "A test \"foo\" string 'foo'.");
+ test_replaceall("\"", "foo", "A test foo%sfoo string '%s'.");
+ test_replaceall("'", "foo", "A test \"%s\" string foo%sfoo.");
}
BOOST_AUTO_TEST_CASE(util_TrimString)
@@ -270,14 +288,10 @@ BOOST_AUTO_TEST_CASE(util_TrimString)
BOOST_CHECK_EQUAL(TrimStringView(std::string("\x05\x04\x03\x02\x01\x00", 6), std::string("\x05\x04\x03\x02\x01\x00", 6)), "");
}
-BOOST_AUTO_TEST_CASE(util_FormatParseISO8601DateTime)
+BOOST_AUTO_TEST_CASE(util_FormatISO8601DateTime)
{
BOOST_CHECK_EQUAL(FormatISO8601DateTime(1317425777), "2011-09-30T23:36:17Z");
BOOST_CHECK_EQUAL(FormatISO8601DateTime(0), "1970-01-01T00:00:00Z");
-
- BOOST_CHECK_EQUAL(ParseISO8601DateTime("1970-01-01T00:00:00Z"), 0);
- BOOST_CHECK_EQUAL(ParseISO8601DateTime("1960-01-01T00:00:00Z"), 0);
- BOOST_CHECK_EQUAL(ParseISO8601DateTime("2011-09-30T23:36:17Z"), 1317425777);
}
BOOST_AUTO_TEST_CASE(util_FormatISO8601Date)
@@ -2499,13 +2513,13 @@ BOOST_AUTO_TEST_CASE(test_tracked_vector)
auto v2 = Vector(std::move(t2));
BOOST_CHECK_EQUAL(v2.size(), 1U);
- BOOST_CHECK(v2[0].origin == &t2);
+ BOOST_CHECK(v2[0].origin == &t2); // NOLINT(*-use-after-move)
BOOST_CHECK_EQUAL(v2[0].copies, 0);
auto v3 = Vector(t1, std::move(t2));
BOOST_CHECK_EQUAL(v3.size(), 2U);
BOOST_CHECK(v3[0].origin == &t1);
- BOOST_CHECK(v3[1].origin == &t2);
+ BOOST_CHECK(v3[1].origin == &t2); // NOLINT(*-use-after-move)
BOOST_CHECK_EQUAL(v3[0].copies, 1);
BOOST_CHECK_EQUAL(v3[1].copies, 0);
@@ -2513,7 +2527,7 @@ BOOST_AUTO_TEST_CASE(test_tracked_vector)
BOOST_CHECK_EQUAL(v4.size(), 3U);
BOOST_CHECK(v4[0].origin == &t1);
BOOST_CHECK(v4[1].origin == &t2);
- BOOST_CHECK(v4[2].origin == &t3);
+ BOOST_CHECK(v4[2].origin == &t3); // NOLINT(*-use-after-move)
BOOST_CHECK_EQUAL(v4[0].copies, 1);
BOOST_CHECK_EQUAL(v4[1].copies, 1);
BOOST_CHECK_EQUAL(v4[2].copies, 0);
diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp
index 7ade4d8195..bb1ade153a 100644
--- a/src/test/validation_block_tests.cpp
+++ b/src/test/validation_block_tests.cpp
@@ -100,7 +100,7 @@ std::shared_ptr<CBlock> MinerTestingSetup::FinalizeBlock(std::shared_ptr<CBlock>
// submit block header, so that miner can get the block height from the
// global state and the node has the topology of the chain
BlockValidationState ignored;
- BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders({pblock->GetBlockHeader()}, ignored));
+ BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders({pblock->GetBlockHeader()}, true, ignored));
return pblock;
}
@@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
bool ignored;
// Connect the genesis block and drain any outstanding events
- BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored));
+ BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared<CBlock>(Params().GenesisBlock()), true, true, &ignored));
SyncWithValidationInterfaceQueue();
// subscribe to events (this subscriber will validate event ordering)
@@ -179,13 +179,13 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
FastRandomContext insecure;
for (int i = 0; i < 1000; i++) {
auto block = blocks[insecure.randrange(blocks.size() - 1)];
- Assert(m_node.chainman)->ProcessNewBlock(block, true, &ignored);
+ Assert(m_node.chainman)->ProcessNewBlock(block, true, true, &ignored);
}
// to make sure that eventually we process the full chain - do it here
- for (auto block : blocks) {
+ for (const auto& block : blocks) {
if (block->vtx.size() == 1) {
- bool processed = Assert(m_node.chainman)->ProcessNewBlock(block, true, &ignored);
+ bool processed = Assert(m_node.chainman)->ProcessNewBlock(block, true, true, &ignored);
assert(processed);
}
}
@@ -224,7 +224,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
{
bool ignored;
auto ProcessBlock = [&](std::shared_ptr<const CBlock> block) -> bool {
- return Assert(m_node.chainman)->ProcessNewBlock(block, /*force_processing=*/true, /*new_block=*/&ignored);
+ return Assert(m_node.chainman)->ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&ignored);
};
// Process all mined blocks
@@ -234,7 +234,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
// Run the test multiple times
for (int test_runs = 3; test_runs > 0; --test_runs) {
- BOOST_CHECK_EQUAL(last_mined->GetHash(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
+ BOOST_CHECK_EQUAL(last_mined->GetHash(), WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip()->GetBlockHash()));
// Later on split from here
const uint256 split_hash{last_mined->hashPrevBlock};
@@ -316,7 +316,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
ProcessBlock(b);
}
// Check that the reorg was eventually successful
- BOOST_CHECK_EQUAL(last_mined->GetHash(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
+ BOOST_CHECK_EQUAL(last_mined->GetHash(), WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip()->GetBlockHash()));
// We can join the other thread, which returns when the reorg was successful
rpc_thread.join();
@@ -325,6 +325,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
BOOST_AUTO_TEST_CASE(witness_commitment_index)
{
+ LOCK(Assert(m_node.chainman)->GetMutex());
CScript pubKey;
pubKey << 1 << OP_TRUE;
auto ptemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get()}.CreateNewBlock(pubKey);
diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp
index 78adb521ac..f868c0d4e6 100644
--- a/src/test/validation_chainstate_tests.cpp
+++ b/src/test/validation_chainstate_tests.cpp
@@ -9,7 +9,6 @@
#include <sync.h>
#include <test/util/chainstate.h>
#include <test/util/setup_common.h>
-#include <timedata.h>
#include <uint256.h>
#include <validation.h>
@@ -17,19 +16,13 @@
#include <boost/test/unit_test.hpp>
-BOOST_FIXTURE_TEST_SUITE(validation_chainstate_tests, TestingSetup)
+BOOST_FIXTURE_TEST_SUITE(validation_chainstate_tests, ChainTestingSetup)
-//! Test resizing coins-related CChainState caches during runtime.
+//! Test resizing coins-related Chainstate caches during runtime.
//!
BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches)
{
- const ChainstateManager::Options chainman_opts{
- .chainparams = Params(),
- .adjusted_time_callback = GetAdjustedTime,
- };
- ChainstateManager manager{chainman_opts};
-
- WITH_LOCK(::cs_main, manager.m_blockman.m_block_tree_db = std::make_unique<CBlockTreeDB>(1 << 20, true));
+ ChainstateManager& manager = *Assert(m_node.chainman);
CTxMemPool& mempool = *Assert(m_node.mempool);
//! Create and add a Coin with DynamicMemoryUsage of 80 bytes to the given view.
@@ -45,7 +38,7 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches)
return outp;
};
- CChainState& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool));
+ Chainstate& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool));
c1.InitCoinsDB(
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23));
@@ -96,7 +89,8 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
// After adding some blocks to the tip, best block should have changed.
BOOST_CHECK(::g_best_block != curr_tip);
- BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root));
+ BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(
+ this, NoMalleation, /*reset_chainstate=*/ true));
// Ensure our active chain is the snapshot chainstate.
BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.IsSnapshotActive()));
@@ -113,8 +107,8 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
BOOST_CHECK_EQUAL(chainman.GetAll().size(), 2);
- CChainState& background_cs{*[&] {
- for (CChainState* cs : chainman.GetAll()) {
+ Chainstate& background_cs{*[&] {
+ for (Chainstate* cs : chainman.GetAll()) {
if (cs != &chainman.ActiveChainstate()) {
return cs;
}
@@ -139,7 +133,7 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
bool checked = CheckBlock(*pblock, state, chainparams.GetConsensus());
BOOST_CHECK(checked);
bool accepted = background_cs.AcceptBlock(
- pblock, state, &pindex, true, nullptr, &newblock);
+ pblock, state, &pindex, true, nullptr, &newblock, true);
BOOST_CHECK(accepted);
}
// UpdateTip is called here
diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp
index 14de96ff41..22b9af1201 100644
--- a/src/test/validation_chainstatemanager_tests.cpp
+++ b/src/test/validation_chainstatemanager_tests.cpp
@@ -10,6 +10,7 @@
#include <sync.h>
#include <test/util/chainstate.h>
#include <test/util/setup_common.h>
+#include <timedata.h>
#include <uint256.h>
#include <validation.h>
#include <validationinterface.h>
@@ -32,13 +33,13 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
ChainstateManager& manager = *m_node.chainman;
CTxMemPool& mempool = *m_node.mempool;
- std::vector<CChainState*> chainstates;
+ std::vector<Chainstate*> chainstates;
BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
// Create a legacy (IBD) chainstate.
//
- CChainState& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(&mempool));
+ Chainstate& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(&mempool));
chainstates.push_back(&c1);
c1.InitCoinsDB(
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
@@ -49,12 +50,12 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
auto all = manager.GetAll();
BOOST_CHECK_EQUAL_COLLECTIONS(all.begin(), all.end(), chainstates.begin(), chainstates.end());
- auto& active_chain = manager.ActiveChain();
+ auto& active_chain = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
BOOST_CHECK_EQUAL(&active_chain, &c1.m_chain);
- BOOST_CHECK_EQUAL(manager.ActiveHeight(), -1);
+ BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), -1);
- auto active_tip = manager.ActiveTip();
+ auto active_tip = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip());
auto exp_tip = c1.m_chain.Tip();
BOOST_CHECK_EQUAL(active_tip, exp_tip);
@@ -63,7 +64,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
// Create a snapshot-based chainstate.
//
const uint256 snapshot_blockhash = GetRandHash();
- CChainState& c2 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(
+ Chainstate& c2 = WITH_LOCK(::cs_main, return manager.ActivateExistingSnapshot(
&mempool, snapshot_blockhash));
chainstates.push_back(&c2);
@@ -84,12 +85,12 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
auto all2 = manager.GetAll();
BOOST_CHECK_EQUAL_COLLECTIONS(all2.begin(), all2.end(), chainstates.begin(), chainstates.end());
- auto& active_chain2 = manager.ActiveChain();
+ auto& active_chain2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
BOOST_CHECK_EQUAL(&active_chain2, &c2.m_chain);
- BOOST_CHECK_EQUAL(manager.ActiveHeight(), 0);
+ BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 0);
- auto active_tip2 = manager.ActiveTip();
+ auto active_tip2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip());
auto exp_tip2 = c2.m_chain.Tip();
BOOST_CHECK_EQUAL(active_tip2, exp_tip2);
@@ -111,11 +112,11 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
manager.m_total_coinsdb_cache = max_cache;
manager.m_total_coinstip_cache = max_cache;
- std::vector<CChainState*> chainstates;
+ std::vector<Chainstate*> chainstates;
// Create a legacy (IBD) chainstate.
//
- CChainState& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool));
+ Chainstate& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool));
chainstates.push_back(&c1);
c1.InitCoinsDB(
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
@@ -133,7 +134,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
// Create a snapshot-based chainstate.
//
- CChainState& c2 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool, GetRandHash()));
+ Chainstate& c2 = WITH_LOCK(cs_main, return manager.ActivateExistingSnapshot(&mempool, GetRandHash()));
chainstates.push_back(&c2);
c2.InitCoinsDB(
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
@@ -154,167 +155,245 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
BOOST_CHECK_CLOSE(c2.m_coinsdb_cache_size_bytes, max_cache * 0.95, 1);
}
-//! Test basic snapshot activation.
-BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup)
-{
- ChainstateManager& chainman = *Assert(m_node.chainman);
-
- size_t initial_size;
- size_t initial_total_coins{100};
-
- // Make some initial assertions about the contents of the chainstate.
+struct SnapshotTestSetup : TestChain100Setup {
+ // Run with coinsdb on the filesystem to support, e.g., moving invalidated
+ // chainstate dirs to "*_invalid".
+ //
+ // Note that this means the tests run considerably slower than in-memory DB
+ // tests, but we can't otherwise test this functionality since it relies on
+ // destructive filesystem operations.
+ SnapshotTestSetup() : TestChain100Setup{
+ {},
+ {},
+ /*coins_db_in_memory=*/false,
+ /*block_tree_db_in_memory=*/false,
+ }
{
- LOCK(::cs_main);
- CCoinsViewCache& ibd_coinscache = chainman.ActiveChainstate().CoinsTip();
- initial_size = ibd_coinscache.GetCacheSize();
- size_t total_coins{0};
-
- for (CTransactionRef& txn : m_coinbase_txns) {
- COutPoint op{txn->GetHash(), 0};
- BOOST_CHECK(ibd_coinscache.HaveCoin(op));
- total_coins++;
- }
-
- BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
- BOOST_CHECK_EQUAL(initial_size, initial_total_coins);
}
- // Snapshot should refuse to load at this height.
- BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(m_node, m_path_root));
- BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
- BOOST_CHECK(!chainman.SnapshotBlockhash());
-
- // Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can
- // be found.
- constexpr int snapshot_height = 110;
- mineBlocks(10);
- initial_size += 10;
- initial_total_coins += 10;
-
- // Should not load malleated snapshots
- BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
- m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
- // A UTXO is missing but count is correct
- metadata.m_coins_count -= 1;
-
- COutPoint outpoint;
- Coin coin;
-
- auto_infile >> outpoint;
- auto_infile >> coin;
- }));
- BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
- m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
- // Coins count is larger than coins in file
- metadata.m_coins_count += 1;
- }));
- BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
- m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
- // Coins count is smaller than coins in file
- metadata.m_coins_count -= 1;
- }));
- BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
- m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
- // Wrong hash
- metadata.m_base_blockhash = uint256::ZERO;
- }));
- BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
- m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
- // Wrong hash
- metadata.m_base_blockhash = uint256::ONE;
- }));
-
- BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root));
-
- // Ensure our active chain is the snapshot chainstate.
- BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
- BOOST_CHECK_EQUAL(
- *chainman.ActiveChainstate().m_from_snapshot_blockhash,
- *chainman.SnapshotBlockhash());
-
- // Ensure that the genesis block was not marked assumed-valid.
- BOOST_CHECK(WITH_LOCK(::cs_main, return !chainman.ActiveChain().Genesis()->IsAssumedValid()));
-
- const AssumeutxoData& au_data = *ExpectedAssumeutxo(snapshot_height, ::Params());
- const CBlockIndex* tip = chainman.ActiveTip();
-
- BOOST_CHECK_EQUAL(tip->nChainTx, au_data.nChainTx);
-
- // To be checked against later when we try loading a subsequent snapshot.
- uint256 loaded_snapshot_blockhash{*chainman.SnapshotBlockhash()};
-
- // Make some assertions about the both chainstates. These checks ensure the
- // legacy chainstate hasn't changed and that the newly created chainstate
- // reflects the expected content.
+ std::tuple<Chainstate*, Chainstate*> SetupSnapshot()
{
- LOCK(::cs_main);
- int chains_tested{0};
+ ChainstateManager& chainman = *Assert(m_node.chainman);
- for (CChainState* chainstate : chainman.GetAll()) {
- BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
- CCoinsViewCache& coinscache = chainstate->CoinsTip();
+ BOOST_CHECK(!chainman.IsSnapshotActive());
- // Both caches will be empty initially.
- BOOST_CHECK_EQUAL((unsigned int)0, coinscache.GetCacheSize());
+ {
+ LOCK(::cs_main);
+ BOOST_CHECK(!chainman.IsSnapshotValidated());
+ BOOST_CHECK(!node::FindSnapshotChainstateDir());
+ }
+ size_t initial_size;
+ size_t initial_total_coins{100};
+
+ // Make some initial assertions about the contents of the chainstate.
+ {
+ LOCK(::cs_main);
+ CCoinsViewCache& ibd_coinscache = chainman.ActiveChainstate().CoinsTip();
+ initial_size = ibd_coinscache.GetCacheSize();
size_t total_coins{0};
for (CTransactionRef& txn : m_coinbase_txns) {
COutPoint op{txn->GetHash(), 0};
- BOOST_CHECK(coinscache.HaveCoin(op));
+ BOOST_CHECK(ibd_coinscache.HaveCoin(op));
total_coins++;
}
- BOOST_CHECK_EQUAL(initial_size , coinscache.GetCacheSize());
BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
- chains_tested++;
+ BOOST_CHECK_EQUAL(initial_size, initial_total_coins);
}
- BOOST_CHECK_EQUAL(chains_tested, 2);
- }
+ Chainstate& validation_chainstate = chainman.ActiveChainstate();
+
+ // Snapshot should refuse to load at this height.
+ BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));
+ BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
+ BOOST_CHECK(!chainman.SnapshotBlockhash());
+
+ // Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can
+ // be found.
+ constexpr int snapshot_height = 110;
+ mineBlocks(10);
+ initial_size += 10;
+ initial_total_coins += 10;
+
+ // Should not load malleated snapshots
+ BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
+ this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
+ // A UTXO is missing but count is correct
+ metadata.m_coins_count -= 1;
+
+ COutPoint outpoint;
+ Coin coin;
+
+ auto_infile >> outpoint;
+ auto_infile >> coin;
+ }));
+
+ BOOST_CHECK(!node::FindSnapshotChainstateDir());
+
+ BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
+ this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
+ // Coins count is larger than coins in file
+ metadata.m_coins_count += 1;
+ }));
+ BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
+ this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
+ // Coins count is smaller than coins in file
+ metadata.m_coins_count -= 1;
+ }));
+ BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
+ this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
+ // Wrong hash
+ metadata.m_base_blockhash = uint256::ZERO;
+ }));
+ BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
+ this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
+ // Wrong hash
+ metadata.m_base_blockhash = uint256::ONE;
+ }));
+
+ BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(this));
+ BOOST_CHECK(fs::exists(*node::FindSnapshotChainstateDir()));
+
+ // Ensure our active chain is the snapshot chainstate.
+ BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
+ BOOST_CHECK_EQUAL(
+ *chainman.ActiveChainstate().m_from_snapshot_blockhash,
+ *chainman.SnapshotBlockhash());
+
+ Chainstate& snapshot_chainstate = chainman.ActiveChainstate();
+
+ {
+ LOCK(::cs_main);
- // Mine some new blocks on top of the activated snapshot chainstate.
- constexpr size_t new_coins{100};
- mineBlocks(new_coins); // Defined in TestChain100Setup.
+ fs::path found = *node::FindSnapshotChainstateDir();
- {
- LOCK(::cs_main);
- size_t coins_in_active{0};
- size_t coins_in_background{0};
- size_t coins_missing_from_background{0};
+ // Note: WriteSnapshotBaseBlockhash() is implicitly tested above.
+ BOOST_CHECK_EQUAL(
+ *node::ReadSnapshotBaseBlockhash(found),
+ *chainman.SnapshotBlockhash());
- for (CChainState* chainstate : chainman.GetAll()) {
- BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
- CCoinsViewCache& coinscache = chainstate->CoinsTip();
- bool is_background = chainstate != &chainman.ActiveChainstate();
+ // Ensure that the genesis block was not marked assumed-valid.
+ BOOST_CHECK(!chainman.ActiveChain().Genesis()->IsAssumedValid());
+ }
- for (CTransactionRef& txn : m_coinbase_txns) {
- COutPoint op{txn->GetHash(), 0};
- if (coinscache.HaveCoin(op)) {
- (is_background ? coins_in_background : coins_in_active)++;
- } else if (is_background) {
- coins_missing_from_background++;
+ const AssumeutxoData& au_data = *ExpectedAssumeutxo(snapshot_height, ::Params());
+ const CBlockIndex* tip = WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip());
+
+ BOOST_CHECK_EQUAL(tip->nChainTx, au_data.nChainTx);
+
+ // To be checked against later when we try loading a subsequent snapshot.
+ uint256 loaded_snapshot_blockhash{*chainman.SnapshotBlockhash()};
+
+ // Make some assertions about the both chainstates. These checks ensure the
+ // legacy chainstate hasn't changed and that the newly created chainstate
+ // reflects the expected content.
+ {
+ LOCK(::cs_main);
+ int chains_tested{0};
+
+ for (Chainstate* chainstate : chainman.GetAll()) {
+ BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
+ CCoinsViewCache& coinscache = chainstate->CoinsTip();
+
+ // Both caches will be empty initially.
+ BOOST_CHECK_EQUAL((unsigned int)0, coinscache.GetCacheSize());
+
+ size_t total_coins{0};
+
+ for (CTransactionRef& txn : m_coinbase_txns) {
+ COutPoint op{txn->GetHash(), 0};
+ BOOST_CHECK(coinscache.HaveCoin(op));
+ total_coins++;
}
+
+ BOOST_CHECK_EQUAL(initial_size , coinscache.GetCacheSize());
+ BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
+ chains_tested++;
}
+
+ BOOST_CHECK_EQUAL(chains_tested, 2);
}
- BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins);
- BOOST_CHECK_EQUAL(coins_in_background, initial_total_coins);
- BOOST_CHECK_EQUAL(coins_missing_from_background, new_coins);
+ // Mine some new blocks on top of the activated snapshot chainstate.
+ constexpr size_t new_coins{100};
+ mineBlocks(new_coins); // Defined in TestChain100Setup.
+
+ {
+ LOCK(::cs_main);
+ size_t coins_in_active{0};
+ size_t coins_in_background{0};
+ size_t coins_missing_from_background{0};
+
+ for (Chainstate* chainstate : chainman.GetAll()) {
+ BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
+ CCoinsViewCache& coinscache = chainstate->CoinsTip();
+ bool is_background = chainstate != &chainman.ActiveChainstate();
+
+ for (CTransactionRef& txn : m_coinbase_txns) {
+ COutPoint op{txn->GetHash(), 0};
+ if (coinscache.HaveCoin(op)) {
+ (is_background ? coins_in_background : coins_in_active)++;
+ } else if (is_background) {
+ coins_missing_from_background++;
+ }
+ }
+ }
+
+ BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins);
+ BOOST_CHECK_EQUAL(coins_in_background, initial_total_coins);
+ BOOST_CHECK_EQUAL(coins_missing_from_background, new_coins);
+ }
+
+ // Snapshot should refuse to load after one has already loaded.
+ BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));
+
+ // Snapshot blockhash should be unchanged.
+ BOOST_CHECK_EQUAL(
+ *chainman.ActiveChainstate().m_from_snapshot_blockhash,
+ loaded_snapshot_blockhash);
+ return std::make_tuple(&validation_chainstate, &snapshot_chainstate);
}
- // Snapshot should refuse to load after one has already loaded.
- BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(m_node, m_path_root));
+ // Simulate a restart of the node by flushing all state to disk, clearing the
+ // existing ChainstateManager, and unloading the block index.
+ //
+ // @returns a reference to the "restarted" ChainstateManager
+ ChainstateManager& SimulateNodeRestart()
+ {
+ ChainstateManager& chainman = *Assert(m_node.chainman);
+
+ BOOST_TEST_MESSAGE("Simulating node restart");
+ {
+ LOCK(::cs_main);
+ for (Chainstate* cs : chainman.GetAll()) {
+ cs->ForceFlushStateToDisk();
+ }
+ chainman.ResetChainstates();
+ BOOST_CHECK_EQUAL(chainman.GetAll().size(), 0);
+ const ChainstateManager::Options chainman_opts{
+ .chainparams = ::Params(),
+ .adjusted_time_callback = GetAdjustedTime,
+ };
+ // For robustness, ensure the old manager is destroyed before creating a
+ // new one.
+ m_node.chainman.reset();
+ m_node.chainman.reset(new ChainstateManager(chainman_opts));
+ }
+ return *Assert(m_node.chainman);
+ }
+};
- // Snapshot blockhash should be unchanged.
- BOOST_CHECK_EQUAL(
- *chainman.ActiveChainstate().m_from_snapshot_blockhash,
- loaded_snapshot_blockhash);
+//! Test basic snapshot activation.
+BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, SnapshotTestSetup)
+{
+ this->SetupSnapshot();
}
//! Test LoadBlockIndex behavior when multiple chainstates are in use.
//!
-//! - First, verfiy that setBlockIndexCandidates is as expected when using a single,
+//! - First, verify that setBlockIndexCandidates is as expected when using a single,
//! fully-validating chainstate.
//!
//! - Then mark a region of the chain BLOCK_ASSUMED_VALID and introduce a second chainstate
@@ -326,7 +405,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
{
ChainstateManager& chainman = *Assert(m_node.chainman);
CTxMemPool& mempool = *m_node.mempool;
- CChainState& cs1 = chainman.ActiveChainstate();
+ Chainstate& cs1 = chainman.ActiveChainstate();
int num_indexes{0};
int num_assumed_valid{0};
@@ -335,10 +414,10 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
const int assumed_valid_start_idx = last_assumed_valid_idx - expected_assumed_valid;
CBlockIndex* validated_tip{nullptr};
- CBlockIndex* assumed_tip{chainman.ActiveChain().Tip()};
+ CBlockIndex* assumed_tip{WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip())};
auto reload_all_block_indexes = [&]() {
- for (CChainState* cs : chainman.GetAll()) {
+ for (Chainstate* cs : chainman.GetAll()) {
LOCK(::cs_main);
cs->UnloadBlockIndex();
BOOST_CHECK(cs->setBlockIndexCandidates.empty());
@@ -373,8 +452,8 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid);
- CChainState& cs2 = WITH_LOCK(::cs_main,
- return chainman.InitializeChainstate(&mempool, GetRandHash()));
+ Chainstate& cs2 = WITH_LOCK(::cs_main,
+ return chainman.ActivateExistingSnapshot(&mempool, GetRandHash()));
reload_all_block_indexes();
@@ -390,4 +469,59 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes);
}
+//! Ensure that snapshot chainstates initialize properly when found on disk.
+BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
+{
+ this->SetupSnapshot();
+
+ ChainstateManager& chainman = *Assert(m_node.chainman);
+
+ fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir();
+ BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
+ BOOST_CHECK_EQUAL(snapshot_chainstate_dir, gArgs.GetDataDirNet() / "chainstate_snapshot");
+
+ BOOST_CHECK(chainman.IsSnapshotActive());
+ const uint256 snapshot_tip_hash = WITH_LOCK(chainman.GetMutex(),
+ return chainman.ActiveTip()->GetBlockHash());
+
+ auto all_chainstates = chainman.GetAll();
+ BOOST_CHECK_EQUAL(all_chainstates.size(), 2);
+
+ // Test that simulating a shutdown (resetting ChainstateManager) and then performing
+ // chainstate reinitializing successfully cleans up the background-validation
+ // chainstate data, and we end up with a single chainstate that is at tip.
+ ChainstateManager& chainman_restarted = this->SimulateNodeRestart();
+
+ BOOST_TEST_MESSAGE("Performing Load/Verify/Activate of chainstate");
+
+ // This call reinitializes the chainstates.
+ this->LoadVerifyActivateChainstate();
+
+ {
+ LOCK(chainman_restarted.GetMutex());
+ BOOST_CHECK_EQUAL(chainman_restarted.GetAll().size(), 2);
+ BOOST_CHECK(chainman_restarted.IsSnapshotActive());
+ BOOST_CHECK(!chainman_restarted.IsSnapshotValidated());
+
+ BOOST_CHECK_EQUAL(chainman_restarted.ActiveTip()->GetBlockHash(), snapshot_tip_hash);
+ BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210);
+ }
+
+ BOOST_TEST_MESSAGE(
+ "Ensure we can mine blocks on top of the initialized snapshot chainstate");
+ mineBlocks(10);
+ {
+ LOCK(chainman_restarted.GetMutex());
+ BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220);
+
+ // Background chainstate should be unaware of new blocks on the snapshot
+ // chainstate.
+ for (Chainstate* cs : chainman_restarted.GetAll()) {
+ if (cs != &chainman_restarted.ActiveChainstate()) {
+ BOOST_CHECK_EQUAL(cs->m_chain.Height(), 110);
+ }
+ }
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/validation_flush_tests.cpp b/src/test/validation_flush_tests.cpp
index 012169b17e..c06e6c8d3b 100644
--- a/src/test/validation_flush_tests.cpp
+++ b/src/test/validation_flush_tests.cpp
@@ -4,27 +4,20 @@
//
#include <sync.h>
#include <test/util/setup_common.h>
-#include <txmempool.h>
#include <validation.h>
#include <boost/test/unit_test.hpp>
-using node::BlockManager;
-
-BOOST_FIXTURE_TEST_SUITE(validation_flush_tests, ChainTestingSetup)
+BOOST_FIXTURE_TEST_SUITE(validation_flush_tests, TestingSetup)
//! Test utilities for detecting when we need to flush the coins cache based
//! on estimated memory usage.
//!
-//! @sa CChainState::GetCoinsCacheSizeState()
+//! @sa Chainstate::GetCoinsCacheSizeState()
//!
BOOST_AUTO_TEST_CASE(getcoinscachesizestate)
{
- CTxMemPool& mempool = *Assert(m_node.mempool);
- BlockManager blockman{};
- CChainState chainstate{&mempool, blockman, *Assert(m_node.chainman)};
- chainstate.InitCoinsDB(/*cache_size_bytes=*/1 << 10, /*in_memory=*/true, /*should_wipe=*/false);
- WITH_LOCK(::cs_main, chainstate.InitCoinsCache(1 << 10));
+ Chainstate& chainstate{m_node.chainman->ActiveChainstate()};
constexpr bool is_64_bit = sizeof(void*) == 8;
diff --git a/src/threadinterrupt.h b/src/threadinterrupt.h
index 363aab39ce..979bc2ee3e 100644
--- a/src/threadinterrupt.h
+++ b/src/threadinterrupt.h
@@ -6,6 +6,7 @@
#define BITCOIN_THREADINTERRUPT_H
#include <sync.h>
+#include <threadsafety.h>
#include <atomic>
#include <chrono>
diff --git a/src/timedata.cpp b/src/timedata.cpp
index ceee08e68c..fe9a5fbed7 100644
--- a/src/timedata.cpp
+++ b/src/timedata.cpp
@@ -32,9 +32,9 @@ int64_t GetTimeOffset()
return nTimeOffset;
}
-int64_t GetAdjustedTime()
+NodeClock::time_point GetAdjustedTime()
{
- return GetTime() + GetTimeOffset();
+ return NodeClock::now() + std::chrono::seconds{GetTimeOffset()};
}
#define BITCOIN_TIMEDATA_MAX_SAMPLES 200
diff --git a/src/timedata.h b/src/timedata.h
index ed2d8639f7..669a571f47 100644
--- a/src/timedata.h
+++ b/src/timedata.h
@@ -75,8 +75,7 @@ public:
/** Functions to keep track of adjusted P2P time */
int64_t GetTimeOffset();
-int64_t GetAdjustedTime();
-inline NodeSeconds AdjustedTime() { return Now<NodeSeconds>() + std::chrono::seconds{GetTimeOffset()}; }
+NodeClock::time_point GetAdjustedTime();
void AddTimeData(const CNetAddr& ip, int64_t nTime);
/**
diff --git a/src/txdb.h b/src/txdb.h
index a04596f7bb..8c41e26f6a 100644
--- a/src/txdb.h
+++ b/src/txdb.h
@@ -9,6 +9,7 @@
#include <coins.h>
#include <dbwrapper.h>
#include <sync.h>
+#include <fs.h>
#include <memory>
#include <optional>
@@ -72,6 +73,9 @@ public:
//! Dynamically alter the underlying leveldb cache size.
void ResizeCache(size_t new_cache_size) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+
+ //! @returns filesystem path to on-disk storage or std::nullopt if in memory.
+ std::optional<fs::path> StoragePath() { return m_db->StoragePath(); }
};
/** Access to the block database (blocks/index/) */
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index 7eff6bdbe3..84ed2e9ef5 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -24,38 +24,6 @@
#include <cmath>
#include <optional>
-// Helpers for modifying CTxMemPool::mapTx, which is a boost multi_index.
-struct update_descendant_state
-{
- update_descendant_state(int64_t _modifySize, CAmount _modifyFee, int64_t _modifyCount) :
- modifySize(_modifySize), modifyFee(_modifyFee), modifyCount(_modifyCount)
- {}
-
- void operator() (CTxMemPoolEntry &e)
- { e.UpdateDescendantState(modifySize, modifyFee, modifyCount); }
-
- private:
- int64_t modifySize;
- CAmount modifyFee;
- int64_t modifyCount;
-};
-
-struct update_ancestor_state
-{
- update_ancestor_state(int64_t _modifySize, CAmount _modifyFee, int64_t _modifyCount, int64_t _modifySigOpsCost) :
- modifySize(_modifySize), modifyFee(_modifyFee), modifyCount(_modifyCount), modifySigOpsCost(_modifySigOpsCost)
- {}
-
- void operator() (CTxMemPoolEntry &e)
- { e.UpdateAncestorState(modifySize, modifyFee, modifyCount, modifySigOpsCost); }
-
- private:
- int64_t modifySize;
- CAmount modifyFee;
- int64_t modifyCount;
- int64_t modifySigOpsCost;
-};
-
bool TestLockPointValidity(CChain& active_chain, const LockPoints& lp)
{
AssertLockHeld(cs_main);
@@ -106,7 +74,7 @@ void CTxMemPoolEntry::UpdateLockPoints(const LockPoints& lp)
size_t CTxMemPoolEntry::GetTxSize() const
{
- return GetVirtualTransactionSize(nTxWeight, sigOpCost);
+ return GetVirtualTransactionSize(nTxWeight, sigOpCost, ::nBytesPerSigOp);
}
void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendants,
@@ -146,7 +114,9 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendan
modifyCount++;
cachedDescendants[updateIt].insert(mapTx.iterator_to(descendant));
// Update ancestor state for each descendant
- mapTx.modify(mapTx.iterator_to(descendant), update_ancestor_state(updateIt->GetTxSize(), updateIt->GetModifiedFee(), 1, updateIt->GetSigOpCost()));
+ mapTx.modify(mapTx.iterator_to(descendant), [=](CTxMemPoolEntry& e) {
+ e.UpdateAncestorState(updateIt->GetTxSize(), updateIt->GetModifiedFee(), 1, updateIt->GetSigOpCost());
+ });
// Don't directly remove the transaction here -- doing so would
// invalidate iterators in cachedDescendants. Mark it for removal
// by inserting into descendants_to_remove.
@@ -155,7 +125,7 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendan
}
}
}
- mapTx.modify(updateIt, update_descendant_state(modifySize, modifyFee, modifyCount));
+ mapTx.modify(updateIt, [=](CTxMemPoolEntry& e) { e.UpdateDescendantState(modifySize, modifyFee, modifyCount); });
}
void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate)
@@ -175,7 +145,7 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256>& vHashes
// Iterate in reverse, so that whenever we are looking at a transaction
// we are sure that all in-mempool descendants have already been processed.
// This maximizes the benefit of the descendant cache and guarantees that
- // CTxMemPool::m_children will be updated, an assumption made in
+ // CTxMemPoolEntry::m_children will be updated, an assumption made in
// UpdateForDescendants.
for (const uint256 &hash : reverse_iterate(vHashesToUpdate)) {
// calculate children from mapNextTx
@@ -184,7 +154,7 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256>& vHashes
continue;
}
auto iter = mapNextTx.lower_bound(COutPoint(hash, 0));
- // First calculate the children, and update CTxMemPool::m_children to
+ // First calculate the children, and update CTxMemPoolEntry::m_children to
// include them, and update their CTxMemPoolEntry::m_parents to include this tx.
// we cache the in-mempool children to avoid duplicate updates
{
@@ -217,10 +187,7 @@ bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size,
size_t entry_count,
setEntries& setAncestors,
CTxMemPoolEntry::Parents& staged_ancestors,
- uint64_t limitAncestorCount,
- uint64_t limitAncestorSize,
- uint64_t limitDescendantCount,
- uint64_t limitDescendantSize,
+ const Limits& limits,
std::string &errString) const
{
size_t totalSizeWithAncestors = entry_size;
@@ -233,14 +200,14 @@ bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size,
staged_ancestors.erase(stage);
totalSizeWithAncestors += stageit->GetTxSize();
- if (stageit->GetSizeWithDescendants() + entry_size > limitDescendantSize) {
- errString = strprintf("exceeds descendant size limit for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limitDescendantSize);
+ if (stageit->GetSizeWithDescendants() + entry_size > static_cast<uint64_t>(limits.descendant_size_vbytes)) {
+ errString = strprintf("exceeds descendant size limit for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_size_vbytes);
return false;
- } else if (stageit->GetCountWithDescendants() + entry_count > limitDescendantCount) {
- errString = strprintf("too many descendants for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limitDescendantCount);
+ } else if (stageit->GetCountWithDescendants() + entry_count > static_cast<uint64_t>(limits.descendant_count)) {
+ errString = strprintf("too many descendants for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_count);
return false;
- } else if (totalSizeWithAncestors > limitAncestorSize) {
- errString = strprintf("exceeds ancestor size limit [limit: %u]", limitAncestorSize);
+ } else if (totalSizeWithAncestors > static_cast<uint64_t>(limits.ancestor_size_vbytes)) {
+ errString = strprintf("exceeds ancestor size limit [limit: %u]", limits.ancestor_size_vbytes);
return false;
}
@@ -252,8 +219,8 @@ bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size,
if (setAncestors.count(parent_it) == 0) {
staged_ancestors.insert(parent);
}
- if (staged_ancestors.size() + setAncestors.size() + entry_count > limitAncestorCount) {
- errString = strprintf("too many unconfirmed ancestors [limit: %u]", limitAncestorCount);
+ if (staged_ancestors.size() + setAncestors.size() + entry_count > static_cast<uint64_t>(limits.ancestor_count)) {
+ errString = strprintf("too many unconfirmed ancestors [limit: %u]", limits.ancestor_count);
return false;
}
}
@@ -263,10 +230,7 @@ bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size,
}
bool CTxMemPool::CheckPackageLimits(const Package& package,
- uint64_t limitAncestorCount,
- uint64_t limitAncestorSize,
- uint64_t limitDescendantCount,
- uint64_t limitDescendantSize,
+ const Limits& limits,
std::string &errString) const
{
CTxMemPoolEntry::Parents staged_ancestors;
@@ -277,8 +241,8 @@ bool CTxMemPool::CheckPackageLimits(const Package& package,
std::optional<txiter> piter = GetIter(input.prevout.hash);
if (piter) {
staged_ancestors.insert(**piter);
- if (staged_ancestors.size() + package.size() > limitAncestorCount) {
- errString = strprintf("too many unconfirmed parents [limit: %u]", limitAncestorCount);
+ if (staged_ancestors.size() + package.size() > static_cast<uint64_t>(limits.ancestor_count)) {
+ errString = strprintf("too many unconfirmed parents [limit: %u]", limits.ancestor_count);
return false;
}
}
@@ -290,8 +254,7 @@ bool CTxMemPool::CheckPackageLimits(const Package& package,
setEntries setAncestors;
const auto ret = CalculateAncestorsAndCheckLimits(total_size, package.size(),
setAncestors, staged_ancestors,
- limitAncestorCount, limitAncestorSize,
- limitDescendantCount, limitDescendantSize, errString);
+ limits, errString);
// It's possible to overestimate the ancestor/descendant totals.
if (!ret) errString.insert(0, "possibly ");
return ret;
@@ -299,10 +262,7 @@ bool CTxMemPool::CheckPackageLimits(const Package& package,
bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry,
setEntries &setAncestors,
- uint64_t limitAncestorCount,
- uint64_t limitAncestorSize,
- uint64_t limitDescendantCount,
- uint64_t limitDescendantSize,
+ const Limits& limits,
std::string &errString,
bool fSearchForParents /* = true */) const
{
@@ -317,8 +277,8 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry,
std::optional<txiter> piter = GetIter(tx.vin[i].prevout.hash);
if (piter) {
staged_ancestors.insert(**piter);
- if (staged_ancestors.size() + 1 > limitAncestorCount) {
- errString = strprintf("too many unconfirmed parents [limit: %u]", limitAncestorCount);
+ if (staged_ancestors.size() + 1 > static_cast<uint64_t>(limits.ancestor_count)) {
+ errString = strprintf("too many unconfirmed parents [limit: %u]", limits.ancestor_count);
return false;
}
}
@@ -332,8 +292,7 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry,
return CalculateAncestorsAndCheckLimits(entry.GetTxSize(), /*entry_count=*/1,
setAncestors, staged_ancestors,
- limitAncestorCount, limitAncestorSize,
- limitDescendantCount, limitDescendantSize, errString);
+ limits, errString);
}
void CTxMemPool::UpdateAncestorsOf(bool add, txiter it, setEntries &setAncestors)
@@ -347,7 +306,7 @@ void CTxMemPool::UpdateAncestorsOf(bool add, txiter it, setEntries &setAncestors
const int64_t updateSize = updateCount * it->GetTxSize();
const CAmount updateFee = updateCount * it->GetModifiedFee();
for (txiter ancestorIt : setAncestors) {
- mapTx.modify(ancestorIt, update_descendant_state(updateSize, updateFee, updateCount));
+ mapTx.modify(ancestorIt, [=](CTxMemPoolEntry& e) { e.UpdateDescendantState(updateSize, updateFee, updateCount); });
}
}
@@ -362,7 +321,7 @@ void CTxMemPool::UpdateEntryForAncestors(txiter it, const setEntries &setAncesto
updateFee += ancestorIt->GetModifiedFee();
updateSigOpsCost += ancestorIt->GetSigOpCost();
}
- mapTx.modify(it, update_ancestor_state(updateSize, updateFee, updateCount, updateSigOpsCost));
+ mapTx.modify(it, [=](CTxMemPoolEntry& e){ e.UpdateAncestorState(updateSize, updateFee, updateCount, updateSigOpsCost); });
}
void CTxMemPool::UpdateChildrenForRemoval(txiter it)
@@ -377,7 +336,6 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b
{
// For each entry, walk back all ancestors and decrement size associated with this
// transaction
- const uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
if (updateDescendants) {
// updateDescendants should be true whenever we're not recursively
// removing a tx and all its descendants, eg when a transaction is
@@ -393,7 +351,7 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b
CAmount modifyFee = -removeIt->GetModifiedFee();
int modifySigOps = -removeIt->GetSigOpCost();
for (txiter dit : setDescendants) {
- mapTx.modify(dit, update_ancestor_state(modifySize, modifyFee, -1, modifySigOps));
+ mapTx.modify(dit, [=](CTxMemPoolEntry& e){ e.UpdateAncestorState(modifySize, modifyFee, -1, modifySigOps); });
}
}
}
@@ -420,7 +378,7 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b
// mempool parents we'd calculate by searching, and it's important that
// we use the cached notion of ancestor transactions as the set of
// things to update for removal.
- CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false);
+ CalculateMemPoolAncestors(entry, setAncestors, Limits::NoLimits(), dummy, false);
// Note that UpdateAncestorsOf severs the child links that point to
// removeIt in the entries for the parents of removeIt.
UpdateAncestorsOf(false, removeIt, setAncestors);
@@ -458,6 +416,12 @@ CTxMemPool::CTxMemPool(const Options& opts)
minerPolicyEstimator{opts.estimator},
m_max_size_bytes{opts.max_size_bytes},
m_expiry{opts.expiry},
+ m_incremental_relay_feerate{opts.incremental_relay_feerate},
+ m_min_relay_feerate{opts.min_relay_feerate},
+ m_dust_relay_feerate{opts.dust_relay_feerate},
+ m_permit_bare_multisig{opts.permit_bare_multisig},
+ m_max_datacarrier_bytes{opts.max_datacarrier_bytes},
+ m_require_standard{opts.require_standard},
m_full_rbf{opts.full_rbf},
m_limits{opts.limits}
{
@@ -768,9 +732,8 @@ void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendhei
assert(std::equal(setParentCheck.begin(), setParentCheck.end(), it->GetMemPoolParentsConst().begin(), comp));
// Verify ancestor state is correct.
setEntries setAncestors;
- uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
std::string dummy;
- CalculateMemPoolAncestors(*it, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy);
+ CalculateMemPoolAncestors(*it, setAncestors, Limits::NoLimits(), dummy);
uint64_t nCountCheck = setAncestors.size() + 1;
uint64_t nSizeCheck = it->GetTxSize();
CAmount nFeesCheck = it->GetModifiedFee();
@@ -932,18 +895,17 @@ void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeD
mapTx.modify(it, [&nFeeDelta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(nFeeDelta); });
// Now update all ancestors' modified fees with descendants
setEntries setAncestors;
- uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
std::string dummy;
- CalculateMemPoolAncestors(*it, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false);
+ CalculateMemPoolAncestors(*it, setAncestors, Limits::NoLimits(), dummy, false);
for (txiter ancestorIt : setAncestors) {
- mapTx.modify(ancestorIt, update_descendant_state(0, nFeeDelta, 0));
+ mapTx.modify(ancestorIt, [=](CTxMemPoolEntry& e){ e.UpdateDescendantState(0, nFeeDelta, 0);});
}
// Now update all descendants' modified fees with ancestors
setEntries setDescendants;
CalculateDescendants(it, setDescendants);
setDescendants.erase(it);
for (txiter descendantIt : setDescendants) {
- mapTx.modify(descendantIt, update_ancestor_state(0, nFeeDelta, 0, 0));
+ mapTx.modify(descendantIt, [=](CTxMemPoolEntry& e){ e.UpdateAncestorState(0, nFeeDelta, 0, 0); });
}
++nTransactionsUpdated;
}
@@ -1073,9 +1035,8 @@ int CTxMemPool::Expire(std::chrono::seconds time)
void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, bool validFeeEstimate)
{
setEntries setAncestors;
- uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
std::string dummy;
- CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy);
+ CalculateMemPoolAncestors(entry, setAncestors, Limits::NoLimits(), dummy);
return addUnchecked(entry, setAncestors, validFeeEstimate);
}
@@ -1117,12 +1078,12 @@ CFeeRate CTxMemPool::GetMinFee(size_t sizelimit) const {
rollingMinimumFeeRate = rollingMinimumFeeRate / pow(2.0, (time - lastRollingFeeUpdate) / halflife);
lastRollingFeeUpdate = time;
- if (rollingMinimumFeeRate < (double)incrementalRelayFee.GetFeePerK() / 2) {
+ if (rollingMinimumFeeRate < (double)m_incremental_relay_feerate.GetFeePerK() / 2) {
rollingMinimumFeeRate = 0;
return CFeeRate(0);
}
}
- return std::max(CFeeRate(llround(rollingMinimumFeeRate)), incrementalRelayFee);
+ return std::max(CFeeRate(llround(rollingMinimumFeeRate)), m_incremental_relay_feerate);
}
void CTxMemPool::trackPackageRemoved(const CFeeRate& rate) {
@@ -1146,7 +1107,7 @@ void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpends
// to have 0 fee). This way, we don't allow txn to enter mempool with feerate
// equal to txn which were removed with no block in between.
CFeeRate removed(it->GetModFeesWithDescendants(), it->GetSizeWithDescendants());
- removed += incrementalRelayFee;
+ removed += m_incremental_relay_feerate;
trackPackageRemoved(removed);
maxFeeRateRemoved = std::max(maxFeeRateRemoved, removed);
diff --git a/src/txmempool.h b/src/txmempool.h
index d7d308038c..4afaac0506 100644
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -35,7 +35,7 @@
class CBlockIndex;
class CChain;
-class CChainState;
+class Chainstate;
extern RecursiveMutex cs_main;
/** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */
@@ -365,7 +365,7 @@ enum class MemPoolRemovalReason {
* - a transaction which doesn't meet the minimum fee requirements.
* - a new transaction that double-spends an input of a transaction already in
* the pool where the new transaction does not meet the Replace-By-Fee
- * requirements as defined in BIP 125.
+ * requirements as defined in doc/policy/mempool-replacements.md.
* - a non-standard transaction.
*
* CTxMemPool::mapTx, and CTxMemPoolEntry bookkeeping:
@@ -526,6 +526,8 @@ public:
typedef std::set<txiter, CompareIteratorByHash> setEntries;
+ using Limits = kernel::MemPoolLimits;
+
uint64_t CalculateDescendantMaximum(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs);
private:
typedef std::map<txiter, setEntries, CompareIteratorByHash> cacheMap;
@@ -545,19 +547,22 @@ private:
/**
* Helper function to calculate all in-mempool ancestors of staged_ancestors and apply ancestor
* and descendant limits (including staged_ancestors thsemselves, entry_size and entry_count).
- * param@[in] entry_size Virtual size to include in the limits.
- * param@[in] entry_count How many entries to include in the limits.
- * param@[in] staged_ancestors Should contain entries in the mempool.
- * param@[out] setAncestors Will be populated with all mempool ancestors.
+ *
+ * @param[in] entry_size Virtual size to include in the limits.
+ * @param[in] entry_count How many entries to include in the limits.
+ * @param[out] setAncestors Will be populated with all mempool ancestors.
+ * @param[in] staged_ancestors Should contain entries in the mempool.
+ * @param[in] limits Maximum number and size of ancestors and descendants
+ * @param[out] errString Populated with error reason if any limits are hit
+ *
+ * @return true if no limits were hit and all in-mempool ancestors were calculated, false
+ * otherwise
*/
bool CalculateAncestorsAndCheckLimits(size_t entry_size,
size_t entry_count,
setEntries& setAncestors,
CTxMemPoolEntry::Parents &staged_ancestors,
- uint64_t limitAncestorCount,
- uint64_t limitAncestorSize,
- uint64_t limitDescendantCount,
- uint64_t limitDescendantSize,
+ const Limits& limits,
std::string &errString) const EXCLUSIVE_LOCKS_REQUIRED(cs);
public:
@@ -568,10 +573,14 @@ public:
const int64_t m_max_size_bytes;
const std::chrono::seconds m_expiry;
+ const CFeeRate m_incremental_relay_feerate;
+ const CFeeRate m_min_relay_feerate;
+ const CFeeRate m_dust_relay_feerate;
+ const bool m_permit_bare_multisig;
+ const std::optional<unsigned> m_max_datacarrier_bytes;
+ const bool m_require_standard;
const bool m_full_rbf;
- using Limits = kernel::MemPoolLimits;
-
const Limits m_limits;
/** Create a new CTxMemPool.
@@ -662,38 +671,41 @@ public:
*/
void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main) LOCKS_EXCLUDED(m_epoch);
- /** Try to calculate all in-mempool ancestors of entry.
- * (these are all calculated including the tx itself)
- * limitAncestorCount = max number of ancestors
- * limitAncestorSize = max size of ancestors
- * limitDescendantCount = max number of descendants any ancestor can have
- * limitDescendantSize = max size of descendants any ancestor can have
- * errString = populated with error reason if any limits are hit
- * fSearchForParents = whether to search a tx's vin for in-mempool parents, or
- * look up parents from mapLinks. Must be true for entries not in the mempool
+ /**
+ * Try to calculate all in-mempool ancestors of entry.
+ * (these are all calculated including the tx itself)
+ *
+ * @param[in] entry CTxMemPoolEntry of which all in-mempool ancestors are calculated
+ * @param[out] setAncestors Will be populated with all mempool ancestors.
+ * @param[in] limits Maximum number and size of ancestors and descendants
+ * @param[out] errString Populated with error reason if any limits are hit
+ * @param[in] fSearchForParents Whether to search a tx's vin for in-mempool parents, or look
+ * up parents from mapLinks. Must be true for entries not in
+ * the mempool
+ *
+ * @return true if no limits were hit and all in-mempool ancestors were calculated, false
+ * otherwise
*/
- bool CalculateMemPoolAncestors(const CTxMemPoolEntry& entry, setEntries& setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string& errString, bool fSearchForParents = true) const EXCLUSIVE_LOCKS_REQUIRED(cs);
+ bool CalculateMemPoolAncestors(const CTxMemPoolEntry& entry,
+ setEntries& setAncestors,
+ const Limits& limits,
+ std::string& errString,
+ bool fSearchForParents = true) const EXCLUSIVE_LOCKS_REQUIRED(cs);
/** Calculate all in-mempool ancestors of a set of transactions not already in the mempool and
* check ancestor and descendant limits. Heuristics are used to estimate the ancestor and
* descendant count of all entries if the package were to be added to the mempool. The limits
* are applied to the union of all package transactions. For example, if the package has 3
- * transactions and limitAncestorCount = 25, the union of all 3 sets of ancestors (including the
+ * transactions and limits.ancestor_count = 25, the union of all 3 sets of ancestors (including the
* transactions themselves) must be <= 22.
* @param[in] package Transaction package being evaluated for acceptance
* to mempool. The transactions need not be direct
* ancestors/descendants of each other.
- * @param[in] limitAncestorCount Max number of txns including ancestors.
- * @param[in] limitAncestorSize Max virtual size including ancestors.
- * @param[in] limitDescendantCount Max number of txns including descendants.
- * @param[in] limitDescendantSize Max virtual size including descendants.
+ * @param[in] limits Maximum number and size of ancestors and descendants
* @param[out] errString Populated with error reason if a limit is hit.
*/
bool CheckPackageLimits(const Package& package,
- uint64_t limitAncestorCount,
- uint64_t limitAncestorSize,
- uint64_t limitDescendantCount,
- uint64_t limitDescendantSize,
+ const Limits& limits,
std::string &errString) const EXCLUSIVE_LOCKS_REQUIRED(cs);
/** Populate setDescendants with all in-mempool descendants of hash.
@@ -702,11 +714,11 @@ public:
void CalculateDescendants(txiter it, setEntries& setDescendants) const EXCLUSIVE_LOCKS_REQUIRED(cs);
/** The minimum fee to get into the mempool, which may itself not be enough
- * for larger-sized transactions.
- * The incrementalRelayFee policy variable is used to bound the time it
- * takes the fee rate to go back down all the way to 0. When the feerate
- * would otherwise be half of this, it is set to 0 instead.
- */
+ * for larger-sized transactions.
+ * The m_incremental_relay_feerate policy variable is used to bound the time it
+ * takes the fee rate to go back down all the way to 0. When the feerate
+ * would otherwise be half of this, it is set to 0 instead.
+ */
CFeeRate GetMinFee() const {
return GetMinFee(m_max_size_bytes);
}
@@ -819,7 +831,7 @@ private:
* mempool but may have child transactions in the mempool, eg during a
* chain reorg.
*
- * @pre CTxMemPool::m_children is correct for the given tx and all
+ * @pre CTxMemPoolEntry::m_children is correct for the given tx and all
* descendants.
* @pre cachedDescendants is an accurate cache where each entry has all
* descendants of the corresponding key, including those that should
diff --git a/src/uint256.h b/src/uint256.h
index 5c3a2f5409..e74b9ff7b1 100644
--- a/src/uint256.h
+++ b/src/uint256.h
@@ -6,6 +6,7 @@
#ifndef BITCOIN_UINT256_H
#define BITCOIN_UINT256_H
+#include <crypto/common.h>
#include <span.h>
#include <assert.h>
@@ -84,15 +85,7 @@ public:
uint64_t GetUint64(int pos) const
{
- const uint8_t* ptr = m_data + pos * 8;
- return ((uint64_t)ptr[0]) | \
- ((uint64_t)ptr[1]) << 8 | \
- ((uint64_t)ptr[2]) << 16 | \
- ((uint64_t)ptr[3]) << 24 | \
- ((uint64_t)ptr[4]) << 32 | \
- ((uint64_t)ptr[5]) << 40 | \
- ((uint64_t)ptr[6]) << 48 | \
- ((uint64_t)ptr[7]) << 56;
+ return ReadLE64(m_data + pos * 8);
}
template<typename Stream>
diff --git a/src/univalue/include/univalue.h b/src/univalue/include/univalue.h
index 8ba6fd5425..850d0a1cbc 100644
--- a/src/univalue/include/univalue.h
+++ b/src/univalue/include/univalue.h
@@ -19,6 +19,11 @@ class UniValue {
public:
enum VType { VNULL, VOBJ, VARR, VSTR, VNUM, VBOOL, };
+ class type_error : public std::runtime_error
+ {
+ using std::runtime_error::runtime_error;
+ };
+
UniValue() { typ = VNULL; }
UniValue(UniValue::VType initialType, const std::string& initialStr = "") {
typ = initialType;
@@ -80,14 +85,14 @@ public:
bool isArray() const { return (typ == VARR); }
bool isObject() const { return (typ == VOBJ); }
- void push_back(const UniValue& val);
+ void push_back(UniValue val);
void push_backV(const std::vector<UniValue>& vec);
template <class It>
void push_backV(It first, It last);
- void __pushKV(const std::string& key, const UniValue& val);
- void pushKV(const std::string& key, const UniValue& val);
- void pushKVs(const UniValue& obj);
+ void __pushKV(std::string key, UniValue val);
+ void pushKV(std::string key, UniValue val);
+ void pushKVs(UniValue obj);
std::string write(unsigned int prettyIndent = 0,
unsigned int indentLevel = 0) const;
diff --git a/src/univalue/lib/univalue.cpp b/src/univalue/lib/univalue.cpp
index 55e777b8ae..4448981d3e 100644
--- a/src/univalue/lib/univalue.cpp
+++ b/src/univalue/lib/univalue.cpp
@@ -101,11 +101,11 @@ void UniValue::setObject()
typ = VOBJ;
}
-void UniValue::push_back(const UniValue& val_)
+void UniValue::push_back(UniValue val)
{
checkType(VARR);
- values.push_back(val_);
+ values.push_back(std::move(val));
}
void UniValue::push_backV(const std::vector<UniValue>& vec)
@@ -115,32 +115,32 @@ void UniValue::push_backV(const std::vector<UniValue>& vec)
values.insert(values.end(), vec.begin(), vec.end());
}
-void UniValue::__pushKV(const std::string& key, const UniValue& val_)
+void UniValue::__pushKV(std::string key, UniValue val)
{
checkType(VOBJ);
- keys.push_back(key);
- values.push_back(val_);
+ keys.push_back(std::move(key));
+ values.push_back(std::move(val));
}
-void UniValue::pushKV(const std::string& key, const UniValue& val_)
+void UniValue::pushKV(std::string key, UniValue val)
{
checkType(VOBJ);
size_t idx;
if (findKey(key, idx))
- values[idx] = val_;
+ values[idx] = std::move(val);
else
- __pushKV(key, val_);
+ __pushKV(std::move(key), std::move(val));
}
-void UniValue::pushKVs(const UniValue& obj)
+void UniValue::pushKVs(UniValue obj)
{
checkType(VOBJ);
obj.checkType(VOBJ);
for (size_t i = 0; i < obj.keys.size(); i++)
- __pushKV(obj.keys[i], obj.values.at(i));
+ __pushKV(std::move(obj.keys.at(i)), std::move(obj.values.at(i)));
}
void UniValue::getObjMap(std::map<std::string,UniValue>& kv) const
@@ -210,7 +210,7 @@ const UniValue& UniValue::operator[](size_t index) const
void UniValue::checkType(const VType& expected) const
{
if (typ != expected) {
- throw std::runtime_error{"JSON value of type " + std::string{uvTypeName(typ)} + " is not of expected type " +
+ throw type_error{"JSON value of type " + std::string{uvTypeName(typ)} + " is not of expected type " +
std::string{uvTypeName(expected)}};
}
}
diff --git a/src/util/bip32.cpp b/src/util/bip32.cpp
index 39e43eeb31..796af4a544 100644
--- a/src/util/bip32.cpp
+++ b/src/util/bip32.cpp
@@ -6,12 +6,10 @@
#include <util/bip32.h>
#include <util/strencodings.h>
-#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <sstream>
-
bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath)
{
std::stringstream ss(keypath_str);
diff --git a/src/util/bitdeque.h b/src/util/bitdeque.h
new file mode 100644
index 0000000000..1e34b72475
--- /dev/null
+++ b/src/util/bitdeque.h
@@ -0,0 +1,469 @@
+// Copyright (c) 2022 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_BITDEQUE_H
+#define BITCOIN_UTIL_BITDEQUE_H
+
+#include <bitset>
+#include <cstddef>
+#include <deque>
+#include <limits>
+#include <stdexcept>
+#include <tuple>
+
+/** Class that mimics std::deque<bool>, but with std::vector<bool>'s bit packing.
+ *
+ * BlobSize selects the (minimum) number of bits that are allocated at once.
+ * Larger values reduce the asymptotic memory usage overhead, at the cost of
+ * needing larger up-front allocations. The default is 4096 bytes.
+ */
+template<int BlobSize = 4096 * 8>
+class bitdeque
+{
+ // Internal definitions
+ using word_type = std::bitset<BlobSize>;
+ using deque_type = std::deque<word_type>;
+ static_assert(BlobSize > 0);
+ static constexpr int BITS_PER_WORD = BlobSize;
+
+ // Forward and friend declarations of iterator types.
+ template<bool Const> class Iterator;
+ template<bool Const> friend class Iterator;
+
+ /** Iterator to a bitdeque element, const or not. */
+ template<bool Const>
+ class Iterator
+ {
+ using deque_iterator = std::conditional_t<Const, typename deque_type::const_iterator, typename deque_type::iterator>;
+
+ deque_iterator m_it;
+ int m_bitpos{0};
+ Iterator(const deque_iterator& it, int bitpos) : m_it(it), m_bitpos(bitpos) {}
+ friend class bitdeque;
+
+ public:
+ using iterator_category = std::random_access_iterator_tag;
+ using value_type = bool;
+ using pointer = void;
+ using const_pointer = void;
+ using reference = std::conditional_t<Const, bool, typename word_type::reference>;
+ using const_reference = bool;
+ using difference_type = std::ptrdiff_t;
+
+ /** Default constructor. */
+ Iterator() = default;
+
+ /** Default copy constructor. */
+ Iterator(const Iterator&) = default;
+
+ /** Conversion from non-const to const iterator. */
+ template<bool ConstArg = Const, typename = std::enable_if_t<Const && ConstArg>>
+ Iterator(const Iterator<false>& x) : m_it(x.m_it), m_bitpos(x.m_bitpos) {}
+
+ Iterator& operator+=(difference_type dist)
+ {
+ if (dist > 0) {
+ if (dist + m_bitpos >= BITS_PER_WORD) {
+ ++m_it;
+ dist -= BITS_PER_WORD - m_bitpos;
+ m_bitpos = 0;
+ }
+ auto jump = dist / BITS_PER_WORD;
+ m_it += jump;
+ m_bitpos += dist - jump * BITS_PER_WORD;
+ } else if (dist < 0) {
+ dist = -dist;
+ if (dist > m_bitpos) {
+ --m_it;
+ dist -= m_bitpos + 1;
+ m_bitpos = BITS_PER_WORD - 1;
+ }
+ auto jump = dist / BITS_PER_WORD;
+ m_it -= jump;
+ m_bitpos -= dist - jump * BITS_PER_WORD;
+ }
+ return *this;
+ }
+
+ friend difference_type operator-(const Iterator& x, const Iterator& y)
+ {
+ return BITS_PER_WORD * (x.m_it - y.m_it) + x.m_bitpos - y.m_bitpos;
+ }
+
+ Iterator& operator=(const Iterator&) = default;
+ Iterator& operator-=(difference_type dist) { return operator+=(-dist); }
+ Iterator& operator++() { ++m_bitpos; if (m_bitpos == BITS_PER_WORD) { m_bitpos = 0; ++m_it; }; return *this; }
+ Iterator operator++(int) { auto ret{*this}; operator++(); return ret; }
+ Iterator& operator--() { if (m_bitpos == 0) { m_bitpos = BITS_PER_WORD; --m_it; }; --m_bitpos; return *this; }
+ Iterator operator--(int) { auto ret{*this}; operator--(); return ret; }
+ friend Iterator operator+(Iterator x, difference_type dist) { x += dist; return x; }
+ friend Iterator operator+(difference_type dist, Iterator x) { x += dist; return x; }
+ friend Iterator operator-(Iterator x, difference_type dist) { x -= dist; return x; }
+ friend bool operator<(const Iterator& x, const Iterator& y) { return std::tie(x.m_it, x.m_bitpos) < std::tie(y.m_it, y.m_bitpos); }
+ friend bool operator>(const Iterator& x, const Iterator& y) { return std::tie(x.m_it, x.m_bitpos) > std::tie(y.m_it, y.m_bitpos); }
+ friend bool operator<=(const Iterator& x, const Iterator& y) { return std::tie(x.m_it, x.m_bitpos) <= std::tie(y.m_it, y.m_bitpos); }
+ friend bool operator>=(const Iterator& x, const Iterator& y) { return std::tie(x.m_it, x.m_bitpos) >= std::tie(y.m_it, y.m_bitpos); }
+ friend bool operator==(const Iterator& x, const Iterator& y) { return x.m_it == y.m_it && x.m_bitpos == y.m_bitpos; }
+ friend bool operator!=(const Iterator& x, const Iterator& y) { return x.m_it != y.m_it || x.m_bitpos != y.m_bitpos; }
+ reference operator*() const { return (*m_it)[m_bitpos]; }
+ reference operator[](difference_type pos) const { return *(*this + pos); }
+ };
+
+public:
+ using value_type = bool;
+ using size_type = std::size_t;
+ using difference_type = typename deque_type::difference_type;
+ using reference = typename word_type::reference;
+ using const_reference = bool;
+ using iterator = Iterator<false>;
+ using const_iterator = Iterator<true>;
+ using pointer = void;
+ using const_pointer = void;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+private:
+ /** Deque of bitsets storing the actual bit data. */
+ deque_type m_deque;
+
+ /** Number of unused bits at the front of m_deque.front(). */
+ int m_pad_begin;
+
+ /** Number of unused bits at the back of m_deque.back(). */
+ int m_pad_end;
+
+ /** Shrink the container by n bits, removing from the end. */
+ void erase_back(size_type n)
+ {
+ if (n >= static_cast<size_type>(BITS_PER_WORD - m_pad_end)) {
+ n -= BITS_PER_WORD - m_pad_end;
+ m_pad_end = 0;
+ m_deque.erase(m_deque.end() - 1 - (n / BITS_PER_WORD), m_deque.end());
+ n %= BITS_PER_WORD;
+ }
+ if (n) {
+ auto& last = m_deque.back();
+ while (n) {
+ last.reset(BITS_PER_WORD - 1 - m_pad_end);
+ ++m_pad_end;
+ --n;
+ }
+ }
+ }
+
+ /** Extend the container by n bits, adding at the end. */
+ void extend_back(size_type n)
+ {
+ if (n > static_cast<size_type>(m_pad_end)) {
+ n -= m_pad_end + 1;
+ m_pad_end = BITS_PER_WORD - 1;
+ m_deque.insert(m_deque.end(), 1 + (n / BITS_PER_WORD), {});
+ n %= BITS_PER_WORD;
+ }
+ m_pad_end -= n;
+ }
+
+ /** Shrink the container by n bits, removing from the beginning. */
+ void erase_front(size_type n)
+ {
+ if (n >= static_cast<size_type>(BITS_PER_WORD - m_pad_begin)) {
+ n -= BITS_PER_WORD - m_pad_begin;
+ m_pad_begin = 0;
+ m_deque.erase(m_deque.begin(), m_deque.begin() + 1 + (n / BITS_PER_WORD));
+ n %= BITS_PER_WORD;
+ }
+ if (n) {
+ auto& first = m_deque.front();
+ while (n) {
+ first.reset(m_pad_begin);
+ ++m_pad_begin;
+ --n;
+ }
+ }
+ }
+
+ /** Extend the container by n bits, adding at the beginning. */
+ void extend_front(size_type n)
+ {
+ if (n > static_cast<size_type>(m_pad_begin)) {
+ n -= m_pad_begin + 1;
+ m_pad_begin = BITS_PER_WORD - 1;
+ m_deque.insert(m_deque.begin(), 1 + (n / BITS_PER_WORD), {});
+ n %= BITS_PER_WORD;
+ }
+ m_pad_begin -= n;
+ }
+
+ /** Insert a sequence of falses anywhere in the container. */
+ void insert_zeroes(size_type before, size_type count)
+ {
+ size_type after = size() - before;
+ if (before < after) {
+ extend_front(count);
+ std::move(begin() + count, begin() + count + before, begin());
+ } else {
+ extend_back(count);
+ std::move_backward(begin() + before, begin() + before + after, end());
+ }
+ }
+
+public:
+ /** Construct an empty container. */
+ explicit bitdeque() : m_pad_begin{0}, m_pad_end{0} {}
+
+ /** Set the container equal to count times the value of val. */
+ void assign(size_type count, bool val)
+ {
+ m_deque.clear();
+ m_deque.resize((count + BITS_PER_WORD - 1) / BITS_PER_WORD);
+ m_pad_begin = 0;
+ m_pad_end = 0;
+ if (val) {
+ for (auto& elem : m_deque) elem.flip();
+ }
+ if (count % BITS_PER_WORD) {
+ erase_back(BITS_PER_WORD - (count % BITS_PER_WORD));
+ }
+ }
+
+ /** Construct a container containing count times the value of val. */
+ bitdeque(size_type count, bool val) { assign(count, val); }
+
+ /** Construct a container containing count false values. */
+ explicit bitdeque(size_t count) { assign(count, false); }
+
+ /** Copy constructor. */
+ bitdeque(const bitdeque&) = default;
+
+ /** Move constructor. */
+ bitdeque(bitdeque&&) noexcept = default;
+
+ /** Copy assignment operator. */
+ bitdeque& operator=(const bitdeque& other) = default;
+
+ /** Move assignment operator. */
+ bitdeque& operator=(bitdeque&& other) noexcept = default;
+
+ // Iterator functions.
+ iterator begin() noexcept { return {m_deque.begin(), m_pad_begin}; }
+ iterator end() noexcept { return iterator{m_deque.end(), 0} - m_pad_end; }
+ const_iterator begin() const noexcept { return const_iterator{m_deque.cbegin(), m_pad_begin}; }
+ const_iterator cbegin() const noexcept { return const_iterator{m_deque.cbegin(), m_pad_begin}; }
+ const_iterator end() const noexcept { return const_iterator{m_deque.cend(), 0} - m_pad_end; }
+ const_iterator cend() const noexcept { return const_iterator{m_deque.cend(), 0} - m_pad_end; }
+ reverse_iterator rbegin() noexcept { return reverse_iterator{end()}; }
+ reverse_iterator rend() noexcept { return reverse_iterator{begin()}; }
+ const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{cend()}; }
+ const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator{cend()}; }
+ const_reverse_iterator rend() const noexcept { return const_reverse_iterator{cbegin()}; }
+ const_reverse_iterator crend() const noexcept { return const_reverse_iterator{cbegin()}; }
+
+ /** Count the number of bits in the container. */
+ size_type size() const noexcept { return m_deque.size() * BITS_PER_WORD - m_pad_begin - m_pad_end; }
+
+ /** Determine whether the container is empty. */
+ bool empty() const noexcept
+ {
+ return m_deque.size() == 0 || (m_deque.size() == 1 && (m_pad_begin + m_pad_end == BITS_PER_WORD));
+ }
+
+ /** Return the maximum size of the container. */
+ size_type max_size() const noexcept
+ {
+ if (m_deque.max_size() < std::numeric_limits<difference_type>::max() / BITS_PER_WORD) {
+ return m_deque.max_size() * BITS_PER_WORD;
+ } else {
+ return std::numeric_limits<difference_type>::max();
+ }
+ }
+
+ /** Set the container equal to the bits in [first,last). */
+ template<typename It>
+ void assign(It first, It last)
+ {
+ size_type count = std::distance(first, last);
+ assign(count, false);
+ auto it = begin();
+ while (first != last) {
+ *(it++) = *(first++);
+ }
+ }
+
+ /** Set the container equal to the bits in ilist. */
+ void assign(std::initializer_list<bool> ilist)
+ {
+ assign(ilist.size(), false);
+ auto it = begin();
+ auto init = ilist.begin();
+ while (init != ilist.end()) {
+ *(it++) = *(init++);
+ }
+ }
+
+ /** Set the container equal to the bits in ilist. */
+ bitdeque& operator=(std::initializer_list<bool> ilist)
+ {
+ assign(ilist);
+ return *this;
+ }
+
+ /** Construct a container containing the bits in [first,last). */
+ template<typename It>
+ bitdeque(It first, It last) { assign(first, last); }
+
+ /** Construct a container containing the bits in ilist. */
+ bitdeque(std::initializer_list<bool> ilist) { assign(ilist); }
+
+ // Access an element of the container, with bounds checking.
+ reference at(size_type position)
+ {
+ if (position >= size()) throw std::out_of_range("bitdeque::at() out of range");
+ return begin()[position];
+ }
+ const_reference at(size_type position) const
+ {
+ if (position >= size()) throw std::out_of_range("bitdeque::at() out of range");
+ return cbegin()[position];
+ }
+
+ // Access elements of the container without bounds checking.
+ reference operator[](size_type position) { return begin()[position]; }
+ const_reference operator[](size_type position) const { return cbegin()[position]; }
+ reference front() { return *begin(); }
+ const_reference front() const { return *cbegin(); }
+ reference back() { return end()[-1]; }
+ const_reference back() const { return cend()[-1]; }
+
+ /** Release unused memory. */
+ void shrink_to_fit()
+ {
+ m_deque.shrink_to_fit();
+ }
+
+ /** Empty the container. */
+ void clear() noexcept
+ {
+ m_deque.clear();
+ m_pad_begin = m_pad_end = 0;
+ }
+
+ // Append an element to the container.
+ void push_back(bool val)
+ {
+ extend_back(1);
+ back() = val;
+ }
+ reference emplace_back(bool val)
+ {
+ extend_back(1);
+ auto ref = back();
+ ref = val;
+ return ref;
+ }
+
+ // Prepend an element to the container.
+ void push_front(bool val)
+ {
+ extend_front(1);
+ front() = val;
+ }
+ reference emplace_front(bool val)
+ {
+ extend_front(1);
+ auto ref = front();
+ ref = val;
+ return ref;
+ }
+
+ // Remove the last element from the container.
+ void pop_back()
+ {
+ erase_back(1);
+ }
+
+ // Remove the first element from the container.
+ void pop_front()
+ {
+ erase_front(1);
+ }
+
+ /** Resize the container. */
+ void resize(size_type n)
+ {
+ if (n < size()) {
+ erase_back(size() - n);
+ } else {
+ extend_back(n - size());
+ }
+ }
+
+ // Swap two containers.
+ void swap(bitdeque& other) noexcept
+ {
+ std::swap(m_deque, other.m_deque);
+ std::swap(m_pad_begin, other.m_pad_begin);
+ std::swap(m_pad_end, other.m_pad_end);
+ }
+ friend void swap(bitdeque& b1, bitdeque& b2) noexcept { b1.swap(b2); }
+
+ // Erase elements from the container.
+ iterator erase(const_iterator first, const_iterator last)
+ {
+ size_type before = std::distance(cbegin(), first);
+ size_type dist = std::distance(first, last);
+ size_type after = std::distance(last, cend());
+ if (before < after) {
+ std::move_backward(begin(), begin() + before, end() - after);
+ erase_front(dist);
+ return begin() + before;
+ } else {
+ std::move(end() - after, end(), begin() + before);
+ erase_back(dist);
+ return end() - after;
+ }
+ }
+
+ iterator erase(iterator first, iterator last) { return erase(const_iterator{first}, const_iterator{last}); }
+ iterator erase(const_iterator pos) { return erase(pos, pos + 1); }
+ iterator erase(iterator pos) { return erase(const_iterator{pos}, const_iterator{pos} + 1); }
+
+ // Insert elements into the container.
+ iterator insert(const_iterator pos, bool val)
+ {
+ size_type before = pos - cbegin();
+ insert_zeroes(before, 1);
+ auto it = begin() + before;
+ *it = val;
+ return it;
+ }
+
+ iterator emplace(const_iterator pos, bool val) { return insert(pos, val); }
+
+ iterator insert(const_iterator pos, size_type count, bool val)
+ {
+ size_type before = pos - cbegin();
+ insert_zeroes(before, count);
+ auto it_begin = begin() + before;
+ auto it = it_begin;
+ auto it_end = it + count;
+ while (it != it_end) *(it++) = val;
+ return it_begin;
+ }
+
+ template<typename It>
+ iterator insert(const_iterator pos, It first, It last)
+ {
+ size_type before = pos - cbegin();
+ size_type count = std::distance(first, last);
+ insert_zeroes(before, count);
+ auto it_begin = begin() + before;
+ auto it = it_begin;
+ while (first != last) {
+ *(it++) = *(first++);
+ }
+ return it_begin;
+ }
+};
+
+#endif // BITCOIN_UTIL_BITDEQUE_H
diff --git a/src/util/error.cpp b/src/util/error.cpp
index 33a35a6d59..390cb6c11b 100644
--- a/src/util/error.cpp
+++ b/src/util/error.cpp
@@ -49,6 +49,11 @@ bilingual_str ResolveErrMsg(const std::string& optname, const std::string& strBi
return strprintf(_("Cannot resolve -%s address: '%s'"), optname, strBind);
}
+bilingual_str InvalidPortErrMsg(const std::string& optname, const std::string& invalid_value)
+{
+ return strprintf(_("Invalid port specified in %s: '%s'"), optname, invalid_value);
+}
+
bilingual_str AmountHighWarn(const std::string& optname)
{
return strprintf(_("%s is set very high!"), optname);
diff --git a/src/util/error.h b/src/util/error.h
index 0429de651a..27916501f0 100644
--- a/src/util/error.h
+++ b/src/util/error.h
@@ -39,6 +39,8 @@ bilingual_str TransactionErrorString(const TransactionError error);
bilingual_str ResolveErrMsg(const std::string& optname, const std::string& strBind);
+bilingual_str InvalidPortErrMsg(const std::string& optname, const std::string& strPort);
+
bilingual_str AmountHighWarn(const std::string& optname);
bilingual_str AmountErrMsg(const std::string& optname, const std::string& strValue);
diff --git a/src/util/message.cpp b/src/util/message.cpp
index 028251a5a8..7d6f3403f4 100644
--- a/src/util/message.cpp
+++ b/src/util/message.cpp
@@ -8,7 +8,6 @@
#include <key_io.h>
#include <pubkey.h>
#include <script/standard.h>
-#include <serialize.h>
#include <uint256.h>
#include <util/message.h>
#include <util/strencodings.h>
diff --git a/src/util/result.h b/src/util/result.h
index 2f586a4c9b..972b1aada0 100644
--- a/src/util/result.h
+++ b/src/util/result.h
@@ -5,45 +5,80 @@
#ifndef BITCOIN_UTIL_RESULT_H
#define BITCOIN_UTIL_RESULT_H
+#include <attributes.h>
#include <util/translation.h>
#include <variant>
-/*
- * 'BResult' is a generic class useful for wrapping a return object
- * (in case of success) or propagating the error cause.
-*/
-template<class T>
-class BResult {
+namespace util {
+
+struct Error {
+ bilingual_str message;
+};
+
+//! The util::Result class provides a standard way for functions to return
+//! either error messages or result values.
+//!
+//! It is intended for high-level functions that need to report error strings to
+//! end users. Lower-level functions that don't need this error-reporting and
+//! only need error-handling should avoid util::Result and instead use standard
+//! classes like std::optional, std::variant, and std::tuple, or custom structs
+//! and enum types to return function results.
+//!
+//! Usage examples can be found in \example ../test/result_tests.cpp, but in
+//! general code returning `util::Result<T>` values is very similar to code
+//! returning `std::optional<T>` values. Existing functions returning
+//! `std::optional<T>` can be updated to return `util::Result<T>` and return
+//! error strings usually just replacing `return std::nullopt;` with `return
+//! util::Error{error_string};`.
+template <class T>
+class Result
+{
private:
std::variant<bilingual_str, T> m_variant;
-public:
- BResult() : m_variant{Untranslated("")} {}
- BResult(T obj) : m_variant{std::move(obj)} {}
- BResult(bilingual_str error) : m_variant{std::move(error)} {}
+ template <typename FT>
+ friend bilingual_str ErrorString(const Result<FT>& result);
- /* Whether the function succeeded or not */
- bool HasRes() const { return std::holds_alternative<T>(m_variant); }
+public:
+ Result(T obj) : m_variant{std::in_place_index_t<1>{}, std::move(obj)} {}
+ Result(Error error) : m_variant{std::in_place_index_t<0>{}, std::move(error.message)} {}
- /* In case of success, the result object */
- const T& GetObj() const {
- assert(HasRes());
- return std::get<T>(m_variant);
+ //! std::optional methods, so functions returning optional<T> can change to
+ //! return Result<T> with minimal changes to existing code, and vice versa.
+ bool has_value() const noexcept { return m_variant.index() == 1; }
+ const T& value() const LIFETIMEBOUND
+ {
+ assert(has_value());
+ return std::get<1>(m_variant);
}
- T ReleaseObj()
+ T& value() LIFETIMEBOUND
{
- assert(HasRes());
- return std::move(std::get<T>(m_variant));
+ assert(has_value());
+ return std::get<1>(m_variant);
}
-
- /* In case of failure, the error cause */
- const bilingual_str& GetError() const {
- assert(!HasRes());
- return std::get<bilingual_str>(m_variant);
+ template <class U>
+ T value_or(U&& default_value) const&
+ {
+ return has_value() ? value() : std::forward<U>(default_value);
}
-
- explicit operator bool() const { return HasRes(); }
+ template <class U>
+ T value_or(U&& default_value) &&
+ {
+ return has_value() ? std::move(value()) : std::forward<U>(default_value);
+ }
+ explicit operator bool() const noexcept { return has_value(); }
+ const T* operator->() const LIFETIMEBOUND { return &value(); }
+ const T& operator*() const LIFETIMEBOUND { return value(); }
+ T* operator->() LIFETIMEBOUND { return &value(); }
+ T& operator*() LIFETIMEBOUND { return value(); }
};
+template <typename T>
+bilingual_str ErrorString(const Result<T>& result)
+{
+ return result ? bilingual_str{} : std::get<0>(result.m_variant);
+}
+} // namespace util
+
#endif // BITCOIN_UTIL_RESULT_H
diff --git a/src/util/sock.cpp b/src/util/sock.cpp
index 125dbc7f18..84ac2759fa 100644
--- a/src/util/sock.cpp
+++ b/src/util/sock.cpp
@@ -117,6 +117,34 @@ int Sock::GetSockName(sockaddr* name, socklen_t* name_len) const
return getsockname(m_socket, name, name_len);
}
+bool Sock::SetNonBlocking() const
+{
+#ifdef WIN32
+ u_long on{1};
+ if (ioctlsocket(m_socket, FIONBIO, &on) == SOCKET_ERROR) {
+ return false;
+ }
+#else
+ const int flags{fcntl(m_socket, F_GETFL, 0)};
+ if (flags == SOCKET_ERROR) {
+ return false;
+ }
+ if (fcntl(m_socket, F_SETFL, flags | O_NONBLOCK) == SOCKET_ERROR) {
+ return false;
+ }
+#endif
+ return true;
+}
+
+bool Sock::IsSelectable() const
+{
+#if defined(USE_POLL) || defined(WIN32)
+ return true;
+#else
+ return m_socket < FD_SETSIZE;
+#endif
+}
+
bool Sock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const
{
// We need a `shared_ptr` owning `this` for `WaitMany()`, but don't want
@@ -185,10 +213,10 @@ bool Sock::WaitMany(std::chrono::milliseconds timeout, EventsPerSock& events_per
SOCKET socket_max{0};
for (const auto& [sock, events] : events_per_sock) {
- const auto& s = sock->m_socket;
- if (!IsSelectableSocket(s)) {
+ if (!sock->IsSelectable()) {
return false;
}
+ const auto& s = sock->m_socket;
if (events.requested & RECV) {
FD_SET(s, &recv);
}
diff --git a/src/util/sock.h b/src/util/sock.h
index 38a7dc80d6..7912284904 100644
--- a/src/util/sock.h
+++ b/src/util/sock.h
@@ -133,6 +133,18 @@ public:
*/
[[nodiscard]] virtual int GetSockName(sockaddr* name, socklen_t* name_len) const;
+ /**
+ * Set the non-blocking option on the socket.
+ * @return true if set successfully
+ */
+ [[nodiscard]] virtual bool SetNonBlocking() const;
+
+ /**
+ * Check if the underlying socket can be used for `select(2)` (or the `Wait()` method).
+ * @return true if selectable
+ */
+ [[nodiscard]] virtual bool IsSelectable() const;
+
using Event = uint8_t;
/**
diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp
index 303e19beec..e28ca8e73a 100644
--- a/src/util/strencodings.cpp
+++ b/src/util/strencodings.cpp
@@ -6,7 +6,6 @@
#include <span.h>
#include <util/strencodings.h>
-#include <algorithm>
#include <array>
#include <cassert>
#include <cstring>
@@ -98,8 +97,9 @@ std::vector<Byte> ParseHex(std::string_view str)
template std::vector<std::byte> ParseHex(std::string_view);
template std::vector<uint8_t> ParseHex(std::string_view);
-void SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut)
+bool SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut)
{
+ bool valid = false;
size_t colon = in.find_last_of(':');
// if a : is found, and it either follows a [...], or no other : is in the string, treat it as port separator
bool fHaveColon = colon != in.npos;
@@ -110,13 +110,18 @@ void SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut)
if (ParseUInt16(in.substr(colon + 1), &n)) {
in = in.substr(0, colon);
portOut = n;
+ valid = (portOut != 0);
}
+ } else {
+ valid = true;
}
if (in.size() > 0 && in[0] == '[' && in[in.size() - 1] == ']') {
hostOut = in.substr(1, in.size() - 2);
} else {
hostOut = in;
}
+
+ return valid;
}
std::string EncodeBase64(Span<const unsigned char> input)
diff --git a/src/util/strencodings.h b/src/util/strencodings.h
index 14867b21b2..94bc6cc2f3 100644
--- a/src/util/strencodings.h
+++ b/src/util/strencodings.h
@@ -88,7 +88,16 @@ std::string EncodeBase32(Span<const unsigned char> input, bool pad = true);
*/
std::string EncodeBase32(std::string_view str, bool pad = true);
-void SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut);
+/**
+ * Splits socket address string into host string and port value.
+ * Validates port value.
+ *
+ * @param[in] in The socket address string to split.
+ * @param[out] portOut Port-portion of the input, if found and parsable.
+ * @param[out] hostOut Host-portion of the input, if found.
+ * @return true if port-portion is absent or within its allowed range, otherwise false
+ */
+bool SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut);
// LocaleIndependentAtoi is provided for backwards compatibility reasons.
//
diff --git a/src/util/string.cpp b/src/util/string.cpp
index dff782c330..e994c85f1c 100644
--- a/src/util/string.cpp
+++ b/src/util/string.cpp
@@ -4,11 +4,11 @@
#include <util/string.h>
-#include <boost/algorithm/string/replace.hpp>
-
+#include <regex>
#include <string>
-void ReplaceAll(std::string& in_out, std::string_view search, std::string_view substitute)
+void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute)
{
- boost::replace_all(in_out, search, substitute);
+ if (search.empty()) return;
+ in_out = std::regex_replace(in_out, std::regex(search), substitute);
}
diff --git a/src/util/string.h b/src/util/string.h
index df20e34ae9..9b4c9a7e28 100644
--- a/src/util/string.h
+++ b/src/util/string.h
@@ -7,7 +7,6 @@
#include <util/spanparsing.h>
-#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
@@ -17,7 +16,7 @@
#include <string_view>
#include <vector>
-void ReplaceAll(std::string& in_out, std::string_view search, std::string_view substitute);
+void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute);
[[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, char sep)
{
@@ -58,34 +57,30 @@ void ReplaceAll(std::string& in_out, std::string_view search, std::string_view s
}
/**
- * Join a list of items
+ * Join all container items. Typically used to concatenate strings but accepts
+ * containers with elements of any type.
*
- * @param list The list to join
- * @param separator The separator
- * @param unary_op Apply this operator to each item in the list
+ * @param container The items to join
+ * @param separator The separator
+ * @param unary_op Apply this operator to each item
*/
-template <typename T, typename BaseType, typename UnaryOp>
-auto Join(const std::vector<T>& list, const BaseType& separator, UnaryOp unary_op)
- -> decltype(unary_op(list.at(0)))
+template <typename C, typename S, typename UnaryOp>
+auto Join(const C& container, const S& separator, UnaryOp unary_op)
{
- decltype(unary_op(list.at(0))) ret;
- for (size_t i = 0; i < list.size(); ++i) {
- if (i > 0) ret += separator;
- ret += unary_op(list.at(i));
+ decltype(unary_op(*container.begin())) ret;
+ bool first{true};
+ for (const auto& item : container) {
+ if (!first) ret += separator;
+ ret += unary_op(item);
+ first = false;
}
return ret;
}
-template <typename T, typename T2>
-T Join(const std::vector<T>& list, const T2& separator)
+template <typename C, typename S>
+auto Join(const C& container, const S& separator)
{
- return Join(list, separator, [](const T& i) { return i; });
-}
-
-// Explicit overload needed for c_str arguments, which would otherwise cause a substitution failure in the template above.
-inline std::string Join(const std::vector<std::string>& list, std::string_view separator)
-{
- return Join<std::string>(list, separator);
+ return Join(container, separator, [](const auto& i) { return i; });
}
/**
diff --git a/src/util/system.cpp b/src/util/system.cpp
index ce45fb2ed4..d7a0793ea8 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -5,19 +5,6 @@
#include <util/system.h>
-#ifdef ENABLE_EXTERNAL_SIGNER
-#if defined(__GNUC__)
-// Boost 1.78 requires the following workaround.
-// See: https://github.com/boostorg/process/issues/235
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wnarrowing"
-#endif
-#include <boost/process.hpp>
-#if defined(__GNUC__)
-#pragma GCC diagnostic pop
-#endif
-#endif // ENABLE_EXTERNAL_SIGNER
-
#include <chainparamsbase.h>
#include <fs.h>
#include <sync.h>
@@ -522,7 +509,7 @@ bool ArgsManager::InitSettings(std::string& error)
bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp, bool backup) const
{
- fs::path settings = GetPathArg("-settings", fs::path{BITCOIN_SETTINGS_FILENAME});
+ fs::path settings = GetPathArg("-settings", BITCOIN_SETTINGS_FILENAME);
if (settings.empty()) {
return false;
}
@@ -831,7 +818,7 @@ std::string HelpMessageOpt(const std::string &option, const std::string &message
std::string("\n\n");
}
-static std::string FormatException(const std::exception* pex, const char* pszThread)
+static std::string FormatException(const std::exception* pex, std::string_view thread_name)
{
#ifdef WIN32
char pszModule[MAX_PATH] = "";
@@ -841,15 +828,15 @@ static std::string FormatException(const std::exception* pex, const char* pszThr
#endif
if (pex)
return strprintf(
- "EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, pszThread);
+ "EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, thread_name);
else
return strprintf(
- "UNKNOWN EXCEPTION \n%s in %s \n", pszModule, pszThread);
+ "UNKNOWN EXCEPTION \n%s in %s \n", pszModule, thread_name);
}
-void PrintExceptionContinue(const std::exception* pex, const char* pszThread)
+void PrintExceptionContinue(const std::exception* pex, std::string_view thread_name)
{
- std::string message = FormatException(pex, pszThread);
+ std::string message = FormatException(pex, thread_name);
LogPrintf("\n\n************************\n%s\n", message);
tfm::format(std::cerr, "\n\n************************\n%s\n", message);
}
@@ -885,9 +872,9 @@ bool CheckDataDirOption()
return datadir.empty() || fs::is_directory(fs::absolute(datadir));
}
-fs::path GetConfigFile(const std::string& confPath)
+fs::path GetConfigFile(const fs::path& configuration_file_path)
{
- return AbsPathForConfigVal(fs::PathFromString(confPath), false);
+ return AbsPathForConfigVal(configuration_file_path, /*net_specific=*/false);
}
static bool GetConfigOptions(std::istream& stream, const std::string& filepath, std::string& error, std::vector<std::pair<std::string, std::string>>& options, std::list<SectionInfo>& sections)
@@ -935,6 +922,20 @@ static bool GetConfigOptions(std::istream& stream, const std::string& filepath,
return true;
}
+bool IsConfSupported(KeyInfo& key, std::string& error) {
+ if (key.name == "conf") {
+ error = "conf cannot be set in the configuration file; use includeconf= if you want to include additional config files";
+ return false;
+ }
+ if (key.name == "reindex") {
+ // reindex can be set in a config file but it is strongly discouraged as this will cause the node to reindex on
+ // every restart. Allow the config but throw a warning
+ LogPrintf("Warning: reindex=1 is set in the configuration file, which will significantly slow down startup. Consider removing or commenting out this option for better performance, unless there is currently a condition which makes rebuilding the indexes necessary\n");
+ return true;
+ }
+ return true;
+}
+
bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& filepath, std::string& error, bool ignore_invalid_keys)
{
LOCK(cs_args);
@@ -945,6 +946,7 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file
for (const std::pair<std::string, std::string>& option : options) {
KeyInfo key = InterpretKey(option.first);
std::optional<unsigned int> flags = GetArgFlags('-' + key.name);
+ if (!IsConfSupported(key, error)) return false;
if (flags) {
std::optional<util::SettingsValue> value = InterpretValue(key, &option.second, *flags, error);
if (!value) {
@@ -971,17 +973,17 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
m_config_sections.clear();
}
- const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME);
- std::ifstream stream{GetConfigFile(confPath)};
+ const fs::path conf_path = GetPathArg("-conf", BITCOIN_CONF_FILENAME);
+ std::ifstream stream{GetConfigFile(conf_path)};
// not ok to have a config file specified that cannot be opened
if (IsArgSet("-conf") && !stream.good()) {
- error = strprintf("specified config file \"%s\" could not be opened.", confPath);
+ error = strprintf("specified config file \"%s\" could not be opened.", fs::PathToString(conf_path));
return false;
}
// ok to not have a config file
if (stream.good()) {
- if (!ReadConfigStream(stream, confPath, error, ignore_invalid_keys)) {
+ if (!ReadConfigStream(stream, fs::PathToString(conf_path), error, ignore_invalid_keys)) {
return false;
}
// `-includeconf` cannot be included in the command line arguments except
@@ -1019,7 +1021,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
const size_t default_includes = add_includes({});
for (const std::string& conf_file_name : conf_file_names) {
- std::ifstream conf_file_stream{GetConfigFile(conf_file_name)};
+ std::ifstream conf_file_stream{GetConfigFile(fs::PathFromString(conf_file_name))};
if (conf_file_stream.good()) {
if (!ReadConfigStream(conf_file_stream, conf_file_name, error, ignore_invalid_keys)) {
return false;
@@ -1332,44 +1334,6 @@ void runCommand(const std::string& strCommand)
}
#endif
-UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in)
-{
-#ifdef ENABLE_EXTERNAL_SIGNER
- namespace bp = boost::process;
-
- UniValue result_json;
- bp::opstream stdin_stream;
- bp::ipstream stdout_stream;
- bp::ipstream stderr_stream;
-
- if (str_command.empty()) return UniValue::VNULL;
-
- bp::child c(
- str_command,
- bp::std_out > stdout_stream,
- bp::std_err > stderr_stream,
- bp::std_in < stdin_stream
- );
- if (!str_std_in.empty()) {
- stdin_stream << str_std_in << std::endl;
- }
- stdin_stream.pipe().close();
-
- std::string result;
- std::string error;
- std::getline(stdout_stream, result);
- std::getline(stderr_stream, error);
-
- c.wait();
- const int n_error = c.exit_code();
- if (n_error) throw std::runtime_error(strprintf("RunCommandParseJSON error: process(%s) returned %d: %s\n", str_command, n_error, error));
- if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result);
-
- return result_json;
-#else
- throw std::runtime_error("Compiled without external signing support (required for external signing).");
-#endif // ENABLE_EXTERNAL_SIGNER
-}
void SetupEnvironment()
{
diff --git a/src/util/system.h b/src/util/system.h
index fa03e88920..29629e547e 100644
--- a/src/util/system.h
+++ b/src/util/system.h
@@ -51,7 +51,7 @@ bool error(const char* fmt, const Args&... args)
return false;
}
-void PrintExceptionContinue(const std::exception *pex, const char* pszThread);
+void PrintExceptionContinue(const std::exception* pex, std::string_view thread_name);
/**
* Ensure file contents are fully committed to disk, using a platform-specific
@@ -97,7 +97,7 @@ bool TryCreateDirectories(const fs::path& p);
fs::path GetDefaultDataDir();
// Return true if -datadir option points to a valid directory or is not specified.
bool CheckDataDirOption();
-fs::path GetConfigFile(const std::string& confPath);
+fs::path GetConfigFile(const fs::path& configuration_file_path);
#ifdef WIN32
fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
#endif
@@ -107,14 +107,6 @@ std::string ShellEscape(const std::string& arg);
#if HAVE_SYSTEM
void runCommand(const std::string& strCommand);
#endif
-/**
- * Execute a command which returns JSON, and parse the result.
- *
- * @param str_command The command to execute, including any arguments
- * @param str_std_in string to pass to stdin
- * @return parsed JSON
- */
-UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in="");
/**
* Most paths passed as configuration arguments are treated as relative to
diff --git a/src/util/thread.cpp b/src/util/thread.cpp
index f9f427ba20..ae98abdb3d 100644
--- a/src/util/thread.cpp
+++ b/src/util/thread.cpp
@@ -10,10 +10,12 @@
#include <exception>
#include <functional>
+#include <string>
+#include <utility>
-void util::TraceThread(const char* thread_name, std::function<void()> thread_func)
+void util::TraceThread(std::string_view thread_name, std::function<void()> thread_func)
{
- util::ThreadRename(thread_name);
+ util::ThreadRename(std::string{thread_name});
try {
LogPrintf("%s thread start\n", thread_name);
thread_func();
diff --git a/src/util/thread.h b/src/util/thread.h
index ca2eccc0c3..b80bf046a0 100644
--- a/src/util/thread.h
+++ b/src/util/thread.h
@@ -6,12 +6,13 @@
#define BITCOIN_UTIL_THREAD_H
#include <functional>
+#include <string>
namespace util {
/**
* A wrapper for do-something-once thread functions.
*/
-void TraceThread(const char* thread_name, std::function<void()> thread_func);
+void TraceThread(std::string_view thread_name, std::function<void()> thread_func);
} // namespace util
diff --git a/src/util/time.cpp b/src/util/time.cpp
index f6d37347f8..0b20849079 100644
--- a/src/util/time.cpp
+++ b/src/util/time.cpp
@@ -12,8 +12,6 @@
#include <util/time.h>
#include <util/check.h>
-#include <boost/date_time/posix_time/posix_time.hpp>
-
#include <atomic>
#include <chrono>
#include <ctime>
@@ -142,20 +140,6 @@ std::string FormatISO8601Date(int64_t nTime) {
return strprintf("%04i-%02i-%02i", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday);
}
-int64_t ParseISO8601DateTime(const std::string& str)
-{
- static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0);
- static const std::locale loc(std::locale::classic(),
- new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ"));
- std::istringstream iss(str);
- iss.imbue(loc);
- boost::posix_time::ptime ptime(boost::date_time::not_a_date_time);
- iss >> ptime;
- if (ptime.is_not_a_date_time() || epoch > ptime)
- return 0;
- return (ptime - epoch).total_seconds();
-}
-
struct timeval MillisToTimeval(int64_t nTimeout)
{
struct timeval timeout;
diff --git a/src/util/time.h b/src/util/time.h
index 4f9bde5d56..10a581a44c 100644
--- a/src/util/time.h
+++ b/src/util/time.h
@@ -57,6 +57,7 @@ constexpr int64_t count_microseconds(std::chrono::microseconds t) { return t.cou
using HoursDouble = std::chrono::duration<double, std::chrono::hours::period>;
using SecondsDouble = std::chrono::duration<double, std::chrono::seconds::period>;
+using MillisecondsDouble = std::chrono::duration<double, std::chrono::milliseconds::period>;
/**
* DEPRECATED
@@ -109,7 +110,6 @@ T GetTime()
*/
std::string FormatISO8601DateTime(int64_t nTime);
std::string FormatISO8601Date(int64_t nTime);
-int64_t ParseISO8601DateTime(const std::string& str);
/**
* Convert milliseconds to a struct timeval for e.g. select.
diff --git a/src/validation.cpp b/src/validation.cpp
index 17211956f5..debdc2ae74 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -82,9 +82,6 @@ using node::SnapshotMetadata;
using node::UndoReadFromDisk;
using node::UnlinkPrunedFiles;
-#define MICRO 0.000001
-#define MILLI 0.001
-
/** Maximum kilobytes for transactions to store for processing during reorg */
static const unsigned int MAX_DISCONNECTED_TX_POOL_SIZE = 20000;
/** Time to wait between writing blocks/block index to disk. */
@@ -123,16 +120,8 @@ RecursiveMutex cs_main;
GlobalMutex g_best_block_mutex;
std::condition_variable g_best_block_cv;
uint256 g_best_block;
-bool g_parallel_script_checks{false};
-bool fRequireStandard = true;
-bool fCheckBlockIndex = false;
-bool fCheckpointsEnabled = DEFAULT_CHECKPOINTS_ENABLED;
-int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE;
-
-uint256 hashAssumeValid;
-arith_uint256 nMinimumChainWork;
-const CBlockIndex* CChainState::FindForkInGlobalIndex(const CBlockLocator& locator) const
+const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locator) const
{
AssertLockHeld(cs_main);
@@ -158,10 +147,9 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
std::vector<CScriptCheck>* pvChecks = nullptr)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
-bool CheckFinalTxAtTip(const CBlockIndex* active_chain_tip, const CTransaction& tx)
+bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx)
{
AssertLockHeld(cs_main);
- assert(active_chain_tip); // TODO: Make active_chain_tip a reference
// CheckFinalTxAtTip() uses active_chain_tip.Height()+1 to evaluate
// nLockTime because when IsFinalTx() is called within
@@ -169,14 +157,14 @@ bool CheckFinalTxAtTip(const CBlockIndex* active_chain_tip, const CTransaction&
// evaluated is what is used. Thus if we want to know if a
// transaction can be part of the *next* block, we need to call
// IsFinalTx() with one more than active_chain_tip.Height().
- const int nBlockHeight = active_chain_tip->nHeight + 1;
+ const int nBlockHeight = active_chain_tip.nHeight + 1;
// BIP113 requires that time-locked transactions have nLockTime set to
// less than the median time of the previous block they're contained in.
// When the next block is created its previous block will be the current
// chain tip, so we use that to calculate the median time passed to
// IsFinalTx().
- const int64_t nBlockTime{active_chain_tip->GetMedianTimePast()};
+ const int64_t nBlockTime{active_chain_tip.GetMedianTimePast()};
return IsFinalTx(tx, nBlockHeight, nBlockTime);
}
@@ -275,7 +263,7 @@ static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache)
coins_cache.Uncache(removed);
}
-static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+static bool IsCurrentForFeeEstimation(Chainstate& active_chainstate) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
AssertLockHeld(cs_main);
if (active_chainstate.IsInitialBlockDownload())
@@ -288,7 +276,7 @@ static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_
return true;
}
-void CChainState::MaybeUpdateMempoolForReorg(
+void Chainstate::MaybeUpdateMempoolForReorg(
DisconnectedBlockTransactions& disconnectpool,
bool fAddToMempool)
{
@@ -338,7 +326,7 @@ void CChainState::MaybeUpdateMempoolForReorg(
const CTransaction& tx = it->GetTx();
// The transaction must be final.
- if (!CheckFinalTxAtTip(m_chain.Tip(), tx)) return true;
+ if (!CheckFinalTxAtTip(*Assert(m_chain.Tip()), tx)) return true;
LockPoints lp = it->GetLockPoints();
const bool validLP{TestLockPointValidity(m_chain, lp)};
CCoinsViewMemPool view_mempool(&CoinsTip(), *m_mempool);
@@ -426,11 +414,13 @@ namespace {
class MemPoolAccept
{
public:
- explicit MemPoolAccept(CTxMemPool& mempool, CChainState& active_chainstate) : m_pool(mempool), m_view(&m_dummy), m_viewmempool(&active_chainstate.CoinsTip(), m_pool), m_active_chainstate(active_chainstate),
- m_limit_ancestors(m_pool.m_limits.ancestor_count),
- m_limit_ancestor_size(m_pool.m_limits.ancestor_size_vbytes),
- m_limit_descendants(m_pool.m_limits.descendant_count),
- m_limit_descendant_size(m_pool.m_limits.descendant_size_vbytes) {
+ explicit MemPoolAccept(CTxMemPool& mempool, Chainstate& active_chainstate) :
+ m_pool(mempool),
+ m_view(&m_dummy),
+ m_viewmempool(&active_chainstate.CoinsTip(), m_pool),
+ m_active_chainstate(active_chainstate),
+ m_limits{m_pool.m_limits}
+ {
}
// We put the arguments we're handed into a struct, so we can pass them
@@ -451,7 +441,7 @@ public:
/** Whether we allow transactions to replace mempool transactions by BIP125 rules. If false,
* any transaction spending the same inputs as a transaction in the mempool is considered
* a conflict. */
- const bool m_allow_bip125_replacement;
+ const bool m_allow_replacement;
/** When true, the mempool will not be trimmed when individual transactions are submitted in
* Finalize(). Instead, limits should be enforced at the end to ensure the package is not
* partially submitted.
@@ -471,7 +461,7 @@ public:
/* m_bypass_limits */ bypass_limits,
/* m_coins_to_uncache */ coins_to_uncache,
/* m_test_accept */ test_accept,
- /* m_allow_bip125_replacement */ true,
+ /* m_allow_replacement */ true,
/* m_package_submission */ false,
/* m_package_feerates */ false,
};
@@ -485,7 +475,7 @@ public:
/* m_bypass_limits */ false,
/* m_coins_to_uncache */ coins_to_uncache,
/* m_test_accept */ true,
- /* m_allow_bip125_replacement */ false,
+ /* m_allow_replacement */ false,
/* m_package_submission */ false, // not submitting to mempool
/* m_package_feerates */ false,
};
@@ -499,7 +489,7 @@ public:
/* m_bypass_limits */ false,
/* m_coins_to_uncache */ coins_to_uncache,
/* m_test_accept */ false,
- /* m_allow_bip125_replacement */ false,
+ /* m_allow_replacement */ false,
/* m_package_submission */ true,
/* m_package_feerates */ true,
};
@@ -512,7 +502,7 @@ public:
/* m_bypass_limits */ false,
/* m_coins_to_uncache */ package_args.m_coins_to_uncache,
/* m_test_accept */ package_args.m_test_accept,
- /* m_allow_bip125_replacement */ true,
+ /* m_allow_replacement */ true,
/* m_package_submission */ false,
/* m_package_feerates */ false, // only 1 transaction
};
@@ -526,7 +516,7 @@ public:
bool bypass_limits,
std::vector<COutPoint>& coins_to_uncache,
bool test_accept,
- bool allow_bip125_replacement,
+ bool allow_replacement,
bool package_submission,
bool package_feerates)
: m_chainparams{chainparams},
@@ -534,7 +524,7 @@ public:
m_bypass_limits{bypass_limits},
m_coins_to_uncache{coins_to_uncache},
m_test_accept{test_accept},
- m_allow_bip125_replacement{allow_bip125_replacement},
+ m_allow_replacement{allow_replacement},
m_package_submission{package_submission},
m_package_feerates{package_feerates}
{
@@ -647,8 +637,9 @@ private:
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool min fee not met", strprintf("%d < %d", package_fee, mempoolRejectFee));
}
- if (package_fee < ::minRelayTxFee.GetFee(package_size)) {
- return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met", strprintf("%d < %d", package_fee, ::minRelayTxFee.GetFee(package_size)));
+ if (package_fee < m_pool.m_min_relay_feerate.GetFee(package_size)) {
+ return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met",
+ strprintf("%d < %d", package_fee, m_pool.m_min_relay_feerate.GetFee(package_size)));
}
return true;
}
@@ -659,15 +650,9 @@ private:
CCoinsViewMemPool m_viewmempool;
CCoinsView m_dummy;
- CChainState& m_active_chainstate;
+ Chainstate& m_active_chainstate;
- // The package limits in effect at the time of invocation.
- const size_t m_limit_ancestors;
- const size_t m_limit_ancestor_size;
- // These may be modified while evaluating a transaction (eg to account for
- // in-mempool conflicts; see below).
- size_t m_limit_descendants;
- size_t m_limit_descendant_size;
+ CTxMemPool::Limits m_limits;
/** Whether the transaction(s) would replace any mempool transactions. If so, RBF rules apply. */
bool m_rbf{false};
@@ -700,8 +685,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Rather not work on nonstandard transactions (unless -testnet/-regtest)
std::string reason;
- if (fRequireStandard && !IsStandardTx(tx, reason))
+ if (m_pool.m_require_standard && !IsStandardTx(tx, m_pool.m_max_datacarrier_bytes, m_pool.m_permit_bare_multisig, m_pool.m_dust_relay_feerate, reason)) {
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason);
+ }
// Do not work on transactions that are too small.
// A transaction with 1 segwit input and 1 P2WPHK output has non-witness size of 82 bytes.
@@ -713,7 +699,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Only accept nLockTime-using transactions that can be mined in the next
// block; we don't want our mempool filled up with transactions that can't
// be mined yet.
- if (!CheckFinalTxAtTip(m_active_chainstate.m_chain.Tip(), tx)) {
+ if (!CheckFinalTxAtTip(*Assert(m_active_chainstate.m_chain.Tip()), tx)) {
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-final");
}
@@ -731,7 +717,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
{
const CTransaction* ptxConflicting = m_pool.GetConflictTx(txin.prevout);
if (ptxConflicting) {
- if (!args.m_allow_bip125_replacement) {
+ if (!args.m_allow_replacement) {
// Transaction conflicts with a mempool tx, but we're not allowing replacements.
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "bip125-replacement-disallowed");
}
@@ -807,13 +793,14 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return false; // state filled in by CheckTxInputs
}
- if (fRequireStandard && !AreInputsStandard(tx, m_view)) {
+ if (m_pool.m_require_standard && !AreInputsStandard(tx, m_view)) {
return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs");
}
// Check for non-standard witnesses.
- if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, m_view))
+ if (tx.HasWitness() && m_pool.m_require_standard && !IsWitnessStandard(tx, m_view)) {
return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard");
+ }
int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);
@@ -840,7 +827,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
strprintf("%d", nSigOpsCost));
- // No individual transactions are allowed below minRelayTxFee and mempool min fee except from
+ // No individual transactions are allowed below the min relay feerate and mempool min feerate except from
// disconnected blocks and transactions in a package. Package transactions will be checked using
// package feerate later.
if (!bypass_limits && !args.m_package_feerates && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false;
@@ -860,8 +847,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// 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.
+ // check is accomplished later, so we don't bother doing anything about it here, but if our
+ // policy changes, 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
@@ -878,12 +865,12 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
assert(ws.m_iters_conflicting.size() == 1);
CTxMemPool::txiter conflict = *ws.m_iters_conflicting.begin();
- m_limit_descendants += 1;
- m_limit_descendant_size += conflict->GetSizeWithDescendants();
+ m_limits.descendant_count += 1;
+ m_limits.descendant_size_vbytes += conflict->GetSizeWithDescendants();
}
std::string errString;
- if (!m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, m_limit_ancestors, m_limit_ancestor_size, m_limit_descendants, m_limit_descendant_size, errString)) {
+ if (!m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, m_limits, errString)) {
ws.m_ancestors.clear();
// If CalculateMemPoolAncestors fails second time, we want the original error string.
std::string dummy_err_string;
@@ -898,8 +885,16 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// 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
+ CTxMemPool::Limits cpfp_carve_out_limits{
+ .ancestor_count = 2,
+ .ancestor_size_vbytes = m_limits.ancestor_size_vbytes,
+ .descendant_count = m_limits.descendant_count + 1,
+ .descendant_size_vbytes = m_limits.descendant_size_vbytes + EXTRA_DESCENDANT_TX_SIZE_LIMIT,
+ };
if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT ||
- !m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, 2, m_limit_ancestor_size, m_limit_descendants + 1, m_limit_descendant_size + EXTRA_DESCENDANT_TX_SIZE_LIMIT, dummy_err_string)) {
+ !m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors,
+ cpfp_carve_out_limits,
+ dummy_err_string)) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", errString);
}
}
@@ -928,7 +923,7 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
TxValidationState& state = ws.m_state;
CFeeRate newFeeRate(ws.m_modified_fees, ws.m_vsize);
- // The replacement transaction must have a higher feerate than its direct conflicts.
+ // Enforce Rule #6. The replacement transaction must have a higher feerate than its direct conflicts.
// - The motivation for this check is to ensure that the replacement transaction is preferable for
// block-inclusion, compared to what would be removed from the mempool.
// - This logic predates ancestor feerate-based transaction selection, which is why it doesn't
@@ -941,24 +936,24 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", *err_string);
}
- // Calculate all conflicting entries and enforce BIP125 Rule #5.
+ // Calculate all conflicting entries and enforce Rule #5.
if (const auto err_string{GetEntriesForConflicts(tx, m_pool, ws.m_iters_conflicting, ws.m_all_conflicting)}) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
"too many potential replacements", *err_string);
}
- // Enforce BIP125 Rule #2.
+ // Enforce Rule #2.
if (const auto err_string{HasNoNewUnconfirmed(tx, m_pool, ws.m_iters_conflicting)}) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
"replacement-adds-unconfirmed", *err_string);
}
// Check if it's economically rational to mine this transaction rather than the ones it
- // replaces and pays for its own relay fees. Enforce BIP125 Rules #3 and #4.
+ // replaces and pays for its own relay fees. Enforce Rules #3 and #4.
for (CTxMemPool::txiter it : ws.m_all_conflicting) {
ws.m_conflicting_fees += it->GetModifiedFee();
ws.m_conflicting_size += it->GetTxSize();
}
if (const auto err_string{PaysForRBF(ws.m_conflicting_fees, ws.m_modified_fees, ws.m_vsize,
- ::incrementalRelayFee, hash)}) {
+ m_pool.m_incremental_relay_feerate, hash)}) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", *err_string);
}
return true;
@@ -975,8 +970,7 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn
{ return !m_pool.exists(GenTxid::Txid(tx->GetHash()));}));
std::string err_string;
- if (!m_pool.CheckPackageLimits(txns, m_limit_ancestors, m_limit_ancestor_size, m_limit_descendants,
- m_limit_descendant_size, err_string)) {
+ if (!m_pool.CheckPackageLimits(txns, m_limits, err_string)) {
// This is a package-wide error, separate from an individual transaction error.
return package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-mempool-limits", err_string);
}
@@ -1120,9 +1114,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
// Re-calculate mempool ancestors to call addUnchecked(). They may have changed since the
// last calculation done in PreChecks, since package ancestors have already been submitted.
std::string unused_err_string;
- if(!m_pool.CalculateMemPoolAncestors(*ws.m_entry, ws.m_ancestors, m_limit_ancestors,
- m_limit_ancestor_size, m_limit_descendants,
- m_limit_descendant_size, unused_err_string)) {
+ if(!m_pool.CalculateMemPoolAncestors(*ws.m_entry, ws.m_ancestors, m_limits, unused_err_string)) {
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
// Since PreChecks() and PackageMempoolChecks() both enforce limits, this should never fail.
Assume(false);
@@ -1223,7 +1215,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
// package to spend. Since we already checked conflicts in the package and we don't allow
// replacements, we don't need to track the coins spent. Note that this logic will need to be
// updated if package replace-by-fee is allowed in the future.
- assert(!args.m_allow_bip125_replacement);
+ assert(!args.m_allow_replacement);
m_viewmempool.PackageAddTransaction(ws.m_ptx);
}
@@ -1410,12 +1402,12 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
} // anon namespace
-MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, const CTransactionRef& tx,
+MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx,
int64_t accept_time, bool bypass_limits, bool test_accept)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
AssertLockHeld(::cs_main);
- const CChainParams& chainparams{active_chainstate.m_params};
+ const CChainParams& chainparams{active_chainstate.m_chainman.GetParams()};
assert(active_chainstate.GetMempool() != nullptr);
CTxMemPool& pool{*active_chainstate.GetMempool()};
@@ -1437,7 +1429,7 @@ MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, const CTr
return result;
}
-PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTxMemPool& pool,
+PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool,
const Package& package, bool test_accept)
{
AssertLockHeld(cs_main);
@@ -1445,7 +1437,7 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx
assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;}));
std::vector<COutPoint> coins_to_uncache;
- const CChainParams& chainparams = active_chainstate.m_params;
+ const CChainParams& chainparams = active_chainstate.m_chainman.GetParams();
const auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
AssertLockHeld(cs_main);
if (test_accept) {
@@ -1496,32 +1488,31 @@ void CoinsViews::InitCache()
m_cacheview = std::make_unique<CCoinsViewCache>(&m_catcherview);
}
-CChainState::CChainState(
+Chainstate::Chainstate(
CTxMemPool* mempool,
BlockManager& blockman,
ChainstateManager& chainman,
std::optional<uint256> from_snapshot_blockhash)
: m_mempool(mempool),
m_blockman(blockman),
- m_params(chainman.GetParams()),
m_chainman(chainman),
m_from_snapshot_blockhash(from_snapshot_blockhash) {}
-void CChainState::InitCoinsDB(
+void Chainstate::InitCoinsDB(
size_t cache_size_bytes,
bool in_memory,
bool should_wipe,
fs::path leveldb_name)
{
if (m_from_snapshot_blockhash) {
- leveldb_name += "_" + m_from_snapshot_blockhash->ToString();
+ leveldb_name += node::SNAPSHOT_CHAINSTATE_SUFFIX;
}
m_coins_views = std::make_unique<CoinsViews>(
leveldb_name, cache_size_bytes, in_memory, should_wipe);
}
-void CChainState::InitCoinsCache(size_t cache_size_bytes)
+void Chainstate::InitCoinsCache(size_t cache_size_bytes)
{
AssertLockHeld(::cs_main);
assert(m_coins_views != nullptr);
@@ -1531,10 +1522,10 @@ void CChainState::InitCoinsCache(size_t cache_size_bytes)
// 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*`)
+// `const` so that `CValidationInterface` clients (which are given a `const Chainstate*`)
// can call it.
//
-bool CChainState::IsInitialBlockDownload() const
+bool Chainstate::IsInitialBlockDownload() const
{
// Optimization: pre-test latch before taking the lock.
if (m_cached_finished_ibd.load(std::memory_order_relaxed))
@@ -1547,10 +1538,12 @@ bool CChainState::IsInitialBlockDownload() const
return true;
if (m_chain.Tip() == nullptr)
return true;
- if (m_chain.Tip()->nChainWork < nMinimumChainWork)
+ if (m_chain.Tip()->nChainWork < m_chainman.MinimumChainWork()) {
return true;
- if (m_chain.Tip()->GetBlockTime() < (GetTime() - nMaxTipAge))
+ }
+ if (m_chain.Tip()->Time() < NodeClock::now() - m_chainman.m_options.max_tip_age) {
return true;
+ }
LogPrintf("Leaving InitialBlockDownload (latching to false)\n");
m_cached_finished_ibd.store(true, std::memory_order_relaxed);
return false;
@@ -1576,7 +1569,7 @@ static void AlertNotify(const std::string& strMessage)
#endif
}
-void CChainState::CheckForkWarningConditions()
+void Chainstate::CheckForkWarningConditions()
{
AssertLockHeld(cs_main);
@@ -1595,7 +1588,7 @@ void CChainState::CheckForkWarningConditions()
}
// Called both upon regular invalid block discovery *and* InvalidateBlock
-void CChainState::InvalidChainFound(CBlockIndex* pindexNew)
+void Chainstate::InvalidChainFound(CBlockIndex* pindexNew)
{
AssertLockHeld(cs_main);
if (!m_chainman.m_best_invalid || pindexNew->nChainWork > m_chainman.m_best_invalid->nChainWork) {
@@ -1618,7 +1611,7 @@ void CChainState::InvalidChainFound(CBlockIndex* pindexNew)
// Same as InvalidChainFound, above, except not called directly from InvalidateBlock,
// which does its own setBlockIndexCandidates management.
-void CChainState::InvalidBlockFound(CBlockIndex* pindex, const BlockValidationState& state)
+void Chainstate::InvalidBlockFound(CBlockIndex* pindex, const BlockValidationState& state)
{
AssertLockHeld(cs_main);
if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
@@ -1654,7 +1647,8 @@ bool CScriptCheck::operator()() {
static CuckooCache::cache<uint256, SignatureCacheHasher> g_scriptExecutionCache;
static CSHA256 g_scriptExecutionCacheHasher;
-void InitScriptExecutionCache() {
+bool InitScriptExecutionCache(size_t max_size_bytes)
+{
// Setup the salted hasher
uint256 nonce = GetRandHash();
// We want the nonce to be 64 bytes long to force the hasher to process
@@ -1662,12 +1656,14 @@ void InitScriptExecutionCache() {
// just write our 32-byte entropy twice to fill the 64 bytes.
g_scriptExecutionCacheHasher.Write(nonce.begin(), 32);
g_scriptExecutionCacheHasher.Write(nonce.begin(), 32);
- // nMaxCacheSize is unsigned. If -maxsigcachesize is set to zero,
- // setup_bytes creates the minimum possible cache (2 elements).
- size_t nMaxCacheSize = std::min(std::max((int64_t)0, gArgs.GetIntArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE) / 2), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20);
- size_t nElems = g_scriptExecutionCache.setup_bytes(nMaxCacheSize);
- LogPrintf("Using %zu MiB out of %zu/2 requested for script execution cache, able to store %zu elements\n",
- (nElems*sizeof(uint256)) >>20, (nMaxCacheSize*2)>>20, nElems);
+
+ auto setup_results = g_scriptExecutionCache.setup_bytes(max_size_bytes);
+ if (!setup_results) return false;
+
+ const auto [num_elems, approx_size_bytes] = *setup_results;
+ LogPrintf("Using %zu MiB out of %zu MiB requested for script execution cache, able to store %zu elements\n",
+ approx_size_bytes >> 20, max_size_bytes >> 20, num_elems);
+ return true;
}
/**
@@ -1820,7 +1816,7 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out)
/** Undo the effects of this block (with given index) on the UTXO set represented by coins.
* When FAILED is returned, view is left in an indeterminate state. */
-DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view)
+DisconnectResult Chainstate::DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view)
{
AssertLockHeld(::cs_main);
bool fClean = true;
@@ -1836,11 +1832,21 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI
return DISCONNECT_FAILED;
}
+ // Ignore blocks that contain transactions which are 'overwritten' by later transactions,
+ // unless those are already completely spent.
+ // See https://github.com/bitcoin/bitcoin/issues/22596 for additional information.
+ // Note: the blocks specified here are different than the ones used in ConnectBlock because DisconnectBlock
+ // unwinds the blocks in reverse. As a result, the inconsistency is not discovered until the earlier
+ // blocks with the duplicate coinbase transactions are disconnected.
+ bool fEnforceBIP30 = !((pindex->nHeight==91722 && pindex->GetBlockHash() == uint256S("0x00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")) ||
+ (pindex->nHeight==91812 && pindex->GetBlockHash() == uint256S("0x00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f")));
+
// undo transactions in reverse order
for (int i = block.vtx.size() - 1; i >= 0; i--) {
const CTransaction &tx = *(block.vtx[i]);
uint256 hash = tx.GetHash();
bool is_coinbase = tx.IsCoinBase();
+ bool is_bip30_exception = (is_coinbase && !fEnforceBIP30);
// Check that all outputs are available and match the outputs in the block itself
// exactly.
@@ -1850,7 +1856,9 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI
Coin coin;
bool is_spent = view.SpendCoin(out, &coin);
if (!is_spent || tx.vout[o] != coin.out || pindex->nHeight != coin.nHeight || is_coinbase != coin.fCoinBase) {
- fClean = false; // transaction output mismatch
+ if (!is_bip30_exception) {
+ fClean = false; // transaction output mismatch
+ }
}
}
}
@@ -1961,19 +1969,19 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Ch
}
-static int64_t nTimeCheck = 0;
-static int64_t nTimeForks = 0;
-static int64_t nTimeConnect = 0;
-static int64_t nTimeVerify = 0;
-static int64_t nTimeUndo = 0;
-static int64_t nTimeIndex = 0;
-static int64_t nTimeTotal = 0;
-static int64_t nBlocksTotal = 0;
+static SteadyClock::duration time_check{};
+static SteadyClock::duration time_forks{};
+static SteadyClock::duration time_connect{};
+static SteadyClock::duration time_verify{};
+static SteadyClock::duration time_undo{};
+static SteadyClock::duration time_index{};
+static SteadyClock::duration time_total{};
+static int64_t num_blocks_total = 0;
/** Apply the effects of this block (with given index) on the UTXO set represented by coins.
* Validity checks that depend on the UTXO set are also done; ConnectBlock()
* can fail if those validity checks fail (among other reasons). */
-bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex,
+bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex,
CCoinsViewCache& view, bool fJustCheck)
{
AssertLockHeld(cs_main);
@@ -1981,8 +1989,10 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
uint256 block_hash{block.GetHash()};
assert(*pindex->phashBlock == block_hash);
+ const bool parallel_script_checks{scriptcheckqueue.HasThreads()};
- int64_t nTimeStart = GetTimeMicros();
+ const auto time_start{SteadyClock::now()};
+ const CChainParams& params{m_chainman.GetParams()};
// Check it again in case a previous version let a bad block in
// NOTE: We don't currently (re-)invoke ContextualCheckBlock() or
@@ -1997,7 +2007,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
// is enforced in ContextualCheckBlockHeader(); we wouldn't want to
// re-enforce that rule here (at least until we make it impossible for
// m_adjusted_time_callback() to go backward).
- if (!CheckBlock(block, state, m_params.GetConsensus(), !fJustCheck, !fJustCheck)) {
+ if (!CheckBlock(block, state, params.GetConsensus(), !fJustCheck, !fJustCheck)) {
if (state.GetResult() == BlockValidationResult::BLOCK_MUTATED) {
// We don't write down blocks to disk if they may have been
// corrupted, so this should be impossible unless we're having hardware
@@ -2011,28 +2021,28 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
uint256 hashPrevBlock = pindex->pprev == nullptr ? uint256() : pindex->pprev->GetBlockHash();
assert(hashPrevBlock == view.GetBestBlock());
- nBlocksTotal++;
+ num_blocks_total++;
// Special case for the genesis block, skipping connection of its transactions
// (its coinbase is unspendable)
- if (block_hash == m_params.GetConsensus().hashGenesisBlock) {
+ if (block_hash == params.GetConsensus().hashGenesisBlock) {
if (!fJustCheck)
view.SetBestBlock(pindex->GetBlockHash());
return true;
}
bool fScriptChecks = true;
- if (!hashAssumeValid.IsNull()) {
+ if (!m_chainman.AssumedValidBlock().IsNull()) {
// We've been configured with the hash of a block which has been externally verified to have a valid history.
// A suitable default value is included with the software and updated from time to time. Because validity
// 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 = m_blockman.m_block_index.find(hashAssumeValid);
+ BlockMap::const_iterator it{m_blockman.m_block_index.find(m_chainman.AssumedValidBlock())};
if (it != m_blockman.m_block_index.end()) {
if (it->second.GetAncestor(pindex->nHeight) == pindex &&
m_chainman.m_best_header->GetAncestor(pindex->nHeight) == pindex &&
- m_chainman.m_best_header->nChainWork >= nMinimumChainWork) {
+ m_chainman.m_best_header->nChainWork >= m_chainman.MinimumChainWork()) {
// 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
@@ -2045,15 +2055,19 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
// it hard to hide the implication of the demand. This also avoids having release candidates
// that are hardly doing any signature verification at all in testing without having to
// artificially set the default assumed verified block further back.
- // The test against nMinimumChainWork prevents the skipping when denied access to any chain at
+ // The test against the minimum chain work prevents the skipping when denied access to any chain at
// least as good as the expected chain.
- fScriptChecks = (GetBlockProofEquivalentTime(*m_chainman.m_best_header, *pindex, *m_chainman.m_best_header, m_params.GetConsensus()) <= 60 * 60 * 24 * 7 * 2);
+ fScriptChecks = (GetBlockProofEquivalentTime(*m_chainman.m_best_header, *pindex, *m_chainman.m_best_header, params.GetConsensus()) <= 60 * 60 * 24 * 7 * 2);
}
}
}
- int64_t nTime1 = GetTimeMicros(); nTimeCheck += nTime1 - nTimeStart;
- LogPrint(BCLog::BENCH, " - Sanity checks: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime1 - nTimeStart), nTimeCheck * MICRO, nTimeCheck * MILLI / nBlocksTotal);
+ const auto time_1{SteadyClock::now()};
+ time_check += time_1 - time_start;
+ LogPrint(BCLog::BENCH, " - Sanity checks: %.2fms [%.2fs (%.2fms/blk)]\n",
+ Ticks<MillisecondsDouble>(time_1 - time_start),
+ Ticks<SecondsDouble>(time_check),
+ Ticks<MillisecondsDouble>(time_check) / num_blocks_total);
// Do not allow blocks that contain transactions which 'overwrite' older transactions,
// unless those are already completely spent.
@@ -2124,9 +2138,9 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
// post BIP34 before approximately height 486,000,000. After block
// 1,983,702 testnet3 starts doing unnecessary BIP30 checking again.
assert(pindex->pprev);
- CBlockIndex* pindexBIP34height = pindex->pprev->GetAncestor(m_params.GetConsensus().BIP34Height);
+ CBlockIndex* pindexBIP34height = pindex->pprev->GetAncestor(params.GetConsensus().BIP34Height);
//Only continue to enforce if we're below BIP34 activation height or the block hash at that height doesn't correspond.
- fEnforceBIP30 = fEnforceBIP30 && (!pindexBIP34height || !(pindexBIP34height->GetBlockHash() == m_params.GetConsensus().BIP34Hash));
+ fEnforceBIP30 = fEnforceBIP30 && (!pindexBIP34height || !(pindexBIP34height->GetBlockHash() == params.GetConsensus().BIP34Hash));
// TODO: Remove BIP30 checking from block height 1,983,702 on, once we have a
// consensus change that ensures coinbases at those heights cannot
@@ -2151,8 +2165,12 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
// Get the script flags for this block
unsigned int flags{GetBlockScriptFlags(*pindex, m_chainman)};
- int64_t nTime2 = GetTimeMicros(); nTimeForks += nTime2 - nTime1;
- LogPrint(BCLog::BENCH, " - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime2 - nTime1), nTimeForks * MICRO, nTimeForks * MILLI / nBlocksTotal);
+ const auto time_2{SteadyClock::now()};
+ time_forks += time_2 - time_1;
+ LogPrint(BCLog::BENCH, " - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n",
+ Ticks<MillisecondsDouble>(time_2 - time_1),
+ Ticks<SecondsDouble>(time_forks),
+ Ticks<MillisecondsDouble>(time_forks) / num_blocks_total);
CBlockUndo blockundo;
@@ -2161,7 +2179,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
// in multiple threads). Preallocate the vector size so a new allocation
// doesn't invalidate pointers into the vector, and keep txsdata in scope
// for as long as `control`.
- CCheckQueueControl<CScriptCheck> control(fScriptChecks && g_parallel_script_checks ? &scriptcheckqueue : nullptr);
+ CCheckQueueControl<CScriptCheck> control(fScriptChecks && parallel_script_checks ? &scriptcheckqueue : nullptr);
std::vector<PrecomputedTransactionData> txsdata(block.vtx.size());
std::vector<int> prevheights;
@@ -2220,7 +2238,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
std::vector<CScriptCheck> vChecks;
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
TxValidationState tx_state;
- if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], g_parallel_script_checks ? &vChecks : nullptr)) {
+ if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], parallel_script_checks ? &vChecks : nullptr)) {
// Any transaction validation failure in ConnectBlock is a block consensus failure
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(), tx_state.GetDebugMessage());
@@ -2236,10 +2254,15 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
}
UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight);
}
- int64_t nTime3 = GetTimeMicros(); nTimeConnect += nTime3 - nTime2;
- LogPrint(BCLog::BENCH, " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs (%.2fms/blk)]\n", (unsigned)block.vtx.size(), MILLI * (nTime3 - nTime2), MILLI * (nTime3 - nTime2) / block.vtx.size(), nInputs <= 1 ? 0 : MILLI * (nTime3 - nTime2) / (nInputs-1), nTimeConnect * MICRO, nTimeConnect * MILLI / nBlocksTotal);
+ const auto time_3{SteadyClock::now()};
+ time_connect += time_3 - time_2;
+ LogPrint(BCLog::BENCH, " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs (%.2fms/blk)]\n", (unsigned)block.vtx.size(),
+ Ticks<MillisecondsDouble>(time_3 - time_2), Ticks<MillisecondsDouble>(time_3 - time_2) / block.vtx.size(),
+ nInputs <= 1 ? 0 : Ticks<MillisecondsDouble>(time_3 - time_2) / (nInputs - 1),
+ Ticks<SecondsDouble>(time_connect),
+ Ticks<MillisecondsDouble>(time_connect) / num_blocks_total);
- CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, m_params.GetConsensus());
+ CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, params.GetConsensus());
if (block.vtx[0]->GetValueOut() > blockReward) {
LogPrintf("ERROR: ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)\n", block.vtx[0]->GetValueOut(), blockReward);
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-amount");
@@ -2249,18 +2272,27 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
LogPrintf("ERROR: %s: CheckQueue failed\n", __func__);
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "block-validation-failed");
}
- int64_t nTime4 = GetTimeMicros(); nTimeVerify += nTime4 - nTime2;
- LogPrint(BCLog::BENCH, " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n", nInputs - 1, MILLI * (nTime4 - nTime2), nInputs <= 1 ? 0 : MILLI * (nTime4 - nTime2) / (nInputs-1), nTimeVerify * MICRO, nTimeVerify * MILLI / nBlocksTotal);
+ const auto time_4{SteadyClock::now()};
+ time_verify += time_4 - time_2;
+ LogPrint(BCLog::BENCH, " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n", nInputs - 1,
+ Ticks<MillisecondsDouble>(time_4 - time_2),
+ nInputs <= 1 ? 0 : Ticks<MillisecondsDouble>(time_4 - time_2) / (nInputs - 1),
+ Ticks<SecondsDouble>(time_verify),
+ Ticks<MillisecondsDouble>(time_verify) / num_blocks_total);
if (fJustCheck)
return true;
- if (!m_blockman.WriteUndoDataForBlock(blockundo, state, pindex, m_params)) {
+ if (!m_blockman.WriteUndoDataForBlock(blockundo, state, pindex, params)) {
return false;
}
- int64_t nTime5 = GetTimeMicros(); nTimeUndo += nTime5 - nTime4;
- LogPrint(BCLog::BENCH, " - Write undo data: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime5 - nTime4), nTimeUndo * MICRO, nTimeUndo * MILLI / nBlocksTotal);
+ const auto time_5{SteadyClock::now()};
+ time_undo += time_5 - time_4;
+ LogPrint(BCLog::BENCH, " - Write undo data: %.2fms [%.2fs (%.2fms/blk)]\n",
+ Ticks<MillisecondsDouble>(time_5 - time_4),
+ Ticks<SecondsDouble>(time_undo),
+ Ticks<MillisecondsDouble>(time_undo) / num_blocks_total);
if (!pindex->IsValid(BLOCK_VALID_SCRIPTS)) {
pindex->RaiseValidity(BLOCK_VALID_SCRIPTS);
@@ -2270,8 +2302,12 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
// add this block to the view's block chain
view.SetBestBlock(pindex->GetBlockHash());
- int64_t nTime6 = GetTimeMicros(); nTimeIndex += nTime6 - nTime5;
- LogPrint(BCLog::BENCH, " - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime6 - nTime5), nTimeIndex * MICRO, nTimeIndex * MILLI / nBlocksTotal);
+ const auto time_6{SteadyClock::now()};
+ time_index += time_6 - time_5;
+ LogPrint(BCLog::BENCH, " - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n",
+ Ticks<MillisecondsDouble>(time_6 - time_5),
+ Ticks<SecondsDouble>(time_index),
+ Ticks<MillisecondsDouble>(time_index) / num_blocks_total);
TRACE6(validation, block_connected,
block_hash.data(),
@@ -2279,13 +2315,13 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
block.vtx.size(),
nInputs,
nSigOpsCost,
- nTime5 - nTimeStart // in microseconds (µs)
+ time_5 - time_start // in microseconds (µs)
);
return true;
}
-CoinsCacheSizeState CChainState::GetCoinsCacheSizeState()
+CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState()
{
AssertLockHeld(::cs_main);
return this->GetCoinsCacheSizeState(
@@ -2293,7 +2329,7 @@ CoinsCacheSizeState CChainState::GetCoinsCacheSizeState()
m_mempool ? m_mempool->m_max_size_bytes : 0);
}
-CoinsCacheSizeState CChainState::GetCoinsCacheSizeState(
+CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState(
size_t max_coins_cache_size_bytes,
size_t max_mempool_size_bytes)
{
@@ -2317,7 +2353,7 @@ CoinsCacheSizeState CChainState::GetCoinsCacheSizeState(
return CoinsCacheSizeState::OK;
}
-bool CChainState::FlushStateToDisk(
+bool Chainstate::FlushStateToDisk(
BlockValidationState &state,
FlushStateMode mode,
int nManualPruneHeight)
@@ -2366,7 +2402,7 @@ bool CChainState::FlushStateToDisk(
} else {
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune", BCLog::BENCH);
- m_blockman.FindFilesToPrune(setFilesToPrune, m_params.PruneAfterHeight(), m_chain.Height(), last_prune, IsInitialBlockDownload());
+ m_blockman.FindFilesToPrune(setFilesToPrune, m_chainman.GetParams().PruneAfterHeight(), m_chain.Height(), last_prune, IsInitialBlockDownload());
m_blockman.m_check_for_pruning = false;
}
if (!setFilesToPrune.empty()) {
@@ -2460,7 +2496,7 @@ bool CChainState::FlushStateToDisk(
return true;
}
-void CChainState::ForceFlushStateToDisk()
+void Chainstate::ForceFlushStateToDisk()
{
BlockValidationState state;
if (!this->FlushStateToDisk(state, FlushStateMode::ALWAYS)) {
@@ -2468,7 +2504,7 @@ void CChainState::ForceFlushStateToDisk()
}
}
-void CChainState::PruneAndFlush()
+void Chainstate::PruneAndFlush()
{
BlockValidationState state;
m_blockman.m_check_for_pruning = true;
@@ -2515,18 +2551,20 @@ static void UpdateTipLog(
!warning_messages.empty() ? strprintf(" warning='%s'", warning_messages) : "");
}
-void CChainState::UpdateTip(const CBlockIndex* pindexNew)
+void Chainstate::UpdateTip(const CBlockIndex* pindexNew)
{
AssertLockHeld(::cs_main);
const auto& coins_tip = this->CoinsTip();
+ const CChainParams& params{m_chainman.GetParams()};
+
// The remainder of the function isn't relevant if we are not acting on
// the active chainstate, so return if need be.
if (this != &m_chainman.ActiveChainstate()) {
// Only log every so often so that we don't bury log messages at the tip.
constexpr int BACKGROUND_LOG_INTERVAL = 2000;
if (pindexNew->nHeight % BACKGROUND_LOG_INTERVAL == 0) {
- UpdateTipLog(coins_tip, pindexNew, m_params, __func__, "[background validation] ", "");
+ UpdateTipLog(coins_tip, pindexNew, params, __func__, "[background validation] ", "");
}
return;
}
@@ -2547,7 +2585,7 @@ void CChainState::UpdateTip(const CBlockIndex* pindexNew)
const CBlockIndex* pindex = pindexNew;
for (int bit = 0; bit < VERSIONBITS_NUM_BITS; bit++) {
WarningBitsConditionChecker checker(m_chainman, bit);
- ThresholdState state = checker.GetStateFor(pindex, m_params.GetConsensus(), warningcache.at(bit));
+ ThresholdState state = checker.GetStateFor(pindex, params.GetConsensus(), warningcache.at(bit));
if (state == ThresholdState::ACTIVE || state == ThresholdState::LOCKED_IN) {
const bilingual_str warning = strprintf(_("Unknown new rules activated (versionbit %i)"), bit);
if (state == ThresholdState::ACTIVE) {
@@ -2558,7 +2596,7 @@ void CChainState::UpdateTip(const CBlockIndex* pindexNew)
}
}
}
- UpdateTipLog(coins_tip, pindexNew, m_params, __func__, "", warning_messages.original);
+ UpdateTipLog(coins_tip, pindexNew, params, __func__, "", warning_messages.original);
}
/** Disconnect m_chain's tip.
@@ -2571,21 +2609,22 @@ void CChainState::UpdateTip(const CBlockIndex* pindexNew)
* disconnectpool (note that the caller is responsible for mempool consistency
* in any case).
*/
-bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool)
+bool Chainstate::DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool)
{
AssertLockHeld(cs_main);
if (m_mempool) AssertLockHeld(m_mempool->cs);
CBlockIndex *pindexDelete = m_chain.Tip();
assert(pindexDelete);
+ assert(pindexDelete->pprev);
// Read block from disk.
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
CBlock& block = *pblock;
- if (!ReadBlockFromDisk(block, pindexDelete, m_params.GetConsensus())) {
+ if (!ReadBlockFromDisk(block, pindexDelete, m_chainman.GetConsensus())) {
return error("DisconnectTip(): Failed to read block");
}
// Apply the block atomically to the chain state.
- int64_t nStart = GetTimeMicros();
+ const auto time_start{SteadyClock::now()};
{
CCoinsViewCache view(&CoinsTip());
assert(view.GetBestBlock() == pindexDelete->GetBlockHash());
@@ -2594,7 +2633,8 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr
bool flushed = view.Flush();
assert(flushed);
}
- LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * MILLI);
+ LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n",
+ Ticks<MillisecondsDouble>(SteadyClock::now() - time_start));
{
// Prune locks that began at or after the tip should be moved backward so they get a chance to reorg
@@ -2625,7 +2665,7 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr
}
}
- m_chain.SetTip(pindexDelete->pprev);
+ m_chain.SetTip(*pindexDelete->pprev);
UpdateTip(pindexDelete->pprev);
// Let wallets know transactions went from 1-confirmed to
@@ -2634,11 +2674,11 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr
return true;
}
-static int64_t nTimeReadFromDiskTotal = 0;
-static int64_t nTimeConnectTotal = 0;
-static int64_t nTimeFlush = 0;
-static int64_t nTimeChainState = 0;
-static int64_t nTimePostConnect = 0;
+static SteadyClock::duration time_read_from_disk_total{};
+static SteadyClock::duration time_connect_total{};
+static SteadyClock::duration time_flush{};
+static SteadyClock::duration time_chainstate{};
+static SteadyClock::duration time_post_connect{};
struct PerBlockConnectTrace {
CBlockIndex* pindex = nullptr;
@@ -2686,18 +2726,18 @@ public:
*
* The block is added to connectTrace if connection succeeds.
*/
-bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool)
+bool Chainstate::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool)
{
AssertLockHeld(cs_main);
if (m_mempool) AssertLockHeld(m_mempool->cs);
assert(pindexNew->pprev == m_chain.Tip());
// Read block from disk.
- int64_t nTime1 = GetTimeMicros();
+ const auto time_1{SteadyClock::now()};
std::shared_ptr<const CBlock> pthisBlock;
if (!pblock) {
std::shared_ptr<CBlock> pblockNew = std::make_shared<CBlock>();
- if (!ReadBlockFromDisk(*pblockNew, pindexNew, m_params.GetConsensus())) {
+ if (!ReadBlockFromDisk(*pblockNew, pindexNew, m_chainman.GetConsensus())) {
return AbortNode(state, "Failed to read block");
}
pthisBlock = pblockNew;
@@ -2707,9 +2747,13 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew
}
const CBlock& blockConnecting = *pthisBlock;
// Apply the block atomically to the chain state.
- int64_t nTime2 = GetTimeMicros(); nTimeReadFromDiskTotal += nTime2 - nTime1;
- int64_t nTime3;
- LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime2 - nTime1) * MILLI, nTimeReadFromDiskTotal * MICRO, nTimeReadFromDiskTotal * MILLI / nBlocksTotal);
+ const auto time_2{SteadyClock::now()};
+ time_read_from_disk_total += time_2 - time_1;
+ SteadyClock::time_point time_3;
+ LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs (%.2fms/blk)]\n",
+ Ticks<MillisecondsDouble>(time_2 - time_1),
+ Ticks<SecondsDouble>(time_read_from_disk_total),
+ Ticks<MillisecondsDouble>(time_read_from_disk_total) / num_blocks_total);
{
CCoinsViewCache view(&CoinsTip());
bool rv = ConnectBlock(blockConnecting, state, pindexNew, view);
@@ -2719,32 +2763,52 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew
InvalidBlockFound(pindexNew, state);
return error("%s: ConnectBlock %s failed, %s", __func__, pindexNew->GetBlockHash().ToString(), state.ToString());
}
- nTime3 = GetTimeMicros(); nTimeConnectTotal += nTime3 - nTime2;
- assert(nBlocksTotal > 0);
- LogPrint(BCLog::BENCH, " - Connect total: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime3 - nTime2) * MILLI, nTimeConnectTotal * MICRO, nTimeConnectTotal * MILLI / nBlocksTotal);
+ time_3 = SteadyClock::now();
+ time_connect_total += time_3 - time_2;
+ assert(num_blocks_total > 0);
+ LogPrint(BCLog::BENCH, " - Connect total: %.2fms [%.2fs (%.2fms/blk)]\n",
+ Ticks<MillisecondsDouble>(time_3 - time_2),
+ Ticks<SecondsDouble>(time_connect_total),
+ Ticks<MillisecondsDouble>(time_connect_total) / num_blocks_total);
bool flushed = view.Flush();
assert(flushed);
}
- int64_t nTime4 = GetTimeMicros(); nTimeFlush += nTime4 - nTime3;
- LogPrint(BCLog::BENCH, " - Flush: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime4 - nTime3) * MILLI, nTimeFlush * MICRO, nTimeFlush * MILLI / nBlocksTotal);
+ const auto time_4{SteadyClock::now()};
+ time_flush += time_4 - time_3;
+ LogPrint(BCLog::BENCH, " - Flush: %.2fms [%.2fs (%.2fms/blk)]\n",
+ Ticks<MillisecondsDouble>(time_4 - time_3),
+ Ticks<SecondsDouble>(time_flush),
+ Ticks<MillisecondsDouble>(time_flush) / num_blocks_total);
// Write the chain state to disk, if necessary.
if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) {
return false;
}
- int64_t nTime5 = GetTimeMicros(); nTimeChainState += nTime5 - nTime4;
- LogPrint(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime5 - nTime4) * MILLI, nTimeChainState * MICRO, nTimeChainState * MILLI / nBlocksTotal);
+ const auto time_5{SteadyClock::now()};
+ time_chainstate += time_5 - time_4;
+ LogPrint(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n",
+ Ticks<MillisecondsDouble>(time_5 - time_4),
+ Ticks<SecondsDouble>(time_chainstate),
+ Ticks<MillisecondsDouble>(time_chainstate) / num_blocks_total);
// Remove conflicting transactions from the mempool.;
if (m_mempool) {
m_mempool->removeForBlock(blockConnecting.vtx, pindexNew->nHeight);
disconnectpool.removeForBlock(blockConnecting.vtx);
}
// Update m_chain & related variables.
- m_chain.SetTip(pindexNew);
+ m_chain.SetTip(*pindexNew);
UpdateTip(pindexNew);
- int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1;
- LogPrint(BCLog::BENCH, " - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime6 - nTime5) * MILLI, nTimePostConnect * MICRO, nTimePostConnect * MILLI / nBlocksTotal);
- LogPrint(BCLog::BENCH, "- Connect block: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime6 - nTime1) * MILLI, nTimeTotal * MICRO, nTimeTotal * MILLI / nBlocksTotal);
+ const auto time_6{SteadyClock::now()};
+ time_post_connect += time_6 - time_5;
+ time_total += time_6 - time_1;
+ LogPrint(BCLog::BENCH, " - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\n",
+ Ticks<MillisecondsDouble>(time_6 - time_5),
+ Ticks<SecondsDouble>(time_post_connect),
+ Ticks<MillisecondsDouble>(time_post_connect) / num_blocks_total);
+ LogPrint(BCLog::BENCH, "- Connect block: %.2fms [%.2fs (%.2fms/blk)]\n",
+ Ticks<MillisecondsDouble>(time_6 - time_1),
+ Ticks<SecondsDouble>(time_total),
+ Ticks<MillisecondsDouble>(time_total) / num_blocks_total);
connectTrace.BlockConnected(pindexNew, std::move(pthisBlock));
return true;
@@ -2754,7 +2818,7 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew
* Return the tip of the chain with the most work in it, that isn't
* known to be invalid (it's however far from certain to be valid).
*/
-CBlockIndex* CChainState::FindMostWorkChain()
+CBlockIndex* Chainstate::FindMostWorkChain()
{
AssertLockHeld(::cs_main);
do {
@@ -2813,7 +2877,7 @@ CBlockIndex* CChainState::FindMostWorkChain()
}
/** Delete all entries in setBlockIndexCandidates that are worse than the current tip. */
-void CChainState::PruneBlockIndexCandidates() {
+void Chainstate::PruneBlockIndexCandidates() {
// Note that we can't delete the current block itself, as we may need to return to it later in case a
// reorganization to a better block fails.
std::set<CBlockIndex*, CBlockIndexWorkComparator>::iterator it = setBlockIndexCandidates.begin();
@@ -2830,7 +2894,7 @@ void CChainState::PruneBlockIndexCandidates() {
*
* @returns true unless a system error occurred
*/
-bool CChainState::ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace)
+bool Chainstate::ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace)
{
AssertLockHeld(cs_main);
if (m_mempool) AssertLockHeld(m_mempool->cs);
@@ -2922,7 +2986,7 @@ static SynchronizationState GetSynchronizationState(bool init)
return SynchronizationState::INIT_DOWNLOAD;
}
-static bool NotifyHeaderTip(CChainState& chainstate) LOCKS_EXCLUDED(cs_main) {
+static bool NotifyHeaderTip(Chainstate& chainstate) LOCKS_EXCLUDED(cs_main) {
bool fNotify = false;
bool fInitialBlockDownload = false;
static CBlockIndex* pindexHeaderOld = nullptr;
@@ -2939,7 +3003,7 @@ static bool NotifyHeaderTip(CChainState& chainstate) LOCKS_EXCLUDED(cs_main) {
}
// Send block tip changed notifications without cs_main
if (fNotify) {
- uiInterface.NotifyHeaderTip(GetSynchronizationState(fInitialBlockDownload), pindexHeader);
+ uiInterface.NotifyHeaderTip(GetSynchronizationState(fInitialBlockDownload), pindexHeader->nHeight, pindexHeader->nTime, false);
}
return fNotify;
}
@@ -2952,7 +3016,7 @@ static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) {
}
}
-bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr<const CBlock> pblock)
+bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<const CBlock> pblock)
{
AssertLockNotHeld(m_chainstate_mutex);
@@ -3054,7 +3118,7 @@ bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr
return true;
}
-bool CChainState::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex)
+bool Chainstate::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex)
{
AssertLockNotHeld(m_chainstate_mutex);
AssertLockNotHeld(::cs_main);
@@ -3085,7 +3149,7 @@ bool CChainState::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex
return ActivateBestChain(state, std::shared_ptr<const CBlock>());
}
-bool CChainState::InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex)
+bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex)
{
AssertLockNotHeld(m_chainstate_mutex);
AssertLockNotHeld(::cs_main);
@@ -3228,7 +3292,7 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, CBlockIndex* pind
return true;
}
-void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) {
+void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) {
AssertLockHeld(cs_main);
int nHeight = pindex->nHeight;
@@ -3261,7 +3325,7 @@ void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) {
}
/** Mark a block as having its data received and checked (up to BLOCK_VALID_TRANSACTIONS). */
-void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos)
+void Chainstate::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos)
{
AssertLockHeld(cs_main);
pindexNew->nTx = block.vtx.size();
@@ -3427,6 +3491,22 @@ std::vector<unsigned char> ChainstateManager::GenerateCoinbaseCommitment(CBlock&
return commitment;
}
+bool HasValidProofOfWork(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams)
+{
+ return std::all_of(headers.cbegin(), headers.cend(),
+ [&](const auto& header) { return CheckProofOfWork(header.GetHash(), header.nBits, consensusParams);});
+}
+
+arith_uint256 CalculateHeadersWork(const std::vector<CBlockHeader>& headers)
+{
+ arith_uint256 total_work{0};
+ for (const CBlockHeader& header : headers) {
+ CBlockIndex dummy(header);
+ total_work += GetBlockProof(dummy);
+ }
+ return total_work;
+}
+
/** Context-dependent validity checks.
* By "context", we mean only the previous block headers, but not the UTXO
* set; UTXO-related validity checks are done in ConnectBlock().
@@ -3436,7 +3516,7 @@ std::vector<unsigned char> ChainstateManager::GenerateCoinbaseCommitment(CBlock&
* in ConnectBlock().
* Note that -reindex-chainstate skips the validation that happens here!
*/
-static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, BlockManager& blockman, const ChainstateManager& chainman, const CBlockIndex* pindexPrev, int64_t nAdjustedTime) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
+static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, BlockManager& blockman, const ChainstateManager& chainman, const CBlockIndex* pindexPrev, NodeClock::time_point now) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
AssertLockHeld(::cs_main);
assert(pindexPrev != nullptr);
@@ -3448,7 +3528,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio
return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-diffbits", "incorrect proof of work");
// Check against checkpoints
- if (fCheckpointsEnabled) {
+ if (chainman.m_options.checkpoints_enabled) {
// Don't accept any forks from the main chain prior to last checkpoint.
// GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our
// BlockIndex().
@@ -3464,8 +3544,9 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio
return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "time-too-old", "block's timestamp is too early");
// Check timestamp
- if (block.GetBlockTime() > nAdjustedTime + MAX_FUTURE_BLOCK_TIME)
+ if (block.Time() > now + std::chrono::seconds{MAX_FUTURE_BLOCK_TIME}) {
return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE, "time-too-new", "block timestamp too far in the future");
+ }
// Reject blocks with outdated version
if ((block.nVersion < 2 && DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_HEIGHTINCB)) ||
@@ -3566,9 +3647,10 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat
return true;
}
-bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, CBlockIndex** ppindex)
+bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, CBlockIndex** ppindex, bool min_pow_checked)
{
AssertLockHeld(cs_main);
+
// Check for duplicate
uint256 hash = block.GetHash();
BlockMap::iterator miSelf{m_blockman.m_block_index.find(hash)};
@@ -3602,7 +3684,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida
LogPrint(BCLog::VALIDATION, "%s: %s prev block invalid\n", __func__, hash.ToString());
return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV, "bad-prevblk");
}
- if (!ContextualCheckBlockHeader(block, state, m_blockman, *this, pindexPrev, m_adjusted_time_callback())) {
+ if (!ContextualCheckBlockHeader(block, state, m_blockman, *this, pindexPrev, m_options.adjusted_time_callback())) {
LogPrint(BCLog::VALIDATION, "%s: Consensus::ContextualCheckBlockHeader: %s, %s\n", __func__, hash.ToString(), state.ToString());
return false;
}
@@ -3646,6 +3728,10 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida
}
}
}
+ if (!min_pow_checked) {
+ LogPrint(BCLog::VALIDATION, "%s: not adding new block header %s, missing anti-dos proof-of-work validation\n", __func__, hash.ToString());
+ return state.Invalid(BlockValidationResult::BLOCK_HEADER_LOW_WORK, "too-little-chainwork");
+ }
CBlockIndex* pindex{m_blockman.AddToBlockIndex(block, m_best_header)};
if (ppindex)
@@ -3655,14 +3741,14 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida
}
// Exposed wrapper for AcceptBlockHeader
-bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, BlockValidationState& state, const CBlockIndex** ppindex)
+bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, bool min_pow_checked, BlockValidationState& state, const CBlockIndex** ppindex)
{
AssertLockNotHeld(cs_main);
{
LOCK(cs_main);
for (const CBlockHeader& header : headers) {
CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast
- bool accepted{AcceptBlockHeader(header, state, &pindex)};
+ bool accepted{AcceptBlockHeader(header, state, &pindex, min_pow_checked)};
ActiveChainstate().CheckBlockIndex();
if (!accepted) {
@@ -3684,8 +3770,33 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>&
return true;
}
+void ChainstateManager::ReportHeadersPresync(const arith_uint256& work, int64_t height, int64_t timestamp)
+{
+ AssertLockNotHeld(cs_main);
+ const auto& chainstate = ActiveChainstate();
+ {
+ LOCK(cs_main);
+ // Don't report headers presync progress if we already have a post-minchainwork header chain.
+ // This means we lose reporting for potentially legitimate, but unlikely, deep reorgs, but
+ // prevent attackers that spam low-work headers from filling our logs.
+ if (m_best_header->nChainWork >= UintToArith256(GetConsensus().nMinimumChainWork)) return;
+ // Rate limit headers presync updates to 4 per second, as these are not subject to DoS
+ // protection.
+ auto now = std::chrono::steady_clock::now();
+ if (now < m_last_presync_update + std::chrono::milliseconds{250}) return;
+ m_last_presync_update = now;
+ }
+ bool initial_download = chainstate.IsInitialBlockDownload();
+ uiInterface.NotifyHeaderTip(GetSynchronizationState(initial_download), height, timestamp, /*presync=*/true);
+ if (initial_download) {
+ const int64_t blocks_left{(GetTime() - timestamp) / GetConsensus().nPowTargetSpacing};
+ const double progress{100.0 * height / (height + blocks_left)};
+ LogPrintf("Pre-synchronizing blockheaders, height: %d (~%.2f%%)\n", height, progress);
+ }
+}
+
/** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */
-bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock)
+bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, bool min_pow_checked)
{
const CBlock& block = *pblock;
@@ -3695,7 +3806,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block
CBlockIndex *pindexDummy = nullptr;
CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy;
- bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex)};
+ bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex, min_pow_checked)};
CheckBlockIndex();
if (!accepted_header)
@@ -3731,10 +3842,12 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block
// If our tip is behind, a peer could try to send us
// low-work blocks on a fake chain that we would never
// request; don't process these.
- if (pindex->nChainWork < nMinimumChainWork) return true;
+ if (pindex->nChainWork < m_chainman.MinimumChainWork()) return true;
}
- if (!CheckBlock(block, state, m_params.GetConsensus()) ||
+ const CChainParams& params{m_chainman.GetParams()};
+
+ if (!CheckBlock(block, state, params.GetConsensus()) ||
!ContextualCheckBlock(block, state, m_chainman, pindex->pprev)) {
if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
pindex->nStatus |= BLOCK_FAILED_VALID;
@@ -3751,7 +3864,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block
// Write block to history file
if (fNewBlock) *fNewBlock = true;
try {
- FlatFilePos blockPos{m_blockman.SaveBlockToDisk(block, pindex->nHeight, m_chain, m_params, dbp)};
+ FlatFilePos blockPos{m_blockman.SaveBlockToDisk(block, pindex->nHeight, m_chain, params, dbp)};
if (blockPos.IsNull()) {
state.Error(strprintf("%s: Failed to find position to write new block to disk", __func__));
return false;
@@ -3768,7 +3881,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block
return true;
}
-bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool* new_block)
+bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked, bool* new_block)
{
AssertLockNotHeld(cs_main);
@@ -3789,7 +3902,7 @@ bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& blo
bool ret = CheckBlock(*block, state, GetConsensus());
if (ret) {
// Store to disk
- ret = ActiveChainstate().AcceptBlock(block, state, &pindex, force_processing, nullptr, new_block);
+ ret = ActiveChainstate().AcceptBlock(block, state, &pindex, force_processing, nullptr, new_block, min_pow_checked);
}
if (!ret) {
GetMainSignals().BlockChecked(*block, state);
@@ -3810,7 +3923,7 @@ bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& blo
MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef& tx, bool test_accept)
{
AssertLockHeld(cs_main);
- CChainState& active_chainstate = ActiveChainstate();
+ Chainstate& active_chainstate = ActiveChainstate();
if (!active_chainstate.GetMempool()) {
TxValidationState state;
state.Invalid(TxValidationResult::TX_NO_MEMPOOL, "no-mempool");
@@ -3823,10 +3936,10 @@ MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef&
bool TestBlockValidity(BlockValidationState& state,
const CChainParams& chainparams,
- CChainState& chainstate,
+ Chainstate& chainstate,
const CBlock& block,
CBlockIndex* pindexPrev,
- const std::function<int64_t()>& adjusted_time_callback,
+ const std::function<NodeClock::time_point()>& adjusted_time_callback,
bool fCheckPOW,
bool fCheckMerkleRoot)
{
@@ -3855,7 +3968,7 @@ bool TestBlockValidity(BlockValidationState& state,
}
/* This function is called from the RPC code for pruneblockchain */
-void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeight)
+void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight)
{
BlockValidationState state;
if (!active_chainstate.FlushStateToDisk(
@@ -3864,14 +3977,14 @@ void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeigh
}
}
-void CChainState::LoadMempool(const fs::path& load_path, FopenFn mockable_fopen_function)
+void Chainstate::LoadMempool(const fs::path& load_path, FopenFn mockable_fopen_function)
{
if (!m_mempool) return;
::LoadMempool(*m_mempool, load_path, *this, mockable_fopen_function);
m_mempool->SetLoadTried(!ShutdownRequested());
}
-bool CChainState::LoadChainTip()
+bool Chainstate::LoadChainTip()
{
AssertLockHeld(cs_main);
const CCoinsViewCache& coins_cache = CoinsTip();
@@ -3887,7 +4000,7 @@ bool CChainState::LoadChainTip()
if (!pindex) {
return false;
}
- m_chain.SetTip(pindex);
+ m_chain.SetTip(*pindex);
PruneBlockIndexCandidates();
tip = m_chain.Tip();
@@ -3895,7 +4008,7 @@ bool CChainState::LoadChainTip()
tip->GetBlockHash().ToString(),
m_chain.Height(),
FormatISO8601DateTime(tip->GetBlockTime()),
- GuessVerificationProgress(m_params.TxData(), tip));
+ GuessVerificationProgress(m_chainman.GetParams().TxData(), tip));
return true;
}
@@ -3910,7 +4023,7 @@ CVerifyDB::~CVerifyDB()
}
bool CVerifyDB::VerifyDB(
- CChainState& chainstate,
+ Chainstate& chainstate,
const Consensus::Params& consensus_params,
CCoinsView& coinsview,
int nCheckLevel, int nCheckDepth)
@@ -4026,12 +4139,12 @@ bool CVerifyDB::VerifyDB(
}
/** Apply the effects of a block on the utxo cache, ignoring that it may already have been applied. */
-bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs)
+bool Chainstate::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs)
{
AssertLockHeld(cs_main);
// TODO: merge with ConnectBlock
CBlock block;
- if (!ReadBlockFromDisk(block, pindex, m_params.GetConsensus())) {
+ if (!ReadBlockFromDisk(block, pindex, m_chainman.GetConsensus())) {
return error("ReplayBlock(): ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
}
@@ -4047,7 +4160,7 @@ bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& i
return true;
}
-bool CChainState::ReplayBlocks()
+bool Chainstate::ReplayBlocks()
{
LOCK(cs_main);
@@ -4083,7 +4196,7 @@ bool CChainState::ReplayBlocks()
while (pindexOld != pindexFork) {
if (pindexOld->nHeight > 0) { // Never disconnect the genesis block.
CBlock block;
- if (!ReadBlockFromDisk(block, pindexOld, m_params.GetConsensus())) {
+ if (!ReadBlockFromDisk(block, pindexOld, m_chainman.GetConsensus())) {
return error("RollbackBlock(): ReadBlockFromDisk() failed at %d, hash=%s", pindexOld->nHeight, pindexOld->GetBlockHash().ToString());
}
LogPrintf("Rolling back %s (%i)\n", pindexOld->GetBlockHash().ToString(), pindexOld->nHeight);
@@ -4115,7 +4228,7 @@ bool CChainState::ReplayBlocks()
return true;
}
-bool CChainState::NeedsRedownload() const
+bool Chainstate::NeedsRedownload() const
{
AssertLockHeld(cs_main);
@@ -4133,7 +4246,7 @@ bool CChainState::NeedsRedownload() const
return false;
}
-void CChainState::UnloadBlockIndex()
+void Chainstate::UnloadBlockIndex()
{
AssertLockHeld(::cs_main);
nBlockSequenceId = 1;
@@ -4202,7 +4315,7 @@ bool ChainstateManager::LoadBlockIndex()
// detecting "holistically" whether the block index under consideration
// relied on an assumed-valid ancestor, but this proved to be too slow to
// be practical.
- for (CChainState* chainstate : GetAll()) {
+ for (Chainstate* chainstate : GetAll()) {
if (chainstate->reliesOnAssumedValid() ||
pindex->nHeight < first_assumed_valid_height) {
chainstate->setBlockIndexCandidates.insert(pindex);
@@ -4231,20 +4344,22 @@ bool ChainstateManager::LoadBlockIndex()
return true;
}
-bool CChainState::LoadGenesisBlock()
+bool Chainstate::LoadGenesisBlock()
{
LOCK(cs_main);
+ const CChainParams& params{m_chainman.GetParams()};
+
// Check whether we're already initialized by checking for genesis in
// 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 (m_blockman.m_block_index.count(m_params.GenesisBlock().GetHash()))
+ if (m_blockman.m_block_index.count(params.GenesisBlock().GetHash()))
return true;
try {
- const CBlock& block = m_params.GenesisBlock();
- FlatFilePos blockPos{m_blockman.SaveBlockToDisk(block, 0, m_chain, m_params, nullptr)};
+ const CBlock& block = params.GenesisBlock();
+ FlatFilePos blockPos{m_blockman.SaveBlockToDisk(block, 0, m_chain, params, nullptr)};
if (blockPos.IsNull()) {
return error("%s: writing genesis block to disk failed", __func__);
}
@@ -4257,7 +4372,7 @@ bool CChainState::LoadGenesisBlock()
return true;
}
-void CChainState::LoadExternalBlockFile(
+void Chainstate::LoadExternalBlockFile(
FILE* fileIn,
FlatFilePos* dbp,
std::multimap<uint256, FlatFilePos>* blocks_with_unknown_parent)
@@ -4267,7 +4382,8 @@ void CChainState::LoadExternalBlockFile(
// Either both should be specified (-reindex), or neither (-loadblock).
assert(!dbp == !blocks_with_unknown_parent);
- int64_t nStart = GetTimeMillis();
+ const auto start{SteadyClock::now()};
+ const CChainParams& params{m_chainman.GetParams()};
int nLoaded = 0;
try {
@@ -4284,10 +4400,10 @@ void CChainState::LoadExternalBlockFile(
try {
// locate a header
unsigned char buf[CMessageHeader::MESSAGE_START_SIZE];
- blkdat.FindByte(m_params.MessageStart()[0]);
+ blkdat.FindByte(params.MessageStart()[0]);
nRewind = blkdat.GetPos() + 1;
blkdat >> buf;
- if (memcmp(buf, m_params.MessageStart(), CMessageHeader::MESSAGE_START_SIZE)) {
+ if (memcmp(buf, params.MessageStart(), CMessageHeader::MESSAGE_START_SIZE)) {
continue;
}
// read size
@@ -4313,7 +4429,7 @@ void CChainState::LoadExternalBlockFile(
{
LOCK(cs_main);
// detect out of order blocks, and store them for later
- if (hash != m_params.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(block.hashPrevBlock)) {
+ if (hash != params.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(block.hashPrevBlock)) {
LogPrint(BCLog::REINDEX, "%s: Out of order block %s, parent %s not known\n", __func__, hash.ToString(),
block.hashPrevBlock.ToString());
if (dbp && blocks_with_unknown_parent) {
@@ -4326,19 +4442,19 @@ void CChainState::LoadExternalBlockFile(
const CBlockIndex* pindex = m_blockman.LookupBlockIndex(hash);
if (!pindex || (pindex->nStatus & BLOCK_HAVE_DATA) == 0) {
BlockValidationState state;
- if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr)) {
+ if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr, true)) {
nLoaded++;
}
if (state.IsError()) {
break;
}
- } else if (hash != m_params.GetConsensus().hashGenesisBlock && pindex->nHeight % 1000 == 0) {
+ } else if (hash != params.GetConsensus().hashGenesisBlock && pindex->nHeight % 1000 == 0) {
LogPrint(BCLog::REINDEX, "Block Import: already had block %s at height %d\n", hash.ToString(), pindex->nHeight);
}
}
// Activate the genesis block so normal node progress can continue
- if (hash == m_params.GetConsensus().hashGenesisBlock) {
+ if (hash == params.GetConsensus().hashGenesisBlock) {
BlockValidationState state;
if (!ActivateBestChain(state, nullptr)) {
break;
@@ -4359,12 +4475,12 @@ void CChainState::LoadExternalBlockFile(
while (range.first != range.second) {
std::multimap<uint256, FlatFilePos>::iterator it = range.first;
std::shared_ptr<CBlock> pblockrecursive = std::make_shared<CBlock>();
- if (ReadBlockFromDisk(*pblockrecursive, it->second, m_params.GetConsensus())) {
+ if (ReadBlockFromDisk(*pblockrecursive, it->second, params.GetConsensus())) {
LogPrint(BCLog::REINDEX, "%s: Processing out of order child %s of %s\n", __func__, pblockrecursive->GetHash().ToString(),
head.ToString());
LOCK(cs_main);
BlockValidationState dummy;
- if (AcceptBlock(pblockrecursive, dummy, nullptr, true, &it->second, nullptr)) {
+ if (AcceptBlock(pblockrecursive, dummy, nullptr, true, &it->second, nullptr, true)) {
nLoaded++;
queue.push_back(pblockrecursive->GetHash());
}
@@ -4375,18 +4491,29 @@ void CChainState::LoadExternalBlockFile(
}
}
} catch (const std::exception& e) {
- LogPrintf("%s: Deserialize or I/O error - %s\n", __func__, e.what());
+ // historical bugs added extra data to the block files that does not deserialize cleanly.
+ // commonly this data is between readable blocks, but it does not really matter. such data is not fatal to the import process.
+ // the code that reads the block files deals with invalid data by simply ignoring it.
+ // it continues to search for the next {4 byte magic message start bytes + 4 byte length + block} that does deserialize cleanly
+ // and passes all of the other block validation checks dealing with POW and the merkle root, etc...
+ // we merely note with this informational log message when unexpected data is encountered.
+ // we could also be experiencing a storage system read error, or a read of a previous bad write. these are possible, but
+ // less likely scenarios. we don't have enough information to tell a difference here.
+ // the reindex process is not the place to attempt to clean and/or compact the block files. if so desired, a studious node operator
+ // may use knowledge of the fact that the block files are not entirely pristine in order to prepare a set of pristine, and
+ // perhaps ordered, block files for later reindexing.
+ LogPrint(BCLog::REINDEX, "%s: unexpected data at file offset 0x%x - %s. continuing\n", __func__, (nRewind - 1), e.what());
}
}
} catch (const std::runtime_error& e) {
AbortNode(std::string("System error: ") + e.what());
}
- LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, GetTimeMillis() - nStart);
+ LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
}
-void CChainState::CheckBlockIndex()
+void Chainstate::CheckBlockIndex()
{
- if (!fCheckBlockIndex) {
+ if (!m_chainman.ShouldCheckBlockIndex()) {
return;
}
@@ -4459,7 +4586,7 @@ void CChainState::CheckBlockIndex()
// Begin: actual consistency checks.
if (pindex->pprev == nullptr) {
// Genesis block checks.
- assert(pindex->GetBlockHash() == m_params.GetConsensus().hashGenesisBlock); // Genesis block's hash must match.
+ assert(pindex->GetBlockHash() == m_chainman.GetConsensus().hashGenesisBlock); // Genesis block's hash must match.
assert(pindex == m_chain.Genesis()); // The current active chain's genesis block must be this block.
}
if (!pindex->HaveTxsDownloaded()) assert(pindex->nSequenceId <= 0); // nSequenceId can't be set positive for blocks that aren't linked (negative is used for preciousblock)
@@ -4606,7 +4733,7 @@ void CChainState::CheckBlockIndex()
assert(nNodes == forward.size());
}
-std::string CChainState::ToString()
+std::string Chainstate::ToString()
{
AssertLockHeld(::cs_main);
CBlockIndex* tip = m_chain.Tip();
@@ -4615,7 +4742,7 @@ std::string CChainState::ToString()
tip ? tip->nHeight : -1, tip ? tip->GetBlockHash().ToString() : "null");
}
-bool CChainState::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size)
+bool Chainstate::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size)
{
AssertLockHeld(::cs_main);
if (coinstip_size == m_coinstip_cache_size_bytes &&
@@ -4676,10 +4803,10 @@ std::optional<uint256> ChainstateManager::SnapshotBlockhash() const
return std::nullopt;
}
-std::vector<CChainState*> ChainstateManager::GetAll()
+std::vector<Chainstate*> ChainstateManager::GetAll()
{
LOCK(::cs_main);
- std::vector<CChainState*> out;
+ std::vector<Chainstate*> out;
if (!IsSnapshotValidated() && m_ibd_chainstate) {
out.push_back(m_ibd_chainstate.get());
@@ -4692,28 +4819,15 @@ std::vector<CChainState*> ChainstateManager::GetAll()
return out;
}
-CChainState& ChainstateManager::InitializeChainstate(
- CTxMemPool* mempool, const std::optional<uint256>& snapshot_blockhash)
+Chainstate& ChainstateManager::InitializeChainstate(CTxMemPool* mempool)
{
AssertLockHeld(::cs_main);
- bool is_snapshot = snapshot_blockhash.has_value();
- std::unique_ptr<CChainState>& to_modify =
- is_snapshot ? m_snapshot_chainstate : m_ibd_chainstate;
-
- if (to_modify) {
- throw std::logic_error("should not be overwriting a chainstate");
- }
- to_modify.reset(new CChainState(mempool, m_blockman, *this, snapshot_blockhash));
-
- // Snapshot chainstates and initial IBD chaintates always become active.
- if (is_snapshot || (!is_snapshot && !m_active_chainstate)) {
- LogPrintf("Switching active chainstate to %s\n", to_modify->ToString());
- m_active_chainstate = to_modify.get();
- } else {
- throw std::logic_error("unexpected chainstate activation");
- }
+ assert(!m_ibd_chainstate);
+ assert(!m_active_chainstate);
- return *to_modify;
+ m_ibd_chainstate = std::make_unique<Chainstate>(mempool, m_blockman, *this);
+ m_active_chainstate = m_ibd_chainstate.get();
+ return *m_active_chainstate;
}
const AssumeutxoData* ExpectedAssumeutxo(
@@ -4728,6 +4842,46 @@ const AssumeutxoData* ExpectedAssumeutxo(
return nullptr;
}
+static bool DeleteCoinsDBFromDisk(const fs::path db_path, bool is_snapshot)
+ EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
+{
+ AssertLockHeld(::cs_main);
+
+ if (is_snapshot) {
+ fs::path base_blockhash_path = db_path / node::SNAPSHOT_BLOCKHASH_FILENAME;
+
+ if (fs::exists(base_blockhash_path)) {
+ bool removed = fs::remove(base_blockhash_path);
+ if (!removed) {
+ LogPrintf("[snapshot] failed to remove file %s\n",
+ fs::PathToString(base_blockhash_path));
+ }
+ } else {
+ LogPrintf("[snapshot] snapshot chainstate dir being removed lacks %s file\n",
+ fs::PathToString(node::SNAPSHOT_BLOCKHASH_FILENAME));
+ }
+ }
+
+ std::string path_str = fs::PathToString(db_path);
+ LogPrintf("Removing leveldb dir at %s\n", path_str);
+
+ // We have to destruct before this call leveldb::DB in order to release the db
+ // lock, otherwise `DestroyDB` will fail. See `leveldb::~DBImpl()`.
+ const bool destroyed = dbwrapper::DestroyDB(path_str, {}).ok();
+
+ if (!destroyed) {
+ LogPrintf("error: leveldb DestroyDB call failed on %s\n", path_str);
+ }
+
+ // Datadir should be removed from filesystem; otherwise initialization may detect
+ // it on subsequent statups and get confused.
+ //
+ // If the base_blockhash_path removal above fails in the case of snapshot
+ // chainstates, this will return false since leveldb won't remove a non-empty
+ // directory.
+ return destroyed && !fs::exists(db_path);
+}
+
bool ChainstateManager::ActivateSnapshot(
AutoFile& coins_file,
const SnapshotMetadata& metadata,
@@ -4773,7 +4927,7 @@ bool ChainstateManager::ActivateSnapshot(
}
auto snapshot_chainstate = WITH_LOCK(::cs_main,
- return std::make_unique<CChainState>(
+ return std::make_unique<Chainstate>(
/*mempool=*/nullptr, m_blockman, *this, base_blockhash));
{
@@ -4785,11 +4939,34 @@ bool ChainstateManager::ActivateSnapshot(
static_cast<size_t>(current_coinstip_cache_size * SNAPSHOT_CACHE_PERC));
}
- const bool snapshot_ok = this->PopulateAndValidateSnapshot(
+ bool snapshot_ok = this->PopulateAndValidateSnapshot(
*snapshot_chainstate, coins_file, metadata);
+ // If not in-memory, persist the base blockhash for use during subsequent
+ // initialization.
+ if (!in_memory) {
+ LOCK(::cs_main);
+ if (!node::WriteSnapshotBaseBlockhash(*snapshot_chainstate)) {
+ snapshot_ok = false;
+ }
+ }
if (!snapshot_ok) {
- WITH_LOCK(::cs_main, this->MaybeRebalanceCaches());
+ LOCK(::cs_main);
+ this->MaybeRebalanceCaches();
+
+ // PopulateAndValidateSnapshot can return (in error) before the leveldb datadir
+ // has been created, so only attempt removal if we got that far.
+ if (auto snapshot_datadir = node::FindSnapshotChainstateDir()) {
+ // We have to destruct leveldb::DB in order to release the db lock, otherwise
+ // DestroyDB() (in DeleteCoinsDBFromDisk()) will fail. See `leveldb::~DBImpl()`.
+ // Destructing the chainstate (and so resetting the coinsviews object) does this.
+ snapshot_chainstate.reset();
+ bool removed = DeleteCoinsDBFromDisk(*snapshot_datadir, /*is_snapshot=*/true);
+ if (!removed) {
+ AbortNode(strprintf("Failed to remove snapshot chainstate dir (%s). "
+ "Manually remove it before restarting.\n", fs::PathToString(*snapshot_datadir)));
+ }
+ }
return false;
}
@@ -4823,7 +5000,7 @@ static void FlushSnapshotToDisk(CCoinsViewCache& coins_cache, bool snapshot_load
}
bool ChainstateManager::PopulateAndValidateSnapshot(
- CChainState& snapshot_chainstate,
+ Chainstate& snapshot_chainstate,
AutoFile& coins_file,
const SnapshotMetadata& metadata)
{
@@ -4917,7 +5094,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
// Important that we set this. This and the coins_cache accesses above are
// sort of a layer violation, but either we reach into the innards of
- // CCoinsViewCache here or we have to invert some of the CChainState to
+ // CCoinsViewCache here or we have to invert some of the Chainstate to
// embed them in a snapshot-activation-specific CCoinsViewCache bulk load
// method.
coins_cache.SetBestBlock(base_blockhash);
@@ -4964,7 +5141,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
return false;
}
- snapshot_chainstate.m_chain.SetTip(snapshot_start_block);
+ snapshot_chainstate.m_chain.SetTip(*snapshot_start_block);
// The remainder of this function requires modifying data protected by cs_main.
LOCK(::cs_main);
@@ -4996,7 +5173,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
index->nStatus |= BLOCK_ASSUMED_VALID;
}
- // Fake BLOCK_OPT_WITNESS so that CChainState::NeedsRedownload()
+ // Fake BLOCK_OPT_WITNESS so that Chainstate::NeedsRedownload()
// won't ask to rewind the entire assumed-valid chain on startup.
if (DeploymentActiveAt(*index, *this, Consensus::DEPLOYMENT_SEGWIT)) {
index->nStatus |= BLOCK_OPT_WITNESS;
@@ -5019,7 +5196,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
return true;
}
-CChainState& ChainstateManager::ActiveChainstate() const
+Chainstate& ChainstateManager::ActiveChainstate() const
{
LOCK(::cs_main);
assert(m_active_chainstate);
@@ -5063,6 +5240,29 @@ void ChainstateManager::MaybeRebalanceCaches()
}
}
+void ChainstateManager::ResetChainstates()
+{
+ m_ibd_chainstate.reset();
+ m_snapshot_chainstate.reset();
+ m_active_chainstate = nullptr;
+}
+
+/**
+ * Apply default chain params to nullopt members.
+ * This helps to avoid coding errors around the accidental use of the compare
+ * operators that accept nullopt, thus ignoring the intended default value.
+ */
+static ChainstateManager::Options&& Flatten(ChainstateManager::Options&& opts)
+{
+ if (!opts.check_block_index.has_value()) opts.check_block_index = opts.chainparams.DefaultConsistencyChecks();
+ if (!opts.minimum_chain_work.has_value()) opts.minimum_chain_work = UintToArith256(opts.chainparams.GetConsensus().nMinimumChainWork);
+ if (!opts.assumed_valid_block.has_value()) opts.assumed_valid_block = opts.chainparams.GetConsensus().defaultAssumeValid;
+ Assert(opts.adjusted_time_callback);
+ return std::move(opts);
+}
+
+ChainstateManager::ChainstateManager(Options options) : m_options{Flatten(std::move(options))} {}
+
ChainstateManager::~ChainstateManager()
{
LOCK(::cs_main);
@@ -5074,3 +5274,31 @@ ChainstateManager::~ChainstateManager()
i.clear();
}
}
+
+bool ChainstateManager::DetectSnapshotChainstate(CTxMemPool* mempool)
+{
+ assert(!m_snapshot_chainstate);
+ std::optional<fs::path> path = node::FindSnapshotChainstateDir();
+ if (!path) {
+ return false;
+ }
+ std::optional<uint256> base_blockhash = node::ReadSnapshotBaseBlockhash(*path);
+ if (!base_blockhash) {
+ return false;
+ }
+ LogPrintf("[snapshot] detected active snapshot chainstate (%s) - loading\n",
+ fs::PathToString(*path));
+
+ this->ActivateExistingSnapshot(mempool, *base_blockhash);
+ return true;
+}
+
+Chainstate& ChainstateManager::ActivateExistingSnapshot(CTxMemPool* mempool, uint256 base_blockhash)
+{
+ assert(!m_snapshot_chainstate);
+ m_snapshot_chainstate =
+ std::make_unique<Chainstate>(mempool, m_blockman, *this, base_blockhash);
+ LogPrintf("[snapshot] switching active chainstate to %s\n", m_snapshot_chainstate->ToString());
+ m_active_chainstate = m_snapshot_chainstate.get();
+ return *m_snapshot_chainstate;
+}
diff --git a/src/validation.h b/src/validation.h
index 9fef69799b..b8151dc1fc 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -43,7 +43,7 @@
#include <utility>
#include <vector>
-class CChainState;
+class Chainstate;
class CBlockTreeDB;
class CTxMemPool;
class ChainstateManager;
@@ -63,11 +63,6 @@ struct Params;
static const int MAX_SCRIPTCHECK_THREADS = 15;
/** -par default (number of script-checking threads, 0 = auto) */
static const int DEFAULT_SCRIPTCHECK_THREADS = 0;
-static const int64_t DEFAULT_MAX_TIP_AGE = 24 * 60 * 60;
-static const bool DEFAULT_CHECKPOINTS_ENABLED = true;
-static const bool DEFAULT_TXINDEX = false;
-static constexpr bool DEFAULT_COINSTATSINDEX{false};
-static const char* const DEFAULT_BLOCKFILTERINDEX = "0";
/** Default for -stopatheight */
static const int DEFAULT_STOPATHEIGHT = 0;
/** Block files containing a block-height within MIN_BLOCKS_TO_KEEP of ActiveChain().Tip() will not be pruned. */
@@ -96,21 +91,6 @@ extern GlobalMutex g_best_block_mutex;
extern std::condition_variable g_best_block_cv;
/** Used to notify getblocktemplate RPC of new tips. */
extern uint256 g_best_block;
-/** Whether there are dedicated script-checking threads running.
- * False indicates all script checking is done on the main threadMessageHandler thread.
- */
-extern bool g_parallel_script_checks;
-extern bool fRequireStandard;
-extern bool fCheckBlockIndex;
-extern bool fCheckpointsEnabled;
-/** If the tip is older than this (in seconds), the node is considered to be in initial block download. */
-extern int64_t nMaxTipAge;
-
-/** Block hash whose ancestors we will assume to have valid scripts without checking them. */
-extern uint256 hashAssumeValid;
-
-/** Minimum work we will assume exists on some valid chain. */
-extern arith_uint256 nMinimumChainWork;
/** Documentation for argument 'checklevel'. */
extern const std::vector<std::string> CHECKLEVEL_DOC;
@@ -128,7 +108,7 @@ bool AbortNode(BlockValidationState& state, const std::string& strMessage, const
double GuessVerificationProgress(const ChainTxData& data, const CBlockIndex* pindex);
/** Prune block files up to a given height */
-void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeight);
+void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight);
/**
* Validation result for a single transaction mempool acceptance.
@@ -148,7 +128,7 @@ struct MempoolAcceptResult {
const TxValidationState m_state;
// The following fields are only present when m_result_type = ResultType::VALID or MEMPOOL_ENTRY
- /** Mempool transactions replaced by the tx per BIP 125 rules. */
+ /** Mempool transactions replaced by the tx. */
const std::optional<std::list<CTransactionRef>> m_replaced_transactions;
/** Virtual size as used by the mempool, calculated using serialized size and sigops. */
const std::optional<int64_t> m_vsize;
@@ -241,7 +221,7 @@ struct PackageMempoolAcceptResult
*
* @returns a MempoolAcceptResult indicating whether the transaction was accepted/rejected with reason.
*/
-MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, const CTransactionRef& tx,
+MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx,
int64_t accept_time, bool bypass_limits, bool test_accept)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@@ -253,16 +233,16 @@ MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, const CTr
* If a transaction fails, validation will exit early and some results may be missing. It is also
* possible for the package to be partially submitted.
*/
-PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTxMemPool& pool,
+PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool,
const Package& txns, bool test_accept)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
-/* Transaction policy functions */
+/* Mempool validation helper functions */
/**
* Check if transaction will be final in the next block to be created.
*/
-bool CheckFinalTxAtTip(const CBlockIndex* active_chain_tip, const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/**
* Check if transaction will be BIP68 final in the next block to be created on top of tip.
@@ -324,7 +304,7 @@ public:
};
/** Initializes the script-execution cache */
-void InitScriptExecutionCache();
+[[nodiscard]] bool InitScriptExecutionCache(size_t max_size_bytes);
/** Functions for validating blocks and updating the block tree */
@@ -334,20 +314,26 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu
/** Check a block is completely valid from start to finish (only works on top of our current best block) */
bool TestBlockValidity(BlockValidationState& state,
const CChainParams& chainparams,
- CChainState& chainstate,
+ Chainstate& chainstate,
const CBlock& block,
CBlockIndex* pindexPrev,
- const std::function<int64_t()>& adjusted_time_callback,
+ const std::function<NodeClock::time_point()>& adjusted_time_callback,
bool fCheckPOW = true,
bool fCheckMerkleRoot = true) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+/** Check with the proof of work on each blockheader matches the value in nBits */
+bool HasValidProofOfWork(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams);
+
+/** Return the sum of the work on a given set of headers */
+arith_uint256 CalculateHeadersWork(const std::vector<CBlockHeader>& headers);
+
/** RAII wrapper for VerifyDB: Verify consistency of the block and coin databases */
class CVerifyDB {
public:
CVerifyDB();
~CVerifyDB();
bool VerifyDB(
- CChainState& chainstate,
+ Chainstate& chainstate,
const Consensus::Params& consensus_params,
CCoinsView& coinsview,
int nCheckLevel,
@@ -363,7 +349,7 @@ enum DisconnectResult
class ConnectTrace;
-/** @see CChainState::FlushStateToDisk */
+/** @see Chainstate::FlushStateToDisk */
enum class FlushStateMode {
NONE,
IF_NEEDED,
@@ -416,7 +402,7 @@ enum class CoinsCacheSizeState
};
/**
- * CChainState stores and provides an API to update our local knowledge of the
+ * Chainstate 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
@@ -429,7 +415,7 @@ enum class CoinsCacheSizeState
* whereas block information and metadata independent of the current tip is
* kept in `BlockManager`.
*/
-class CChainState
+class Chainstate
{
protected:
/**
@@ -467,19 +453,15 @@ protected:
public:
//! Reference to a BlockManager instance which itself is shared across all
- //! CChainState instances.
+ //! Chainstate instances.
node::BlockManager& m_blockman;
- /** Chain parameters for this chainstate */
- /* TODO: replace with m_chainman.GetParams() */
- const CChainParams& m_params;
-
//! The chainstate manager that owns this chainstate. The reference is
//! necessary so that this instance can check whether it is the active
//! chainstate within deeply nested method calls.
ChainstateManager& m_chainman;
- explicit CChainState(
+ explicit Chainstate(
CTxMemPool* mempool,
node::BlockManager& blockman,
ChainstateManager& chainman,
@@ -651,7 +633,7 @@ public:
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex)
LOCKS_EXCLUDED(::cs_main);
- bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, bool min_pow_checked) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
// Block (dis)connection on a given view:
DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view)
@@ -700,7 +682,7 @@ public:
/**
* Make various assertions about the state of the block index.
*
- * By default this only executes fully when using the Regtest chain; see: fCheckBlockIndex.
+ * By default this only executes fully when using the Regtest chain; see: m_options.check_block_index.
*/
void CheckBlockIndex();
@@ -809,7 +791,7 @@ private:
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
//! that call.
- std::unique_ptr<CChainState> m_ibd_chainstate GUARDED_BY(::cs_main);
+ std::unique_ptr<Chainstate> m_ibd_chainstate GUARDED_BY(::cs_main);
//! A chainstate initialized on the basis of a UTXO snapshot. If this is
//! non-null, it is always our active chainstate.
@@ -820,7 +802,7 @@ private:
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
//! that call.
- std::unique_ptr<CChainState> m_snapshot_chainstate GUARDED_BY(::cs_main);
+ std::unique_ptr<Chainstate> m_snapshot_chainstate GUARDED_BY(::cs_main);
//! Points to either the ibd or snapshot chainstate; indicates our
//! most-work chain.
@@ -831,7 +813,7 @@ private:
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
//! that call.
- CChainState* m_active_chainstate GUARDED_BY(::cs_main) {nullptr};
+ Chainstate* m_active_chainstate GUARDED_BY(::cs_main) {nullptr};
//! If true, the assumed-valid chainstate has been fully validated
//! by the background validation chainstate.
@@ -839,36 +821,54 @@ private:
CBlockIndex* m_best_invalid GUARDED_BY(::cs_main){nullptr};
- const CChainParams m_chainparams;
-
- const std::function<int64_t()> m_adjusted_time_callback;
-
//! Internal helper for ActivateSnapshot().
[[nodiscard]] bool PopulateAndValidateSnapshot(
- CChainState& snapshot_chainstate,
+ Chainstate& snapshot_chainstate,
AutoFile& coins_file,
const node::SnapshotMetadata& metadata);
/**
* 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.
+ * Caller must set min_pow_checked=true in order to add a new header to the
+ * block index (permanent memory storage), indicating that the header is
+ * known to be part of a sufficiently high-work chain (anti-dos check).
*/
bool AcceptBlockHeader(
const CBlockHeader& block,
BlockValidationState& state,
- CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
- friend CChainState;
+ CBlockIndex** ppindex,
+ bool min_pow_checked) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ friend Chainstate;
+
+ /** Most recent headers presync progress update, for rate-limiting. */
+ std::chrono::time_point<std::chrono::steady_clock> m_last_presync_update GUARDED_BY(::cs_main) {};
public:
using Options = kernel::ChainstateManagerOpts;
- explicit ChainstateManager(const Options& opts)
- : m_chainparams{opts.chainparams},
- m_adjusted_time_callback{Assert(opts.adjusted_time_callback)} {};
+ explicit ChainstateManager(Options options);
- const CChainParams& GetParams() const { return m_chainparams; }
- const Consensus::Params& GetConsensus() const { return m_chainparams.GetConsensus(); }
+ const CChainParams& GetParams() const { return m_options.chainparams; }
+ const Consensus::Params& GetConsensus() const { return m_options.chainparams.GetConsensus(); }
+ bool ShouldCheckBlockIndex() const { return *Assert(m_options.check_block_index); }
+ const arith_uint256& MinimumChainWork() const { return *Assert(m_options.minimum_chain_work); }
+ const uint256& AssumedValidBlock() const { return *Assert(m_options.assumed_valid_block); }
+ /**
+ * Alias for ::cs_main.
+ * Should be used in new code to make it easier to make ::cs_main a member
+ * of this class.
+ * Generally, methods of this class should be annotated to require this
+ * mutex. This will make calling code more verbose, but also help to:
+ * - Clarify that the method will acquire a mutex that heavily affects
+ * overall performance.
+ * - Force call sites to think how long they need to acquire the mutex to
+ * get consistent results.
+ */
+ RecursiveMutex& GetMutex() const LOCK_RETURNED(::cs_main) { return ::cs_main; }
+
+ const Options m_options;
std::thread m_load_block;
//! A single BlockManager instance is shared across each constructed
//! chainstate to avoid duplicating block metadata.
@@ -906,26 +906,20 @@ public:
//! coins databases. This will be split somehow across chainstates.
int64_t m_total_coinsdb_cache{0};
- //! Instantiate a new chainstate and assign it based upon whether it is
- //! from a snapshot.
+ //! Instantiate a new chainstate.
//!
//! @param[in] mempool The mempool to pass to the chainstate
// constructor
- //! @param[in] snapshot_blockhash If given, signify that this chainstate
- //! is based on a snapshot.
- CChainState& InitializeChainstate(
- CTxMemPool* mempool,
- const std::optional<uint256>& snapshot_blockhash = std::nullopt)
- LIFETIMEBOUND EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ Chainstate& InitializeChainstate(CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Get all chainstates currently being used.
- std::vector<CChainState*> GetAll();
+ std::vector<Chainstate*> GetAll();
//! Construct and activate a Chainstate on the basis of UTXO snapshot data.
//!
//! Steps:
//!
- //! - Initialize an unused CChainState.
+ //! - Initialize an unused Chainstate.
//! - Load its `CoinsViews` contents from `coins_file`.
//! - Verify that the hash of the resulting coinsdb matches the expected hash
//! per assumeutxo chain parameters.
@@ -938,10 +932,10 @@ public:
AutoFile& coins_file, const node::SnapshotMetadata& metadata, bool in_memory);
//! The most-work chain.
- CChainState& ActiveChainstate() const;
- CChain& ActiveChain() const { return ActiveChainstate().m_chain; }
- int ActiveHeight() const { return ActiveChain().Height(); }
- CBlockIndex* ActiveTip() const { return ActiveChain().Tip(); }
+ Chainstate& ActiveChainstate() const;
+ CChain& ActiveChain() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { return ActiveChainstate().m_chain; }
+ int ActiveHeight() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { return ActiveChain().Height(); }
+ CBlockIndex* ActiveTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { return ActiveChain().Tip(); }
node::BlockMap& BlockIndex() EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
@@ -979,10 +973,15 @@ public:
*
* @param[in] block The block we want to process.
* @param[in] force_processing Process this block even if unrequested; used for non-network block sources.
+ * @param[in] min_pow_checked True if proof-of-work anti-DoS checks have
+ * been done by caller for headers chain
+ * (note: only affects headers acceptance; if
+ * block header is already present in block
+ * index then this parameter has no effect)
* @param[out] new_block A boolean which is set to indicate if the block was first received via this call
* @returns If the block was processed, independently of block validity
*/
- bool ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool* new_block) LOCKS_EXCLUDED(cs_main);
+ bool ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked, bool* new_block) LOCKS_EXCLUDED(cs_main);
/**
* Process incoming block headers.
@@ -991,10 +990,11 @@ public:
* validationinterface callback.
*
* @param[in] block The block headers themselves
+ * @param[in] min_pow_checked True if proof-of-work anti-DoS checks have been done by caller for headers chain
* @param[out] state This may be set to an Error state if any error occurred processing them
* @param[out] ppindex If set, the pointer will be set to point to the last new block index object for the given headers
*/
- bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, BlockValidationState& state, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main);
+ bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, bool min_pow_checked, BlockValidationState& state, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main);
/**
* Try to add a transaction to the memory pool.
@@ -1018,6 +1018,23 @@ public:
/** Produce the necessary coinbase commitment for a block (modifies the hash, don't call for mined blocks). */
std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev) const;
+ /** This is used by net_processing to report pre-synchronization progress of headers, as
+ * headers are not yet fed to validation during that time, but validation is (for now)
+ * responsible for logging and signalling through NotifyHeaderTip, so it needs this
+ * information. */
+ void ReportHeadersPresync(const arith_uint256& work, int64_t height, int64_t timestamp);
+
+ //! When starting up, search the datadir for a chainstate based on a UTXO
+ //! snapshot that is in the process of being validated.
+ bool DetectSnapshotChainstate(CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
+ void ResetChainstates() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
+ //! Switch the active chainstate to one based on a UTXO snapshot that was loaded
+ //! previously.
+ Chainstate& ActivateExistingSnapshot(CTxMemPool* mempool, uint256 base_blockhash)
+ EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
~ChainstateManager();
};
diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp
index 60715ff3c8..3a9d277f65 100644
--- a/src/wallet/bdb.cpp
+++ b/src/wallet/bdb.cpp
@@ -432,7 +432,7 @@ void BerkeleyEnvironment::ReloadDbEnv()
});
std::vector<fs::path> filenames;
- for (auto it : m_databases) {
+ for (const auto& it : m_databases) {
filenames.push_back(it.first);
}
// Close the individual Db's
@@ -533,7 +533,7 @@ bool BerkeleyDatabase::Rewrite(const char* pszSkip)
void BerkeleyEnvironment::Flush(bool fShutdown)
{
- int64_t nStart = GetTimeMillis();
+ const auto start{SteadyClock::now()};
// Flush log data to the actual data file on all files that are not in use
LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: [%s] Flush(%s)%s\n", strPath, fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started");
if (!fDbEnvInit)
@@ -561,7 +561,7 @@ void BerkeleyEnvironment::Flush(bool fShutdown)
no_dbs_accessed = false;
}
}
- LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart);
+ LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
if (fShutdown) {
char** listp;
if (no_dbs_accessed) {
@@ -591,14 +591,14 @@ bool BerkeleyDatabase::PeriodicFlush()
const std::string strFile = fs::PathToString(m_filename);
LogPrint(BCLog::WALLETDB, "Flushing %s\n", strFile);
- int64_t nStart = GetTimeMillis();
+ const auto start{SteadyClock::now()};
// Flush wallet file so it's self contained
env->CloseDb(m_filename);
env->CheckpointLSN(strFile);
m_refcount = -1;
- LogPrint(BCLog::WALLETDB, "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart);
+ LogPrint(BCLog::WALLETDB, "Flushed %s %dms\n", strFile, Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
return true;
}
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h
index d08d3664c4..b56a6d3aee 100644
--- a/src/wallet/coincontrol.h
+++ b/src/wallet/coincontrol.h
@@ -37,7 +37,7 @@ public:
bool m_include_unsafe_inputs = false;
//! If true, the selection process can add extra unselected inputs from the wallet
//! while requires all selected inputs be used
- bool m_allow_other_inputs = false;
+ bool m_allow_other_inputs = true;
//! Includes watch only addresses which are solvable
bool fAllowWatchOnly = false;
//! Override automatic min/max checks on fee, m_feerate must be set if true
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index 49e6bac462..a8be6cd83a 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -156,7 +156,7 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
for (const size_t& i : best_selection) {
result.AddInput(utxo_pool.at(i));
}
- result.ComputeAndSetWaste(CAmount{0});
+ result.ComputeAndSetWaste(cost_of_change, cost_of_change, CAmount{0});
assert(best_waste == result.GetWaste());
return result;
@@ -166,6 +166,12 @@ std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& ut
{
SelectionResult result(target_value, SelectionAlgorithm::SRD);
+ // Include change for SRD as we want to avoid making really small change if the selection just
+ // barely meets the target. Just use the lower bound change target instead of the randomly
+ // generated one, since SRD will result in a random change amount anyway; avoid making the
+ // target needlessly large.
+ target_value += CHANGE_LOWER;
+
std::vector<size_t> indexes;
indexes.resize(utxo_pool.size());
std::iota(indexes.begin(), indexes.end(), 0);
@@ -389,20 +395,26 @@ CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost,
return waste;
}
-CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng)
+CAmount GenerateChangeTarget(const CAmount payment_value, const CAmount change_fee, FastRandomContext& rng)
{
if (payment_value <= CHANGE_LOWER / 2) {
- return CHANGE_LOWER;
+ return change_fee + CHANGE_LOWER;
} else {
// random value between 50ksat and min (payment_value * 2, 1milsat)
const auto upper_bound = std::min(payment_value * 2, CHANGE_UPPER);
- return rng.randrange(upper_bound - CHANGE_LOWER) + CHANGE_LOWER;
+ return change_fee + rng.randrange(upper_bound - CHANGE_LOWER) + CHANGE_LOWER;
}
}
-void SelectionResult::ComputeAndSetWaste(CAmount change_cost)
+void SelectionResult::ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee)
{
- m_waste = GetSelectionWaste(m_selected_inputs, change_cost, m_target, m_use_effective);
+ const CAmount change = GetChange(min_viable_change, change_fee);
+
+ if (change > 0) {
+ m_waste = GetSelectionWaste(m_selected_inputs, change_cost, m_target, m_use_effective);
+ } else {
+ m_waste = GetSelectionWaste(m_selected_inputs, 0, m_target, m_use_effective);
+ }
}
CAmount SelectionResult::GetWaste() const
@@ -415,6 +427,11 @@ CAmount SelectionResult::GetSelectedValue() const
return std::accumulate(m_selected_inputs.cbegin(), m_selected_inputs.cend(), CAmount{0}, [](CAmount sum, const auto& coin) { return sum + coin.txout.nValue; });
}
+CAmount SelectionResult::GetSelectedEffectiveValue() const
+{
+ return std::accumulate(m_selected_inputs.cbegin(), m_selected_inputs.cend(), CAmount{0}, [](CAmount sum, const auto& coin) { return sum + coin.GetEffectiveValue(); });
+}
+
void SelectionResult::Clear()
{
m_selected_inputs.clear();
@@ -427,6 +444,22 @@ void SelectionResult::AddInput(const OutputGroup& group)
m_use_effective = !group.m_subtract_fee_outputs;
}
+void SelectionResult::AddInputs(const std::set<COutput>& inputs, bool subtract_fee_outputs)
+{
+ util::insert(m_selected_inputs, inputs);
+ m_use_effective = !subtract_fee_outputs;
+}
+
+void SelectionResult::Merge(const SelectionResult& other)
+{
+ m_target += other.m_target;
+ m_use_effective |= other.m_use_effective;
+ if (m_algo == SelectionAlgorithm::MANUAL) {
+ m_algo = other.m_algo;
+ }
+ util::insert(m_selected_inputs, other.m_selected_inputs);
+}
+
const std::set<COutput>& SelectionResult::GetInputSet() const
{
return m_selected_inputs;
@@ -464,4 +497,24 @@ std::string GetAlgorithmName(const SelectionAlgorithm algo)
}
assert(false);
}
+
+CAmount SelectionResult::GetChange(const CAmount min_viable_change, const CAmount change_fee) const
+{
+ // change = SUM(inputs) - SUM(outputs) - fees
+ // 1) With SFFO we don't pay any fees
+ // 2) Otherwise we pay all the fees:
+ // - input fees are covered by GetSelectedEffectiveValue()
+ // - non_input_fee is included in m_target
+ // - change_fee
+ const CAmount change = m_use_effective
+ ? GetSelectedEffectiveValue() - m_target - change_fee
+ : GetSelectedValue() - m_target;
+
+ if (change < min_viable_change) {
+ return 0;
+ }
+
+ return change;
+}
+
} // namespace wallet
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index 9135e48104..b23dd10867 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -123,10 +123,12 @@ struct CoinSelectionParams {
/** Mininmum change to target in Knapsack solver: select coins to cover the payment and
* at least this value of change. */
CAmount m_min_change_target{0};
+ /** Minimum amount for creating a change output.
+ * If change budget is smaller than min_change then we forgo creation of change output.
+ */
+ CAmount min_viable_change{0};
/** Cost of creating the change output. */
CAmount m_change_fee{0};
- /** The pre-determined minimum value to target when funding a change output. */
- CAmount m_change_target{0};
/** Cost of creating the change output + cost of spending the change output in the future. */
CAmount m_cost_of_change{0};
/** The targeted feerate of the transaction being built. */
@@ -254,6 +256,7 @@ struct OutputGroup
/** Choose a random change target for each transaction to make it harder to fingerprint the Core
* wallet based on the change output values of transactions it creates.
+ * Change target covers at least change fees and adds a random value on top of it.
* The random value is between 50ksat and min(2 * payment_value, 1milsat)
* When payment_value <= 25ksat, the value is just 50ksat.
*
@@ -263,8 +266,9 @@ struct OutputGroup
* coins selected are just sufficient to cover the payment amount ("unnecessary input" heuristic).
*
* @param[in] payment_value Average payment value of the transaction output(s).
+ * @param[in] change_fee Fee for creating a change output.
*/
-[[nodiscard]] CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng);
+[[nodiscard]] CAmount GenerateChangeTarget(const CAmount payment_value, const CAmount change_fee, FastRandomContext& rng);
enum class SelectionAlgorithm : uint8_t
{
@@ -281,17 +285,16 @@ struct SelectionResult
private:
/** Set of inputs selected by the algorithm to use in the transaction */
std::set<COutput> m_selected_inputs;
+ /** The target the algorithm selected for. Equal to the recipient amount plus non-input fees */
+ CAmount m_target;
+ /** The algorithm used to produce this result */
+ SelectionAlgorithm m_algo;
/** Whether the input values for calculations should be the effective value (true) or normal value (false) */
bool m_use_effective{false};
/** The computed waste */
std::optional<CAmount> m_waste;
public:
- /** The target the algorithm selected for. Note that this may not be equal to the recipient amount as it can include non-input fees */
- const CAmount m_target;
- /** The algorithm used to produce this result */
- const SelectionAlgorithm m_algo;
-
explicit SelectionResult(const CAmount target, SelectionAlgorithm algo)
: m_target(target), m_algo(algo) {}
@@ -300,20 +303,48 @@ public:
/** Get the sum of the input values */
[[nodiscard]] CAmount GetSelectedValue() const;
+ [[nodiscard]] CAmount GetSelectedEffectiveValue() const;
+
void Clear();
void AddInput(const OutputGroup& group);
+ void AddInputs(const std::set<COutput>& inputs, bool subtract_fee_outputs);
/** Calculates and stores the waste for this selection via GetSelectionWaste */
- void ComputeAndSetWaste(CAmount change_cost);
+ void ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee);
[[nodiscard]] CAmount GetWaste() const;
+ void Merge(const SelectionResult& other);
+
/** Get m_selected_inputs */
const std::set<COutput>& GetInputSet() const;
/** Get the vector of COutputs that will be used to fill in a CTransaction's vin */
std::vector<COutput> GetShuffledInputVector() const;
bool operator<(SelectionResult other) const;
+
+ /** Get the amount for the change output after paying needed fees.
+ *
+ * The change amount is not 100% precise due to discrepancies in fee calculation.
+ * The final change amount (if any) should be corrected after calculating the final tx fees.
+ * When there is a discrepancy, most of the time the final change would be slightly bigger than estimated.
+ *
+ * Following are the possible factors of discrepancy:
+ * + non-input fees always include segwit flags
+ * + input fee estimation always include segwit stack size
+ * + input fees are rounded individually and not collectively, which leads to small rounding errors
+ * - input counter size is always assumed to be 1vbyte
+ *
+ * @param[in] min_viable_change Minimum amount for change output, if change would be less then we forgo change
+ * @param[in] change_fee Fees to include change output in the tx
+ * @returns Amount for change output, 0 when there is no change.
+ *
+ */
+ CAmount GetChange(const CAmount min_viable_change, const CAmount change_fee) const;
+
+ CAmount GetTarget() const { return m_target; }
+
+ SelectionAlgorithm GetAlgo() const { return m_algo; }
};
std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change);
diff --git a/src/wallet/external_signer_scriptpubkeyman.cpp b/src/wallet/external_signer_scriptpubkeyman.cpp
index 9d5f58b784..76de51ac3e 100644
--- a/src/wallet/external_signer_scriptpubkeyman.cpp
+++ b/src/wallet/external_signer_scriptpubkeyman.cpp
@@ -45,7 +45,8 @@ ExternalSigner ExternalSignerScriptPubKeyMan::GetExternalSigner() {
std::vector<ExternalSigner> signers;
ExternalSigner::Enumerate(command, signers, Params().NetworkIDString());
if (signers.empty()) throw std::runtime_error(std::string(__func__) + ": No external signers found");
- // TODO: add fingerprint argument in case of multiple signers
+ // TODO: add fingerprint argument instead of failing in case of multiple signers.
+ if (signers.size() > 1) throw std::runtime_error(std::string(__func__) + ": More than one external signer found. Please connect only one at a time.");
return signers[0];
}
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 43fdcee48d..6d7bb299cc 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.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 <consensus/validation.h>
#include <interfaces/chain.h>
#include <policy/fees.h>
#include <policy/policy.h>
@@ -19,9 +20,9 @@
namespace wallet {
//! Check whether transaction has descendant in wallet or mempool, or has been
//! mined, or conflicts with a mined transaction. Return a feebumper::Result.
-static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWalletTx& wtx, std::vector<bilingual_str>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
+static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWalletTx& wtx, bool require_mine, std::vector<bilingual_str>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
{
- if (wallet.HasWalletSpend(wtx.GetHash())) {
+ if (wallet.HasWalletSpend(wtx.tx)) {
errors.push_back(Untranslated("Transaction has descendants in the wallet"));
return feebumper::Result::INVALID_PARAMETER;
}
@@ -48,20 +49,21 @@ static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWallet
return feebumper::Result::WALLET_ERROR;
}
- // check that original tx consists entirely of our inputs
- // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee)
- isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
- if (!AllInputsMine(wallet, *wtx.tx, filter)) {
- errors.push_back(Untranslated("Transaction contains inputs that don't belong to this wallet"));
- return feebumper::Result::WALLET_ERROR;
+ if (require_mine) {
+ // check that original tx consists entirely of our inputs
+ // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee)
+ isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
+ if (!AllInputsMine(wallet, *wtx.tx, filter)) {
+ errors.push_back(Untranslated("Transaction contains inputs that don't belong to this wallet"));
+ return feebumper::Result::WALLET_ERROR;
+ }
}
-
return feebumper::Result::OK;
}
//! Check if the user provided a valid feeRate
-static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CFeeRate& newFeerate, const int64_t maxTxSize, std::vector<bilingual_str>& errors)
+static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CFeeRate& newFeerate, const int64_t maxTxSize, CAmount old_fee, std::vector<bilingual_str>& errors)
{
// check that fee rate is higher than mempool's minimum fee
// (no point in bumping fee if we know that the new tx won't be accepted to the mempool)
@@ -83,8 +85,6 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt
CFeeRate incrementalRelayFee = std::max(wallet.chain().relayIncrementalFee(), CFeeRate(WALLET_INCREMENTAL_RELAY_FEE));
// Given old total fee and transaction size, calculate the old feeRate
- isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
- CAmount old_fee = CachedTxGetDebit(wallet, wtx, filter) - wtx.tx->GetValueOut();
const int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
CFeeRate nOldFeeRate(old_fee, txSize);
// Min total fee is old fee + relay fee
@@ -128,8 +128,8 @@ static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, con
// WALLET_INCREMENTAL_RELAY_FEE value to future proof against changes to
// network wide policy for incremental relay fee that our node may not be
// aware of. This ensures we're over the required relay fee rate
- // (BIP 125 rule 4). The replacement tx will be at least as large as the
- // original tx, so the total fee will be greater (BIP 125 rule 3)
+ // (Rule 4). The replacement tx will be at least as large as the
+ // original tx, so the total fee will be greater (Rule 3)
CFeeRate node_incremental_relay_fee = wallet.chain().relayIncrementalFee();
CFeeRate wallet_incremental_relay_fee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE);
feerate += std::max(node_incremental_relay_fee, wallet_incremental_relay_fee);
@@ -150,12 +150,12 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid)
if (wtx == nullptr) return false;
std::vector<bilingual_str> errors_dummy;
- feebumper::Result res = PreconditionChecks(wallet, *wtx, errors_dummy);
+ feebumper::Result res = PreconditionChecks(wallet, *wtx, /* require_mine=*/ true, errors_dummy);
return res == feebumper::Result::OK;
}
Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<bilingual_str>& errors,
- CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx)
+ CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, bool require_mine)
{
// We are going to modify coin control later, copy to re-use
CCoinControl new_coin_control(coin_control);
@@ -169,13 +169,63 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
}
const CWalletTx& wtx = it->second;
- Result result = PreconditionChecks(wallet, wtx, errors);
+ // Retrieve all of the UTXOs and add them to coin control
+ // While we're here, calculate the input amount
+ std::map<COutPoint, Coin> coins;
+ CAmount input_value = 0;
+ std::vector<CTxOut> spent_outputs;
+ for (const CTxIn& txin : wtx.tx->vin) {
+ coins[txin.prevout]; // Create empty map entry keyed by prevout.
+ }
+ wallet.chain().findCoins(coins);
+ for (const CTxIn& txin : wtx.tx->vin) {
+ const Coin& coin = coins.at(txin.prevout);
+ if (coin.out.IsNull()) {
+ errors.push_back(Untranslated(strprintf("%s:%u is already spent", txin.prevout.hash.GetHex(), txin.prevout.n)));
+ return Result::MISC_ERROR;
+ }
+ if (wallet.IsMine(txin.prevout)) {
+ new_coin_control.Select(txin.prevout);
+ } else {
+ new_coin_control.SelectExternal(txin.prevout, coin.out);
+ }
+ input_value += coin.out.nValue;
+ spent_outputs.push_back(coin.out);
+ }
+
+ // Figure out if we need to compute the input weight, and do so if necessary
+ PrecomputedTransactionData txdata;
+ txdata.Init(*wtx.tx, std::move(spent_outputs), /* force=*/ true);
+ for (unsigned int i = 0; i < wtx.tx->vin.size(); ++i) {
+ const CTxIn& txin = wtx.tx->vin.at(i);
+ const Coin& coin = coins.at(txin.prevout);
+
+ if (new_coin_control.IsExternalSelected(txin.prevout)) {
+ // For external inputs, we estimate the size using the size of this input
+ int64_t input_weight = GetTransactionInputWeight(txin);
+ // Because signatures can have different sizes, we need to figure out all of the
+ // signature sizes and replace them with the max sized signature.
+ // In order to do this, we verify the script with a special SignatureChecker which
+ // will observe the signatures verified and record their sizes.
+ SignatureWeights weights;
+ TransactionSignatureChecker tx_checker(wtx.tx.get(), i, coin.out.nValue, txdata, MissingDataBehavior::FAIL);
+ SignatureWeightChecker size_checker(weights, tx_checker);
+ VerifyScript(txin.scriptSig, coin.out.scriptPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, size_checker);
+ // Add the difference between max and current to input_weight so that it represents the largest the input could be
+ input_weight += weights.GetWeightDiffToMax();
+ new_coin_control.SetInputWeight(txin.prevout, input_weight);
+ }
+ }
+
+ Result result = PreconditionChecks(wallet, wtx, require_mine, errors);
if (result != Result::OK) {
return result;
}
// Fill in recipients(and preserve a single change key if there is one)
+ // While we're here, calculate the output amount
std::vector<CRecipient> recipients;
+ CAmount output_value = 0;
for (const auto& output : wtx.tx->vout) {
if (!OutputIsChange(wallet, output)) {
CRecipient recipient = {output.scriptPubKey, output.nValue, false};
@@ -185,16 +235,22 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
ExtractDestination(output.scriptPubKey, change_dest);
new_coin_control.destChange = change_dest;
}
+ output_value += output.nValue;
}
- isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
- old_fee = CachedTxGetDebit(wallet, wtx, filter) - wtx.tx->GetValueOut();
+ old_fee = input_value - output_value;
if (coin_control.m_feerate) {
// The user provided a feeRate argument.
// We calculate this here to avoid compiler warning on the cs_wallet lock
- const int64_t maxTxSize{CalculateMaximumSignedTxSize(*wtx.tx, &wallet).vsize};
- Result res = CheckFeeRate(wallet, wtx, *new_coin_control.m_feerate, maxTxSize, errors);
+ // We need to make a temporary transaction with no input witnesses as the dummy signer expects them to be empty for external inputs
+ CMutableTransaction mtx{*wtx.tx};
+ for (auto& txin : mtx.vin) {
+ txin.scriptSig.clear();
+ txin.scriptWitness.SetNull();
+ }
+ const int64_t maxTxSize{CalculateMaximumSignedTxSize(CTransaction(mtx), &wallet, &new_coin_control).vsize};
+ Result res = CheckFeeRate(wallet, wtx, *new_coin_control.m_feerate, maxTxSize, old_fee, errors);
if (res != Result::OK) {
return res;
}
@@ -221,11 +277,11 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
constexpr int RANDOM_CHANGE_POSITION = -1;
auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, new_coin_control, false);
if (!res) {
- errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + res.GetError());
+ errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + util::ErrorString(res));
return Result::WALLET_ERROR;
}
- const auto& txr = res.GetObj();
+ const auto& txr = *res;
// Write back new fee if successful
new_fee = txr.fee;
@@ -254,7 +310,7 @@ Result CommitTransaction(CWallet& wallet, const uint256& txid, CMutableTransacti
const CWalletTx& oldWtx = it->second;
// make sure the transaction still has no descendants and hasn't been mined in the meantime
- Result result = PreconditionChecks(wallet, oldWtx, errors);
+ Result result = PreconditionChecks(wallet, oldWtx, /* require_mine=*/ false, errors);
if (result != Result::OK) {
return result;
}
diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h
index 191878a137..760ab58e5c 100644
--- a/src/wallet/feebumper.h
+++ b/src/wallet/feebumper.h
@@ -5,6 +5,8 @@
#ifndef BITCOIN_WALLET_FEEBUMPER_H
#define BITCOIN_WALLET_FEEBUMPER_H
+#include <consensus/consensus.h>
+#include <script/interpreter.h>
#include <primitives/transaction.h>
class uint256;
@@ -31,14 +33,25 @@ enum class Result
//! Return whether transaction can be bumped.
bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid);
-//! Create bumpfee transaction based on feerate estimates.
+/** Create bumpfee transaction based on feerate estimates.
+ *
+ * @param[in] wallet The wallet to use for this bumping
+ * @param[in] txid The txid of the transaction to bump
+ * @param[in] coin_control A CCoinControl object which provides feerates and other information used for coin selection
+ * @param[out] errors Errors
+ * @param[out] old_fee The fee the original transaction pays
+ * @param[out] new_fee the fee that the bump transaction pays
+ * @param[out] mtx The bump transaction itself
+ * @param[in] require_mine Whether the original transaction must consist of inputs that can be spent by the wallet
+ */
Result CreateRateBumpTransaction(CWallet& wallet,
const uint256& txid,
const CCoinControl& coin_control,
std::vector<bilingual_str>& errors,
CAmount& old_fee,
CAmount& new_fee,
- CMutableTransaction& mtx);
+ CMutableTransaction& mtx,
+ bool require_mine);
//! Sign the new transaction,
//! @return false if the tx couldn't be found or if it was
@@ -55,6 +68,55 @@ Result CommitTransaction(CWallet& wallet,
std::vector<bilingual_str>& errors,
uint256& bumped_txid);
+struct SignatureWeights
+{
+private:
+ int m_sigs_count{0};
+ int64_t m_sigs_weight{0};
+
+public:
+ void AddSigWeight(const size_t weight, const SigVersion sigversion)
+ {
+ switch (sigversion) {
+ case SigVersion::BASE:
+ m_sigs_weight += weight * WITNESS_SCALE_FACTOR;
+ m_sigs_count += 1 * WITNESS_SCALE_FACTOR;
+ break;
+ case SigVersion::WITNESS_V0:
+ m_sigs_weight += weight;
+ m_sigs_count++;
+ break;
+ case SigVersion::TAPROOT:
+ case SigVersion::TAPSCRIPT:
+ assert(false);
+ }
+ }
+
+ int64_t GetWeightDiffToMax() const
+ {
+ // Note: the witness scaling factor is already accounted for because the count is multiplied by it.
+ return (/* max signature size=*/ 72 * m_sigs_count) - m_sigs_weight;
+ }
+};
+
+class SignatureWeightChecker : public DeferringSignatureChecker
+{
+private:
+ SignatureWeights& m_weights;
+
+public:
+ SignatureWeightChecker(SignatureWeights& weights, const BaseSignatureChecker& checker) : DeferringSignatureChecker(checker), m_weights(weights) {}
+
+ bool CheckECDSASignature(const std::vector<unsigned char>& sig, const std::vector<unsigned char>& pubkey, const CScript& script, SigVersion sigversion) const override
+ {
+ if (m_checker.CheckECDSASignature(sig, pubkey, script, sigversion)) {
+ m_weights.AddSigWeight(sig.size(), sigversion);
+ return true;
+ }
+ return false;
+ }
+};
+
} // namespace feebumper
} // namespace wallet
diff --git a/src/wallet/fees.cpp b/src/wallet/fees.cpp
index 6f81fa30a1..3514d018b7 100644
--- a/src/wallet/fees.cpp
+++ b/src/wallet/fees.cpp
@@ -87,7 +87,7 @@ CFeeRate GetDiscardRate(const CWallet& wallet)
CFeeRate discard_rate = wallet.chain().estimateSmartFee(highest_target, false /* conservative */);
// Don't let discard_rate be greater than longest possible fee estimate if we get a valid fee estimate
discard_rate = (discard_rate == CFeeRate(0)) ? wallet.m_discard_rate : std::min(discard_rate, wallet.m_discard_rate);
- // Discard rate must be at least dustRelayFee
+ // Discard rate must be at least dust relay feerate
discard_rate = std::max(discard_rate, wallet.chain().relayDustFee());
return discard_rate;
}
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index 4f30060e29..9cf2b677e6 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -148,7 +148,7 @@ 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(); }
- BResult<CTxDestination> getNewDestination(const OutputType type, const std::string label) override
+ util::Result<CTxDestination> getNewDestination(const OutputType type, const std::string& label) override
{
LOCK(m_wallet->cs_wallet);
return m_wallet->GetNewDestination(type, label);
@@ -251,17 +251,17 @@ public:
LOCK(m_wallet->cs_wallet);
return m_wallet->ListLockedCoins(outputs);
}
- BResult<CTransactionRef> createTransaction(const std::vector<CRecipient>& recipients,
+ util::Result<CTransactionRef> createTransaction(const std::vector<CRecipient>& recipients,
const CCoinControl& coin_control,
bool sign,
int& change_pos,
CAmount& fee) override
{
LOCK(m_wallet->cs_wallet);
- const auto& res = CreateTransaction(*m_wallet, recipients, change_pos,
+ auto res = CreateTransaction(*m_wallet, recipients, change_pos,
coin_control, sign);
- if (!res) return res.GetError();
- const auto& txr = res.GetObj();
+ if (!res) return util::Error{util::ErrorString(res)};
+ const auto& txr = *res;
fee = txr.fee;
change_pos = txr.change_pos;
@@ -291,7 +291,7 @@ public:
CAmount& new_fee,
CMutableTransaction& mtx) override
{
- return feebumper::CreateRateBumpTransaction(*m_wallet.get(), txid, coin_control, errors, old_fee, new_fee, mtx) == feebumper::Result::OK;
+ return feebumper::CreateRateBumpTransaction(*m_wallet.get(), txid, coin_control, errors, old_fee, new_fee, mtx, /* require_mine= */ true) == feebumper::Result::OK;
}
bool signBumpTransaction(CMutableTransaction& mtx) override { return feebumper::SignTransaction(*m_wallet.get(), mtx); }
bool commitBumpTransaction(const uint256& txid,
@@ -320,13 +320,12 @@ public:
}
return {};
}
- std::vector<WalletTx> getWalletTxs() override
+ std::set<WalletTx> getWalletTxs() override
{
LOCK(m_wallet->cs_wallet);
- std::vector<WalletTx> result;
- result.reserve(m_wallet->mapWallet.size());
+ std::set<WalletTx> result;
for (const auto& entry : m_wallet->mapWallet) {
- result.emplace_back(MakeWalletTx(*m_wallet, entry.second));
+ result.emplace(MakeWalletTx(*m_wallet, entry.second));
}
return result;
}
@@ -552,32 +551,46 @@ public:
void setMockTime(int64_t time) override { return SetMockTime(time); }
//! WalletLoader methods
- std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) override
+ util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) override
{
- std::shared_ptr<CWallet> wallet;
DatabaseOptions options;
DatabaseStatus status;
ReadDatabaseArgs(*m_context.args, options);
options.require_create = true;
options.create_flags = wallet_creation_flags;
options.create_passphrase = passphrase;
- return MakeWallet(m_context, CreateWallet(m_context, name, true /* load_on_start */, options, status, error, warnings));
+ bilingual_str error;
+ std::unique_ptr<Wallet> wallet{MakeWallet(m_context, CreateWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))};
+ if (wallet) {
+ return {std::move(wallet)};
+ } else {
+ return util::Error{error};
+ }
}
- std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) override
+ util::Result<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings) override
{
DatabaseOptions options;
DatabaseStatus status;
ReadDatabaseArgs(*m_context.args, options);
options.require_existing = true;
- return MakeWallet(m_context, LoadWallet(m_context, name, true /* load_on_start */, options, status, error, warnings));
+ bilingual_str error;
+ std::unique_ptr<Wallet> wallet{MakeWallet(m_context, LoadWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))};
+ if (wallet) {
+ return {std::move(wallet)};
+ } else {
+ return util::Error{error};
+ }
}
- BResult<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) override
+ util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) override
{
DatabaseStatus status;
bilingual_str error;
- BResult<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))};
- if (!wallet) return error;
- return wallet;
+ std::unique_ptr<Wallet> wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))};
+ if (wallet) {
+ return {std::move(wallet)};
+ } else {
+ return util::Error{error};
+ }
}
std::string getWalletDir() override
{
diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp
index c06513588b..6eb0ef5e7a 100644
--- a/src/wallet/load.cpp
+++ b/src/wallet/load.cpp
@@ -151,7 +151,7 @@ void StartWallets(WalletContext& context, CScheduler& scheduler)
if (context.args->GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) {
scheduler.scheduleEvery([&context] { MaybeCompactWalletDB(context); }, std::chrono::milliseconds{500});
}
- scheduler.scheduleEvery([&context] { MaybeResendWalletTxs(context); }, std::chrono::milliseconds{1000});
+ scheduler.scheduleEvery([&context] { MaybeResendWalletTxs(context); }, 1min);
}
void FlushWallets(WalletContext& context)
diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp
index 944925d600..7fbf2ff8cf 100644
--- a/src/wallet/receive.cpp
+++ b/src/wallet/receive.cpp
@@ -193,7 +193,8 @@ CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx,
void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx,
std::list<COutputEntry>& listReceived,
- std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter)
+ std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter,
+ bool include_change)
{
nFee = 0;
listReceived.clear();
@@ -218,8 +219,7 @@ void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx,
// 2) the output is to us (received)
if (nDebit > 0)
{
- // Don't report 'change' txouts
- if (OutputIsChange(wallet, txout))
+ if (!include_change && OutputIsChange(wallet, txout))
continue;
}
else if (!(fIsMine & filter))
@@ -416,7 +416,7 @@ std::set< std::set<CTxDestination> > GetAddressGroupings(const CWallet& wallet)
std::set< std::set<CTxDestination>* > uniqueGroupings; // a set of pointers to groups of addresses
std::map< CTxDestination, std::set<CTxDestination>* > setmap; // map addresses to the unique group containing it
- for (std::set<CTxDestination> _grouping : groupings)
+ for (const std::set<CTxDestination>& _grouping : groupings)
{
// make a set of all the groups hit by this new group
std::set< std::set<CTxDestination>* > hits;
diff --git a/src/wallet/receive.h b/src/wallet/receive.h
index 41a70b089a..9125b1e9aa 100644
--- a/src/wallet/receive.h
+++ b/src/wallet/receive.h
@@ -42,7 +42,8 @@ struct COutputEntry
void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx,
std::list<COutputEntry>& listReceived,
std::list<COutputEntry>& listSent,
- CAmount& nFee, const isminefilter& filter);
+ CAmount& nFee, const isminefilter& filter,
+ bool include_change);
bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter);
bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set<uint256>& trusted_parents) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx);
diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp
index 2a6eb96871..903a569cb9 100644
--- a/src/wallet/rpc/addresses.cpp
+++ b/src/wallet/rpc/addresses.cpp
@@ -60,10 +60,10 @@ RPCHelpMan getnewaddress()
auto op_dest = pwallet->GetNewDestination(output_type, label);
if (!op_dest) {
- throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, op_dest.GetError().original);
+ throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original);
}
- return EncodeDestination(op_dest.GetObj());
+ return EncodeDestination(*op_dest);
},
};
}
@@ -107,9 +107,9 @@ RPCHelpMan getrawchangeaddress()
auto op_dest = pwallet->GetNewChangeDestination(output_type);
if (!op_dest) {
- throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, op_dest.GetError().original);
+ throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original);
}
- return EncodeDestination(op_dest.GetObj());
+ return EncodeDestination(*op_dest);
},
};
}
@@ -578,7 +578,7 @@ RPCHelpMan getaddressinfo()
if (provider) {
auto inferred = InferDescriptor(scriptPubKey, *provider);
- bool solvable = inferred->IsSolvable() || IsSolvable(*provider, scriptPubKey);
+ bool solvable = inferred->IsSolvable();
ret.pushKV("solvable", solvable);
if (solvable) {
ret.pushKV("desc", inferred->ToString());
diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp
index 306053fd0c..1d2d7d2a10 100644
--- a/src/wallet/rpc/backup.cpp
+++ b/src/wallet/rpc/backup.cpp
@@ -101,7 +101,7 @@ RPCHelpMan importprivkey()
"\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
"may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
"The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
- "but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
+ "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n"
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
{
{"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key (see dumpprivkey)"},
@@ -204,7 +204,7 @@ RPCHelpMan importaddress()
"\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
"may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
"The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
- "but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
+ "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n"
"If you have the full public key, you should call importpubkey instead of this.\n"
"Hint: use importmulti to import more than one address.\n"
"\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n"
@@ -293,10 +293,7 @@ RPCHelpMan importaddress()
if (fRescan)
{
RescanWallet(*pwallet, reserver);
- {
- LOCK(pwallet->cs_wallet);
- pwallet->ReacceptWalletTransactions();
- }
+ pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
}
return UniValue::VNULL;
@@ -406,7 +403,7 @@ RPCHelpMan importpubkey()
"\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
"may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
"The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
- "but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
+ "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n"
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
{
{"pubkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The hex-encoded public key"},
@@ -474,10 +471,7 @@ RPCHelpMan importpubkey()
if (fRescan)
{
RescanWallet(*pwallet, reserver);
- {
- LOCK(pwallet->cs_wallet);
- pwallet->ReacceptWalletTransactions();
- }
+ pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
}
return UniValue::VNULL;
@@ -1257,7 +1251,7 @@ RPCHelpMan importmulti()
"\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
"may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
"The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
- "but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
+ "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n"
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
{
{"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
@@ -1266,15 +1260,15 @@ RPCHelpMan importmulti()
{
{"desc", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Descriptor to import. If using descriptor, do not also provide address/scriptPubKey, scripts, or pubkeys"},
{"scriptPubKey", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of scriptPubKey (string for script, json for address). Should not be provided if using a descriptor",
- /*oneline_description=*/"", {"\"<script>\" | { \"address\":\"<address>\" }", "string / json"}
+ RPCArgOptions{.type_str={"\"<script>\" | { \"address\":\"<address>\" }", "string / json"}}
},
{"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Creation time of the key expressed in " + UNIX_EPOCH_TIME + ",\n"
- " or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n"
- " key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n"
- " \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
- " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n"
- " creation time of all keys being imported by the importmulti call will be scanned.",
- /*oneline_description=*/"", {"timestamp | \"now\"", "integer / string"}
+ "or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n"
+ "key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n"
+ "\"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
+ "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n"
+ "creation time of all keys being imported by the importmulti call will be scanned.",
+ RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}}
},
{"redeemscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey"},
{"witnessscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey"},
@@ -1296,12 +1290,12 @@ RPCHelpMan importmulti()
},
},
},
- "\"requests\""},
+ RPCArgOptions{.oneline_description="\"requests\""}},
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
{
{"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions after all imports."},
},
- "\"options\""},
+ RPCArgOptions{.oneline_description="\"options\""}},
},
RPCResult{
RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
@@ -1363,7 +1357,18 @@ RPCHelpMan importmulti()
UniValue response(UniValue::VARR);
{
LOCK(pwallet->cs_wallet);
- EnsureWalletIsUnlocked(*pwallet);
+
+ // Check all requests are watchonly
+ bool is_watchonly{true};
+ for (size_t i = 0; i < requests.size(); ++i) {
+ const UniValue& request = requests[i];
+ if (!request.exists("watchonly") || !request["watchonly"].get_bool()) {
+ is_watchonly = false;
+ break;
+ }
+ }
+ // Wallet does not need to be unlocked if all requests are watchonly
+ if (!is_watchonly) EnsureWalletIsUnlocked(wallet);
// Verify all timestamps are present before importing any keys.
CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nLowestTimestamp).mtpTime(now)));
@@ -1395,10 +1400,7 @@ RPCHelpMan importmulti()
}
if (fRescan && fRunScan && requests.size()) {
int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */);
- {
- LOCK(pwallet->cs_wallet);
- pwallet->ReacceptWalletTransactions();
- }
+ pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
if (pwallet->IsAbortingRescan()) {
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
@@ -1587,7 +1589,8 @@ RPCHelpMan importdescriptors()
return RPCHelpMan{"importdescriptors",
"\nImport descriptors. This will trigger a rescan of the blockchain based on the earliest timestamp of all descriptors being imported. Requires a new wallet backup.\n"
"\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
- "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n",
+ "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
+ "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",
{
{"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
{
@@ -1598,18 +1601,18 @@ RPCHelpMan importdescriptors()
{"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in the form [begin,end]) to import"},
{"next_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "If a ranged descriptor is set to active, this specifies the next index to generate addresses from"},
{"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n"
- " Use the string \"now\" to substitute the current synced blockchain time.\n"
- " \"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
- " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
+ "Use the string \"now\" to substitute the current synced blockchain time.\n"
+ "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
+ "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
"of all descriptors being imported will be scanned as well as the mempool.",
- /*oneline_description=*/"", {"timestamp | \"now\"", "integer / string"}
+ RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}}
},
{"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
{"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"},
},
},
},
- "\"requests\""},
+ RPCArgOptions{.oneline_description="\"requests\""}},
},
RPCResult{
RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
@@ -1689,10 +1692,7 @@ RPCHelpMan importdescriptors()
// Rescan the blockchain using the lowest timestamp
if (rescan) {
int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver, true /* update */);
- {
- LOCK(pwallet->cs_wallet);
- pwallet->ReacceptWalletTransactions();
- }
+ pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
if (pwallet->IsAbortingRescan()) {
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
@@ -1749,7 +1749,7 @@ RPCHelpMan listdescriptors()
},
RPCResult{RPCResult::Type::OBJ, "", "", {
{RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
- {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects",
+ {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)",
{
{RPCResult::Type::OBJ, "", "", {
{RPCResult::Type::STR, "desc", "Descriptor string representation"},
@@ -1784,34 +1784,59 @@ RPCHelpMan listdescriptors()
LOCK(wallet->cs_wallet);
- UniValue descriptors(UniValue::VARR);
const auto active_spk_mans = wallet->GetActiveScriptPubKeyMans();
+
+ struct WalletDescInfo {
+ std::string descriptor;
+ uint64_t creation_time;
+ bool active;
+ std::optional<bool> internal;
+ std::optional<std::pair<int64_t,int64_t>> range;
+ int64_t next_index;
+ };
+
+ std::vector<WalletDescInfo> wallet_descriptors;
for (const auto& spk_man : wallet->GetAllScriptPubKeyMans()) {
const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
if (!desc_spk_man) {
throw JSONRPCError(RPC_WALLET_ERROR, "Unexpected ScriptPubKey manager type.");
}
- UniValue spk(UniValue::VOBJ);
LOCK(desc_spk_man->cs_desc_man);
const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();
std::string descriptor;
-
if (!desc_spk_man->GetDescriptorString(descriptor, priv)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Can't get descriptor string.");
}
- spk.pushKV("desc", descriptor);
- spk.pushKV("timestamp", wallet_descriptor.creation_time);
- spk.pushKV("active", active_spk_mans.count(desc_spk_man) != 0);
- const auto internal = wallet->IsInternalScriptPubKeyMan(desc_spk_man);
- if (internal.has_value()) {
- spk.pushKV("internal", *internal);
+ const bool is_range = wallet_descriptor.descriptor->IsRange();
+ wallet_descriptors.push_back({
+ descriptor,
+ wallet_descriptor.creation_time,
+ active_spk_mans.count(desc_spk_man) != 0,
+ wallet->IsInternalScriptPubKeyMan(desc_spk_man),
+ is_range ? std::optional(std::make_pair(wallet_descriptor.range_start, wallet_descriptor.range_end)) : std::nullopt,
+ wallet_descriptor.next_index
+ });
+ }
+
+ std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) {
+ return a.descriptor < b.descriptor;
+ });
+
+ UniValue descriptors(UniValue::VARR);
+ for (const WalletDescInfo& info : wallet_descriptors) {
+ UniValue spk(UniValue::VOBJ);
+ spk.pushKV("desc", info.descriptor);
+ spk.pushKV("timestamp", info.creation_time);
+ spk.pushKV("active", info.active);
+ if (info.internal.has_value()) {
+ spk.pushKV("internal", info.internal.value());
}
- if (wallet_descriptor.descriptor->IsRange()) {
+ if (info.range.has_value()) {
UniValue range(UniValue::VARR);
- range.push_back(wallet_descriptor.range_start);
- range.push_back(wallet_descriptor.range_end - 1);
+ range.push_back(info.range->first);
+ range.push_back(info.range->second - 1);
spk.pushKV("range", range);
- spk.pushKV("next", wallet_descriptor.next_index);
+ spk.pushKV("next", info.next_index);
}
descriptors.push_back(spk);
}
@@ -1863,7 +1888,9 @@ RPCHelpMan restorewallet()
{
return RPCHelpMan{
"restorewallet",
- "\nRestore and loads a wallet from backup.\n",
+ "\nRestore and loads a wallet from backup.\n"
+ "\nThe rescan is significantly faster if a descriptor wallet is restored"
+ "\nand block filters are available (using startup option \"-blockfilterindex=1\").\n",
{
{"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
{"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp
index d40d800f28..9c0c953a7a 100644
--- a/src/wallet/rpc/coins.cpp
+++ b/src/wallet/rpc/coins.cpp
@@ -290,8 +290,6 @@ RPCHelpMan lockunspent()
LOCK(pwallet->cs_wallet);
- RPCTypeCheckArgument(request.params[0], UniValue::VBOOL);
-
bool fUnlock = request.params[0].get_bool();
const bool persistent{request.params[2].isNull() ? false : request.params[2].get_bool()};
@@ -304,9 +302,7 @@ RPCHelpMan lockunspent()
return true;
}
- RPCTypeCheckArgument(request.params[1], UniValue::VARR);
-
- const UniValue& output_params = request.params[1];
+ const UniValue& output_params = request.params[1].get_array();
// Create and validate the COutPoints first.
@@ -520,7 +516,7 @@ RPCHelpMan listunspent()
{"maximumCount", RPCArg::Type::NUM, RPCArg::DefaultHint{"unlimited"}, "Maximum number of UTXOs"},
{"minimumSumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""},
},
- "query_options"},
+ RPCArgOptions{.oneline_description="query_options"}},
},
RPCResult{
RPCResult::Type::ARR, "", "",
@@ -543,6 +539,9 @@ RPCHelpMan listunspent()
{RPCResult::Type::BOOL, "solvable", "Whether we know how to spend this output, ignoring the lack of keys"},
{RPCResult::Type::BOOL, "reused", /*optional=*/true, "(only present if avoid_reuse is set) Whether this output is reused/dirty (sent to an address that was previously spent from)"},
{RPCResult::Type::STR, "desc", /*optional=*/true, "(only when solvable) A descriptor for spending this output"},
+ {RPCResult::Type::ARR, "parent_descs", /*optional=*/false, "List of parent descriptors for the scriptPubKey of this coin.", {
+ {RPCResult::Type::STR, "desc", "The descriptor string."},
+ }},
{RPCResult::Type::BOOL, "safe", "Whether this output is considered safe to spend. Unconfirmed transactions\n"
"from outside keys and unconfirmed replacement transactions are considered unsafe\n"
"and are not eligible for spending by fundrawtransaction and sendtoaddress."},
@@ -563,19 +562,16 @@ RPCHelpMan listunspent()
int nMinDepth = 1;
if (!request.params[0].isNull()) {
- RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
nMinDepth = request.params[0].getInt<int>();
}
int nMaxDepth = 9999999;
if (!request.params[1].isNull()) {
- RPCTypeCheckArgument(request.params[1], UniValue::VNUM);
nMaxDepth = request.params[1].getInt<int>();
}
std::set<CTxDestination> destinations;
if (!request.params[2].isNull()) {
- RPCTypeCheckArgument(request.params[2], UniValue::VARR);
UniValue inputs = request.params[2].get_array();
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
const UniValue& input = inputs[idx];
@@ -591,7 +587,6 @@ RPCHelpMan listunspent()
bool include_unsafe = true;
if (!request.params[3].isNull()) {
- RPCTypeCheckArgument(request.params[3], UniValue::VBOOL);
include_unsafe = request.params[3].get_bool();
}
@@ -638,7 +633,7 @@ RPCHelpMan listunspent()
cctl.m_max_depth = nMaxDepth;
cctl.m_include_unsafe_inputs = include_unsafe;
LOCK(pwallet->cs_wallet);
- vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).all();
+ vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).All();
}
LOCK(pwallet->cs_wallet);
@@ -722,6 +717,7 @@ RPCHelpMan listunspent()
entry.pushKV("desc", descriptor->ToString());
}
}
+ PushParentDescriptors(*pwallet, scriptPubKey, entry);
if (avoid_reuse) entry.pushKV("reused", reused);
entry.pushKV("safe", out.safe);
results.push_back(entry);
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index e27cb1139e..6cb33fc11e 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -158,14 +158,14 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vecto
constexpr int RANDOM_CHANGE_POSITION = -1;
auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, coin_control, true);
if (!res) {
- throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, res.GetError().original);
+ throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, util::ErrorString(res).original);
}
- const CTransactionRef& tx = res.GetObj().tx;
+ const CTransactionRef& tx = res->tx;
wallet.CommitTransaction(tx, std::move(map_value), {} /* orderForm */);
if (verbose) {
UniValue entry(UniValue::VOBJ);
entry.pushKV("txid", tx->GetHash().GetHex());
- entry.pushKV("fee_reason", StringForFeeReason(res.GetObj().fee_calc.reason));
+ entry.pushKV("fee_reason", StringForFeeReason(res->fee_calc.reason));
return entry;
}
return tx->GetHash().GetHex();
@@ -224,10 +224,10 @@ RPCHelpMan sendtoaddress()
"transaction, just kept in your wallet."},
{"subtractfeefromamount", RPCArg::Type::BOOL, RPCArg::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, RPCArg::DefaultHint{"wallet default"}, "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
+ {"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Signal that this transaction can be replaced by a transaction (BIP 125)"},
{"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"},
- {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
- " \"" + FeeModes("\"\n\"") + "\""},
+ {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n"
+ "\"" + FeeModes("\"\n\"") + "\""},
{"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{true}, "(only available if avoid_reuse wallet flag is set) Avoid spending from dirty addresses; addresses are considered\n"
"dirty if they have previously been used in a transaction. If true, this also activates avoidpartialspends, grouping outputs by their addresses."},
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
@@ -317,7 +317,7 @@ RPCHelpMan sendmany()
"\nSend multiple times. Amounts are double-precision floating point numbers." +
HELP_REQUIRING_PASSPHRASE,
{
- {"dummy", RPCArg::Type::STR, RPCArg::Optional::NO, "Must be set to \"\" for backwards compatibility.", "\"\""},
+ {"dummy", RPCArg::Type::STR, RPCArg::Optional::NO, "Must be set to \"\" for backwards compatibility.", RPCArgOptions{.oneline_description="\"\""}},
{"amounts", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::NO, "The addresses and amounts",
{
{"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value"},
@@ -333,12 +333,12 @@ RPCHelpMan sendmany()
{"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Subtract fee from this address"},
},
},
- {"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
+ {"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Signal that this transaction can be replaced by a transaction (BIP 125)"},
{"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"},
- {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
- " \"" + FeeModes("\"\n\"") + "\""},
+ {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n"
+ "\"" + FeeModes("\"\n\"") + "\""},
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
- {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra infomration about the transaction."},
+ {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."},
},
{
RPCResult{"if verbose is not set or set to false",
@@ -451,8 +451,8 @@ static std::vector<RPCArg> FundTxDoc(bool solving_data = true)
{
std::vector<RPCArg> args = {
{"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"},
- {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
- " \"" + FeeModes("\"\n\"") + "\""},
+ {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n"
+ "\"" + FeeModes("\"\n\"") + "\""},
{
"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Marks this transaction as BIP125-replaceable.\n"
"Allows this transaction to be replaced by a transaction with higher fees"
@@ -503,7 +503,6 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
coinControl.fAllowWatchOnly = options.get_bool();
}
else {
- RPCTypeCheckArgument(options, UniValue::VOBJ);
RPCTypeCheckObj(options,
{
{"add_inputs", UniValueType(UniValue::VBOOL)},
@@ -644,7 +643,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unable to parse descriptor '%s': %s", desc_str, error));
}
desc->Expand(0, desc_out, scripts_temp, desc_out);
- coinControl.m_external_provider = Merge(coinControl.m_external_provider, desc_out);
+ coinControl.m_external_provider.Merge(std::move(desc_out));
}
}
}
@@ -775,7 +774,7 @@ RPCHelpMan fundrawtransaction()
},
},
FundTxDoc()),
- "options"},
+ RPCArgOptions{.oneline_description="options"}},
{"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n"
"If iswitness is not present, heuristic tests will be used in decoding.\n"
"If true, only witness deserialization will be tried.\n"
@@ -967,9 +966,9 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
"still be replaceable in practice, for example if it has unconfirmed ancestors which\n"
"are replaceable).\n"},
{"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n"
- "\"" + FeeModes("\"\n\"") + "\""},
+ "\"" + FeeModes("\"\n\"") + "\""},
},
- "options"},
+ RPCArgOptions{.oneline_description="options"}},
},
RPCResult{
RPCResult::Type::OBJ, "", "", Cat(
@@ -1045,7 +1044,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
CMutableTransaction mtx;
feebumper::Result res;
// Targeting feerate bump.
- res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx);
+ res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt);
if (res != feebumper::Result::OK) {
switch(res) {
case feebumper::Result::INVALID_ADDRESS_OR_KEY:
@@ -1131,13 +1130,13 @@ RPCHelpMan send()
},
},
{"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"},
- {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
- " \"" + FeeModes("\"\n\"") + "\""},
+ {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n"
+ "\"" + FeeModes("\"\n\"") + "\""},
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
Cat<std::vector<RPCArg>>(
{
- {"add_inputs", RPCArg::Type::BOOL, RPCArg::Default{false}, "If inputs are specified, automatically include more if they are not enough."},
+ {"add_inputs", RPCArg::Type::BOOL, RPCArg::DefaultHint{"false when \"inputs\" are specified, true otherwise"},"Automatically include coins from the wallet to cover the target amount.\n"},
{"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include inputs that are not safe to spend (unconfirmed transactions from outside keys and unconfirmed replacement transactions).\n"
"Warning: the resulting transaction may become invalid if one of the unsafe inputs disappears.\n"
"If that happens, you will need to fund the transaction with different inputs and republish it."},
@@ -1174,7 +1173,7 @@ RPCHelpMan send()
},
},
FundTxDoc()),
- "options"},
+ RPCArgOptions{.oneline_description="options"}},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -1252,8 +1251,8 @@ RPCHelpMan sendall()
},
},
{"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"},
- {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
- " \"" + FeeModes("\"\n\"") + "\""},
+ {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n"
+ "\"" + FeeModes("\"\n\"") + "\""},
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
{
"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
@@ -1264,11 +1263,15 @@ RPCHelpMan sendall()
{"include_watching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch-only.\n"
"Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
"e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."},
- {"inputs", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Use exactly the specified inputs to build the transaction. Specifying inputs is incompatible with send_max. A JSON array of JSON objects",
+ {"inputs", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Use exactly the specified inputs to build the transaction. Specifying inputs is incompatible with send_max.",
{
- {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
- {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
- {"sequence", RPCArg::Type::NUM, RPCArg::Optional::NO, "The sequence number"},
+ {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
+ {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
+ {"sequence", RPCArg::Type::NUM, RPCArg::DefaultHint{"depends on the value of the 'replaceable' and 'locktime' arguments"}, "The sequence number"},
+ },
+ },
},
},
{"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
@@ -1278,7 +1281,7 @@ RPCHelpMan sendall()
},
FundTxDoc()
),
- "options"
+ RPCArgOptions{.oneline_description="options"}
},
},
RPCResult{
@@ -1376,13 +1379,13 @@ RPCHelpMan sendall()
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not available. UTXO (%s:%d) was already spent.", input.prevout.hash.ToString(), input.prevout.n));
}
const CWalletTx* tx{pwallet->GetWalletTx(input.prevout.hash)};
- if (!tx || pwallet->IsMine(tx->tx->vout[input.prevout.n]) != (coin_control.fAllowWatchOnly ? ISMINE_ALL : ISMINE_SPENDABLE)) {
+ if (!tx || input.prevout.n >= tx->tx->vout.size() || !(pwallet->IsMine(tx->tx->vout[input.prevout.n]) & (coin_control.fAllowWatchOnly ? ISMINE_ALL : ISMINE_SPENDABLE))) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not found. UTXO (%s:%d) is not part of wallet.", input.prevout.hash.ToString(), input.prevout.n));
}
total_input_value += tx->tx->vout[input.prevout.n].nValue;
}
} else {
- for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, /*nMinimumAmount=*/0).all()) {
+ for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, /*nMinimumAmount=*/0).All()) {
CHECK_NONFATAL(output.input_bytes > 0);
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
continue;
@@ -1398,6 +1401,10 @@ RPCHelpMan sendall()
const CAmount fee_from_size{fee_rate.GetFee(tx_size.vsize)};
const CAmount effective_value{total_input_value - fee_from_size};
+ if (fee_from_size > pwallet->m_default_max_tx_fee) {
+ throw JSONRPCError(RPC_WALLET_ERROR, TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED).original);
+ }
+
if (effective_value <= 0) {
if (send_max) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Total value of UTXO pool too low to pay for transaction, try using lower feerate.");
@@ -1406,8 +1413,13 @@ RPCHelpMan sendall()
}
}
+ // If this transaction is too large, e.g. because the wallet has many UTXOs, it will be rejected by the node's mempool.
+ if (tx_size.weight > MAX_STANDARD_TX_WEIGHT) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Transaction too large.");
+ }
+
CAmount output_amounts_claimed{0};
- for (CTxOut out : rawTx.vout) {
+ for (const CTxOut& out : rawTx.vout) {
output_amounts_claimed += out.nValue;
}
@@ -1578,7 +1590,7 @@ RPCHelpMan walletcreatefundedpsbt()
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
Cat<std::vector<RPCArg>>(
{
- {"add_inputs", RPCArg::Type::BOOL, RPCArg::Default{false}, "If inputs are specified, automatically include more if they are not enough."},
+ {"add_inputs", RPCArg::Type::BOOL, RPCArg::DefaultHint{"false when \"inputs\" are specified, true otherwise"}, "Automatically include coins from the wallet to cover the target amount.\n"},
{"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include inputs that are not safe to spend (unconfirmed transactions from outside keys and unconfirmed replacement transactions).\n"
"Warning: the resulting transaction may become invalid if one of the unsafe inputs disappears.\n"
"If that happens, you will need to fund the transaction with different inputs and republish it."},
@@ -1599,7 +1611,7 @@ RPCHelpMan walletcreatefundedpsbt()
},
},
FundTxDoc()),
- "options"},
+ RPCArgOptions{.oneline_description="options"}},
{"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"},
},
RPCResult{
@@ -1640,7 +1652,6 @@ RPCHelpMan walletcreatefundedpsbt()
bool rbf{wallet.m_signal_rbf};
const UniValue &replaceable_arg = options["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);
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index 9c8003d6d7..3c10b47082 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -315,13 +315,16 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest)
* @param filter_label Optional label string to filter incoming transactions.
*/
template <class Vec>
-static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nMinDepth, bool fLong, Vec& ret, const isminefilter& filter_ismine, const std::string* filter_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
+static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nMinDepth, bool fLong,
+ Vec& ret, const isminefilter& filter_ismine, const std::string* filter_label,
+ bool include_change = false)
+ EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
{
CAmount nFee;
std::list<COutputEntry> listReceived;
std::list<COutputEntry> listSent;
- CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, filter_ismine);
+ CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, filter_ismine, include_change);
bool involvesWatchonly = CachedTxIsFromMe(wallet, wtx, ISMINE_WATCH_ONLY);
@@ -367,6 +370,7 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM
entry.pushKV("involvesWatchonly", true);
}
MaybePushAddress(entry, r.destination);
+ PushParentDescriptors(wallet, wtx.tx->vout.at(r.vout).scriptPubKey, entry);
if (wtx.IsCoinBase())
{
if (wallet.GetTxDepthInMainChain(wtx) < 1)
@@ -417,8 +421,12 @@ static const std::vector<RPCResult> TransactionDescriptionString()
{RPCResult::Type::NUM_TIME, "time", "The transaction time expressed in " + UNIX_EPOCH_TIME + "."},
{RPCResult::Type::NUM_TIME, "timereceived", "The time received expressed in " + UNIX_EPOCH_TIME + "."},
{RPCResult::Type::STR, "comment", /*optional=*/true, "If a comment is associated with the transaction, only present if not empty."},
- {RPCResult::Type::STR, "bip125-replaceable", "(\"yes|no|unknown\") Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
- "may be unknown for unconfirmed transactions not in the mempool."}};
+ {RPCResult::Type::STR, "bip125-replaceable", "(\"yes|no|unknown\") Whether this transaction signals BIP125 replaceability or has an unconfirmed ancestor signaling BIP125 replaceability.\n"
+ "May be unknown for unconfirmed transactions not in the mempool because their unconfirmed ancestors are unknown."},
+ {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the scriptPubKey of this coin.", {
+ {RPCResult::Type::STR, "desc", "The descriptor string."},
+ }},
+ };
}
RPCHelpMan listtransactions()
@@ -439,7 +447,7 @@ RPCHelpMan listtransactions()
{RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
{
{RPCResult::Type::BOOL, "involvesWatchonly", /*optional=*/true, "Only returns true if imported addresses were involved in transaction."},
- {RPCResult::Type::STR, "address", "The bitcoin address of the transaction."},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address of the transaction (not returned if the output does not have an address, e.g. OP_RETURN null data)."},
{RPCResult::Type::STR, "category", "The transaction category.\n"
"\"send\" Transactions sent.\n"
"\"receive\" Non-coinbase transactions received.\n"
@@ -543,6 +551,7 @@ RPCHelpMan listsinceblock()
{"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Include transactions to watch-only addresses (see 'importaddress')"},
{"include_removed", RPCArg::Type::BOOL, RPCArg::Default{true}, "Show transactions that were removed due to a reorg in the \"removed\" array\n"
"(not guaranteed to work on pruned nodes)"},
+ {"include_change", RPCArg::Type::BOOL, RPCArg::Default{false}, "Also add entries for change outputs.\n"},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -552,7 +561,7 @@ RPCHelpMan listsinceblock()
{RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
{
{RPCResult::Type::BOOL, "involvesWatchonly", /*optional=*/true, "Only returns true if imported addresses were involved in transaction."},
- {RPCResult::Type::STR, "address", "The bitcoin address of the transaction."},
+ {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address of the transaction (not returned if the output does not have an address, e.g. OP_RETURN null data)."},
{RPCResult::Type::STR, "category", "The transaction category.\n"
"\"send\" Transactions sent.\n"
"\"receive\" Non-coinbase transactions received.\n"
@@ -623,6 +632,7 @@ RPCHelpMan listsinceblock()
}
bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
+ bool include_change = (!request.params[4].isNull() && request.params[4].get_bool());
int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1;
@@ -632,7 +642,7 @@ RPCHelpMan listsinceblock()
const CWalletTx& tx = pairWtx.second;
if (depth == -1 || abs(wallet.GetTxDepthInMainChain(tx)) < depth) {
- ListTransactions(wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */);
+ ListTransactions(wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */, /*include_change=*/include_change);
}
}
@@ -649,7 +659,7 @@ RPCHelpMan listsinceblock()
if (it != wallet.mapWallet.end()) {
// We want all transactions regardless of confirmation count to appear here,
// even negative confirmation ones, hence the big negative.
- ListTransactions(wallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */);
+ ListTransactions(wallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */, /*include_change=*/include_change);
}
}
blockId = block.hashPrevBlock;
@@ -709,6 +719,9 @@ RPCHelpMan gettransaction()
"'send' category of transactions."},
{RPCResult::Type::BOOL, "abandoned", /*optional=*/true, "'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
"'send' category of transactions."},
+ {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the scriptPubKey of this coin.", {
+ {RPCResult::Type::STR, "desc", "The descriptor string."},
+ }},
}},
}},
{RPCResult::Type::STR_HEX, "hex", "Raw data for transaction"},
@@ -826,7 +839,9 @@ RPCHelpMan rescanblockchain()
{
return RPCHelpMan{"rescanblockchain",
"\nRescan the local blockchain for wallet related transactions.\n"
- "Note: Use \"getwalletinfo\" to query the scanning progress.\n",
+ "Note: Use \"getwalletinfo\" to query the scanning progress.\n"
+ "The rescan is significantly faster when used on a descriptor wallet\n"
+ "and block filters are available (using startup option \"-blockfilterindex=1\").\n",
{
{"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "block height where the rescan should start"},
{"stop_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "the last block height that should be scanned. If none is provided it will rescan up to the tip at return time of this call."},
diff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp
index 4fcb393226..26270f23ed 100644
--- a/src/wallet/rpc/util.cpp
+++ b/src/wallet/rpc/util.cpp
@@ -4,18 +4,34 @@
#include <wallet/rpc/util.h>
+#include <common/url.h>
#include <rpc/util.h>
#include <util/translation.h>
-#include <util/url.h>
#include <wallet/context.h>
#include <wallet/wallet.h>
#include <univalue.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
namespace wallet {
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
const std::string HELP_REQUIRING_PASSPHRASE{"\nRequires wallet passphrase to be set with walletpassphrase call if wallet is encrypted.\n"};
+int64_t ParseISO8601DateTime(const std::string& str)
+{
+ static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0);
+ static const std::locale loc(std::locale::classic(),
+ new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ"));
+ std::istringstream iss(str);
+ iss.imbue(loc);
+ boost::posix_time::ptime ptime(boost::date_time::not_a_date_time);
+ iss >> ptime;
+ if (ptime.is_not_a_date_time() || epoch > ptime)
+ return 0;
+ return (ptime - epoch).total_seconds();
+}
+
bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param) {
bool can_avoid_reuse = wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool();
@@ -64,12 +80,11 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques
return pwallet;
}
- std::vector<std::shared_ptr<CWallet>> wallets = GetWallets(context);
- if (wallets.size() == 1) {
- return wallets[0];
- }
+ size_t count{0};
+ auto wallet = GetDefaultWallet(context, count);
+ if (wallet) return wallet;
- if (wallets.empty()) {
+ if (count == 0) {
throw JSONRPCError(
RPC_WALLET_NOT_FOUND, "No wallet is loaded. Load a wallet using loadwallet or create a new one with createwallet. (Note: A default wallet is no longer automatically created)");
}
@@ -123,6 +138,15 @@ std::string LabelFromValue(const UniValue& value)
return label;
}
+void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey, UniValue& entry)
+{
+ UniValue parent_descs(UniValue::VARR);
+ for (const auto& desc: wallet.GetWalletDescriptors(script_pubkey)) {
+ parent_descs.push_back(desc.descriptor->ToString());
+ }
+ entry.pushKV("parent_descs", parent_descs);
+}
+
void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& status, bilingual_str& error)
{
if (!wallet) {
diff --git a/src/wallet/rpc/util.h b/src/wallet/rpc/util.h
index 7b810eb06e..87d34f7c11 100644
--- a/src/wallet/rpc/util.h
+++ b/src/wallet/rpc/util.h
@@ -5,6 +5,8 @@
#ifndef BITCOIN_WALLET_RPC_UTIL_H
#define BITCOIN_WALLET_RPC_UTIL_H
+#include <script/script.h>
+
#include <any>
#include <memory>
#include <string>
@@ -39,8 +41,12 @@ const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wal
bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param);
bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& wallet);
std::string LabelFromValue(const UniValue& value);
+//! Fetch parent descriptors of this scriptPubKey.
+void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey, UniValue& entry);
void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& status, bilingual_str& error);
+
+int64_t ParseISO8601DateTime(const std::string& str);
} // namespace wallet
#endif // BITCOIN_WALLET_RPC_UTIL_H
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp
index 0fe8871152..675c4a759d 100644
--- a/src/wallet/rpc/wallet.cpp
+++ b/src/wallet/rpc/wallet.cpp
@@ -590,6 +590,170 @@ static RPCHelpMan upgradewallet()
};
}
+RPCHelpMan simulaterawtransaction()
+{
+ return RPCHelpMan{"simulaterawtransaction",
+ "\nCalculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
+ {
+ {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "An array of hex strings of raw transactions.\n",
+ {
+ {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
+ },
+ },
+ {"options", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED_NAMED_ARG, "Options",
+ {
+ {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Whether to include watch-only addresses (see RPC importaddress)"},
+ },
+ },
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
+ }
+ },
+ RPCExamples{
+ HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
+ + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);
+ if (!rpc_wallet) return UniValue::VNULL;
+ const CWallet& wallet = *rpc_wallet;
+
+ RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VOBJ}, true);
+
+ LOCK(wallet.cs_wallet);
+
+ UniValue include_watchonly(UniValue::VNULL);
+ if (request.params[1].isObject()) {
+ UniValue options = request.params[1];
+ RPCTypeCheckObj(options,
+ {
+ {"include_watchonly", UniValueType(UniValue::VBOOL)},
+ },
+ true, true);
+
+ include_watchonly = options["include_watchonly"];
+ }
+
+ isminefilter filter = ISMINE_SPENDABLE;
+ if (ParseIncludeWatchonly(include_watchonly, wallet)) {
+ filter |= ISMINE_WATCH_ONLY;
+ }
+
+ const auto& txs = request.params[0].get_array();
+ CAmount changes{0};
+ std::map<COutPoint, CAmount> new_utxos; // UTXO:s that were made available in transaction array
+ std::set<COutPoint> spent;
+
+ for (size_t i = 0; i < txs.size(); ++i) {
+ CMutableTransaction mtx;
+ if (!DecodeHexTx(mtx, txs[i].get_str(), /* try_no_witness */ true, /* try_witness */ true)) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Transaction hex string decoding failure.");
+ }
+
+ // Fetch previous transactions (inputs)
+ std::map<COutPoint, Coin> coins;
+ for (const CTxIn& txin : mtx.vin) {
+ coins[txin.prevout]; // Create empty map entry keyed by prevout.
+ }
+ wallet.chain().findCoins(coins);
+
+ // Fetch debit; we are *spending* these; if the transaction is signed and
+ // broadcast, we will lose everything in these
+ for (const auto& txin : mtx.vin) {
+ const auto& outpoint = txin.prevout;
+ if (spent.count(outpoint)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction(s) are spending the same output more than once");
+ }
+ if (new_utxos.count(outpoint)) {
+ changes -= new_utxos.at(outpoint);
+ new_utxos.erase(outpoint);
+ } else {
+ if (coins.at(outpoint).IsSpent()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more transaction inputs are missing or have been spent already");
+ }
+ changes -= wallet.GetDebit(txin, filter);
+ }
+ spent.insert(outpoint);
+ }
+
+ // Iterate over outputs; we are *receiving* these, if the wallet considers
+ // them "mine"; if the transaction is signed and broadcast, we will receive
+ // everything in these
+ // Also populate new_utxos in case these are spent in later transactions
+
+ const auto& hash = mtx.GetHash();
+ for (size_t i = 0; i < mtx.vout.size(); ++i) {
+ const auto& txout = mtx.vout[i];
+ bool is_mine = 0 < (wallet.IsMine(txout) & filter);
+ changes += new_utxos[COutPoint(hash, i)] = is_mine ? txout.nValue : 0;
+ }
+ }
+
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("balance_change", ValueFromAmount(changes));
+
+ return result;
+}
+ };
+}
+
+static RPCHelpMan migratewallet()
+{
+ return RPCHelpMan{"migratewallet",
+ "EXPERIMENTAL warning: This call may not work as expected and may be changed in future releases\n"
+ "\nMigrate the wallet to a descriptor wallet.\n"
+ "A new wallet backup will need to be made.\n"
+ "\nThe migration process will create a backup of the wallet before migrating. This backup\n"
+ "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
+ "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet." +
+ HELP_REQUIRING_PASSPHRASE,
+ {},
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
+ {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
+ {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
+ {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
+ }
+ },
+ RPCExamples{
+ HelpExampleCli("migratewallet", "")
+ + HelpExampleRpc("migratewallet", "")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ std::shared_ptr<CWallet> wallet = GetWalletForJSONRPCRequest(request);
+ if (!wallet) return NullUniValue;
+
+ EnsureWalletIsUnlocked(*wallet);
+
+ WalletContext& context = EnsureWalletContext(request.context);
+
+ util::Result<MigrationResult> res = MigrateLegacyToDescriptor(std::move(wallet), context);
+ if (!res) {
+ throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
+ }
+
+ UniValue r{UniValue::VOBJ};
+ r.pushKV("wallet_name", res->wallet_name);
+ if (res->watchonly_wallet) {
+ r.pushKV("watchonly_name", res->watchonly_wallet->GetName());
+ }
+ if (res->solvables_wallet) {
+ r.pushKV("solvables_name", res->solvables_wallet->GetName());
+ }
+ r.pushKV("backup_path", res->backup_path.u8string());
+
+ return r;
+ },
+ };
+}
+
// addresses
RPCHelpMan getaddressinfo();
RPCHelpMan getnewaddress();
@@ -709,6 +873,7 @@ Span<const CRPCCommand> GetWalletRPCCommands()
{"wallet", &listwallets},
{"wallet", &loadwallet},
{"wallet", &lockunspent},
+ {"wallet", &migratewallet},
{"wallet", &newkeypool},
{"wallet", &removeprunedfunds},
{"wallet", &rescanblockchain},
@@ -721,6 +886,7 @@ Span<const CRPCCommand> GetWalletRPCCommands()
{"wallet", &setwalletflag},
{"wallet", &signmessage},
{"wallet", &signrawtransactionwithwallet},
+ {"wallet", &simulaterawtransaction},
{"wallet", &sendall},
{"wallet", &unloadwallet},
{"wallet", &upgradewallet},
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index bba31dfe0b..4c534d64ec 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -21,19 +21,22 @@ namespace wallet {
//! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details.
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
-BResult<CTxDestination> LegacyScriptPubKeyMan::GetNewDestination(const OutputType type)
+util::Result<CTxDestination> LegacyScriptPubKeyMan::GetNewDestination(const OutputType type)
{
if (LEGACY_OUTPUT_TYPES.count(type) == 0) {
- return _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types");;
+ return util::Error{_("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types")};
}
assert(type != OutputType::BECH32M);
+ // Fill-up keypool if needed
+ TopUp();
+
LOCK(cs_KeyStore);
// Generate a new key that is added to wallet
CPubKey new_key;
if (!GetKeyFromPool(new_key, type)) {
- return _("Error: Keypool ran out, please call keypoolrefill first");
+ return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")};
}
LearnRelatedScripts(new_key, type);
return GetDestinationForKey(new_key, type);
@@ -292,26 +295,25 @@ bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBat
return true;
}
-bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error)
+util::Result<CTxDestination> LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool)
{
if (LEGACY_OUTPUT_TYPES.count(type) == 0) {
- error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types");
- return false;
+ return util::Error{_("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types")};
}
assert(type != OutputType::BECH32M);
LOCK(cs_KeyStore);
if (!CanGetAddresses(internal)) {
- error = _("Error: Keypool ran out, please call keypoolrefill first");
- return false;
+ return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")};
}
+ // Fill-up keypool if needed
+ TopUp();
+
if (!ReserveKeyFromKeyPool(index, keypool, internal)) {
- error = _("Error: Keypool ran out, please call keypoolrefill first");
- return false;
+ return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")};
}
- address = GetDestinationForKey(keypool.vchPubKey, type);
- return true;
+ return GetDestinationForKey(keypool.vchPubKey, type);
}
bool LegacyScriptPubKeyMan::TopUpInactiveHDChain(const CKeyID seed_id, int64_t index, bool internal)
@@ -590,7 +592,7 @@ bool LegacyScriptPubKeyMan::CanProvide(const CScript& script, SignatureData& sig
// or solving information, even if not able to sign fully.
return true;
} else {
- // If, given the stuff in sigdata, we could make a valid sigature, then we can provide for this script
+ // If, given the stuff in sigdata, we could make a valid signature, then we can provide for this script
ProduceSignature(*this, DUMMY_SIGNATURE_CREATOR, script, sigdata);
if (!sigdata.signatures.empty()) {
// If we could make signatures, make sure we have a private key to actually make a signature
@@ -1003,9 +1005,10 @@ bool LegacyScriptPubKeyMan::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& inf
{
LOCK(cs_KeyStore);
auto it = mapKeyMetadata.find(keyID);
- if (it != mapKeyMetadata.end()) {
- meta = it->second;
+ if (it == mapKeyMetadata.end()) {
+ return false;
}
+ meta = it->second;
}
if (meta.has_key_origin) {
std::copy(meta.key_origin.fingerprint, meta.key_origin.fingerprint + 4, info.fingerprint);
@@ -1084,6 +1087,13 @@ CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, CHDChain& hd_c
return pubkey;
}
+//! Try to derive an extended key, throw if it fails.
+static void DeriveExtKey(CExtKey& key_in, unsigned int index, CExtKey& key_out) {
+ if (!key_in.Derive(key_out, index)) {
+ throw std::runtime_error("Could not derive extended key");
+ }
+}
+
void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, CHDChain& hd_chain, bool internal)
{
// for now we use a fixed keypath scheme of m/0'/0'/k
@@ -1101,11 +1111,11 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata&
// derive m/0'
// use hardened derivation (child keys >= 0x80000000 are hardened after bip32)
- masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
+ DeriveExtKey(masterKey, BIP32_HARDENED_KEY_LIMIT, accountKey);
// derive m/0'/0' (external chain) OR m/0'/1' (internal chain)
assert(internal ? m_storage.CanSupportFeature(FEATURE_HD_SPLIT) : true);
- accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0));
+ DeriveExtKey(accountKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0), chainChildKey);
// derive child key at next index, skip keys already known to the wallet
do {
@@ -1113,7 +1123,7 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata&
// childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
// example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
if (internal) {
- chainChildKey.Derive(childKey, hd_chain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
+ DeriveExtKey(chainChildKey, hd_chain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT, childKey);
metadata.hdKeypath = "m/0'/1'/" + ToString(hd_chain.nInternalChainCounter) + "'";
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT);
@@ -1121,7 +1131,7 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata&
hd_chain.nInternalChainCounter++;
}
else {
- chainChildKey.Derive(childKey, hd_chain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
+ DeriveExtKey(chainChildKey, hd_chain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT, childKey);
metadata.hdKeypath = "m/0'/0'/" + ToString(hd_chain.nExternalChainCounter) + "'";
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
@@ -1453,7 +1463,8 @@ void LegacyScriptPubKeyMan::LearnRelatedScripts(const CPubKey& key, OutputType t
CTxDestination witdest = WitnessV0KeyHash(key.GetID());
CScript witprog = GetScriptForDestination(witdest);
// Make sure the resulting program is solvable.
- assert(IsSolvable(*this, witprog));
+ const auto desc = InferDescriptor(witprog, *this);
+ assert(desc && desc->IsSolvable());
AddCScript(witprog);
}
}
@@ -1654,11 +1665,323 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const
return set_address;
}
-BResult<CTxDestination> DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type)
+const std::unordered_set<CScript, SaltedSipHasher> LegacyScriptPubKeyMan::GetScriptPubKeys() const
+{
+ LOCK(cs_KeyStore);
+ std::unordered_set<CScript, SaltedSipHasher> spks;
+
+ // All keys have at least P2PK and P2PKH
+ for (const auto& key_pair : mapKeys) {
+ const CPubKey& pub = key_pair.second.GetPubKey();
+ spks.insert(GetScriptForRawPubKey(pub));
+ spks.insert(GetScriptForDestination(PKHash(pub)));
+ }
+ for (const auto& key_pair : mapCryptedKeys) {
+ const CPubKey& pub = key_pair.second.first;
+ spks.insert(GetScriptForRawPubKey(pub));
+ spks.insert(GetScriptForDestination(PKHash(pub)));
+ }
+
+ // For every script in mapScript, only the ISMINE_SPENDABLE ones are being tracked.
+ // The watchonly ones will be in setWatchOnly which we deal with later
+ // For all keys, if they have segwit scripts, those scripts will end up in mapScripts
+ for (const auto& script_pair : mapScripts) {
+ const CScript& script = script_pair.second;
+ if (IsMine(script) == ISMINE_SPENDABLE) {
+ // Add ScriptHash for scripts that are not already P2SH
+ if (!script.IsPayToScriptHash()) {
+ spks.insert(GetScriptForDestination(ScriptHash(script)));
+ }
+ // For segwit scripts, we only consider them spendable if we have the segwit spk
+ int wit_ver = -1;
+ std::vector<unsigned char> witprog;
+ if (script.IsWitnessProgram(wit_ver, witprog) && wit_ver == 0) {
+ spks.insert(script);
+ }
+ } else {
+ // Multisigs are special. They don't show up as ISMINE_SPENDABLE unless they are in a P2SH
+ // So check the P2SH of a multisig to see if we should insert it
+ std::vector<std::vector<unsigned char>> sols;
+ TxoutType type = Solver(script, sols);
+ if (type == TxoutType::MULTISIG) {
+ CScript ms_spk = GetScriptForDestination(ScriptHash(script));
+ if (IsMine(ms_spk) != ISMINE_NO) {
+ spks.insert(ms_spk);
+ }
+ }
+ }
+ }
+
+ // All watchonly scripts are raw
+ spks.insert(setWatchOnly.begin(), setWatchOnly.end());
+
+ return spks;
+}
+
+std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor()
+{
+ LOCK(cs_KeyStore);
+ if (m_storage.IsLocked()) {
+ return std::nullopt;
+ }
+
+ MigrationData out;
+
+ std::unordered_set<CScript, SaltedSipHasher> spks{GetScriptPubKeys()};
+
+ // Get all key ids
+ std::set<CKeyID> keyids;
+ for (const auto& key_pair : mapKeys) {
+ keyids.insert(key_pair.first);
+ }
+ for (const auto& key_pair : mapCryptedKeys) {
+ keyids.insert(key_pair.first);
+ }
+
+ // Get key metadata and figure out which keys don't have a seed
+ // Note that we do not ignore the seeds themselves because they are considered IsMine!
+ for (auto keyid_it = keyids.begin(); keyid_it != keyids.end();) {
+ const CKeyID& keyid = *keyid_it;
+ const auto& it = mapKeyMetadata.find(keyid);
+ if (it != mapKeyMetadata.end()) {
+ const CKeyMetadata& meta = it->second;
+ if (meta.hdKeypath == "s" || meta.hdKeypath == "m") {
+ keyid_it++;
+ continue;
+ }
+ if (m_hd_chain.seed_id == meta.hd_seed_id || m_inactive_hd_chains.count(meta.hd_seed_id) > 0) {
+ keyid_it = keyids.erase(keyid_it);
+ continue;
+ }
+ }
+ keyid_it++;
+ }
+
+ // keyids is now all non-HD keys. Each key will have its own combo descriptor
+ for (const CKeyID& keyid : keyids) {
+ CKey key;
+ if (!GetKey(keyid, key)) {
+ assert(false);
+ }
+
+ // Get birthdate from key meta
+ uint64_t creation_time = 0;
+ const auto& it = mapKeyMetadata.find(keyid);
+ if (it != mapKeyMetadata.end()) {
+ creation_time = it->second.nCreateTime;
+ }
+
+ // Get the key origin
+ // Maybe this doesn't matter because floating keys here shouldn't have origins
+ KeyOriginInfo info;
+ bool has_info = GetKeyOrigin(keyid, info);
+ std::string origin_str = has_info ? "[" + HexStr(info.fingerprint) + FormatHDKeypath(info.path) + "]" : "";
+
+ // Construct the combo descriptor
+ std::string desc_str = "combo(" + origin_str + HexStr(key.GetPubKey()) + ")";
+ FlatSigningProvider keys;
+ std::string error;
+ std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false);
+ WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0);
+
+ // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys
+ auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc));
+ desc_spk_man->AddDescriptorKey(key, key.GetPubKey());
+ desc_spk_man->TopUp();
+ auto desc_spks = desc_spk_man->GetScriptPubKeys();
+
+ // Remove the scriptPubKeys from our current set
+ for (const CScript& spk : desc_spks) {
+ size_t erased = spks.erase(spk);
+ assert(erased == 1);
+ assert(IsMine(spk) == ISMINE_SPENDABLE);
+ }
+
+ out.desc_spkms.push_back(std::move(desc_spk_man));
+ }
+
+ // Handle HD keys by using the CHDChains
+ std::vector<CHDChain> chains;
+ chains.push_back(m_hd_chain);
+ for (const auto& chain_pair : m_inactive_hd_chains) {
+ chains.push_back(chain_pair.second);
+ }
+ for (const CHDChain& chain : chains) {
+ for (int i = 0; i < 2; ++i) {
+ // Skip if doing internal chain and split chain is not supported
+ if (chain.seed_id.IsNull() || (i == 1 && !m_storage.CanSupportFeature(FEATURE_HD_SPLIT))) {
+ continue;
+ }
+ // Get the master xprv
+ CKey seed_key;
+ if (!GetKey(chain.seed_id, seed_key)) {
+ assert(false);
+ }
+ CExtKey master_key;
+ master_key.SetSeed(seed_key);
+
+ // Make the combo descriptor
+ std::string xpub = EncodeExtPubKey(master_key.Neuter());
+ std::string desc_str = "combo(" + xpub + "/0'/" + ToString(i) + "'/*')";
+ FlatSigningProvider keys;
+ std::string error;
+ std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false);
+ uint32_t chain_counter = std::max((i == 1 ? chain.nInternalChainCounter : chain.nExternalChainCounter), (uint32_t)0);
+ WalletDescriptor w_desc(std::move(desc), 0, 0, chain_counter, 0);
+
+ // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys
+ auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc));
+ desc_spk_man->AddDescriptorKey(master_key.key, master_key.key.GetPubKey());
+ desc_spk_man->TopUp();
+ auto desc_spks = desc_spk_man->GetScriptPubKeys();
+
+ // Remove the scriptPubKeys from our current set
+ for (const CScript& spk : desc_spks) {
+ size_t erased = spks.erase(spk);
+ assert(erased == 1);
+ assert(IsMine(spk) == ISMINE_SPENDABLE);
+ }
+
+ out.desc_spkms.push_back(std::move(desc_spk_man));
+ }
+ }
+ // Add the current master seed to the migration data
+ if (!m_hd_chain.seed_id.IsNull()) {
+ CKey seed_key;
+ if (!GetKey(m_hd_chain.seed_id, seed_key)) {
+ assert(false);
+ }
+ out.master_key.SetSeed(seed_key);
+ }
+
+ // Handle the rest of the scriptPubKeys which must be imports and may not have all info
+ for (auto it = spks.begin(); it != spks.end();) {
+ const CScript& spk = *it;
+
+ // Get birthdate from script meta
+ uint64_t creation_time = 0;
+ const auto& mit = m_script_metadata.find(CScriptID(spk));
+ if (mit != m_script_metadata.end()) {
+ creation_time = mit->second.nCreateTime;
+ }
+
+ // InferDescriptor as that will get us all the solving info if it is there
+ std::unique_ptr<Descriptor> desc = InferDescriptor(spk, *GetSolvingProvider(spk));
+ // Get the private keys for this descriptor
+ std::vector<CScript> scripts;
+ FlatSigningProvider keys;
+ if (!desc->Expand(0, DUMMY_SIGNING_PROVIDER, scripts, keys)) {
+ assert(false);
+ }
+ std::set<CKeyID> privkeyids;
+ for (const auto& key_orig_pair : keys.origins) {
+ privkeyids.insert(key_orig_pair.first);
+ }
+
+ std::vector<CScript> desc_spks;
+
+ // Make the descriptor string with private keys
+ std::string desc_str;
+ bool watchonly = !desc->ToPrivateString(*this, desc_str);
+ if (watchonly && !m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ out.watch_descs.push_back({desc->ToString(), creation_time});
+
+ // Get the scriptPubKeys without writing this to the wallet
+ FlatSigningProvider provider;
+ desc->Expand(0, provider, desc_spks, provider);
+ } else {
+ // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys
+ WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0);
+ auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc));
+ for (const auto& keyid : privkeyids) {
+ CKey key;
+ if (!GetKey(keyid, key)) {
+ continue;
+ }
+ desc_spk_man->AddDescriptorKey(key, key.GetPubKey());
+ }
+ desc_spk_man->TopUp();
+ auto desc_spks_set = desc_spk_man->GetScriptPubKeys();
+ desc_spks.insert(desc_spks.end(), desc_spks_set.begin(), desc_spks_set.end());
+
+ out.desc_spkms.push_back(std::move(desc_spk_man));
+ }
+
+ // Remove the scriptPubKeys from our current set
+ for (const CScript& desc_spk : desc_spks) {
+ auto del_it = spks.find(desc_spk);
+ assert(del_it != spks.end());
+ assert(IsMine(desc_spk) != ISMINE_NO);
+ it = spks.erase(del_it);
+ }
+ }
+
+ // Multisigs are special. They don't show up as ISMINE_SPENDABLE unless they are in a P2SH
+ // So we have to check if any of our scripts are a multisig and if so, add the P2SH
+ for (const auto& script_pair : mapScripts) {
+ const CScript script = script_pair.second;
+
+ // Get birthdate from script meta
+ uint64_t creation_time = 0;
+ const auto& it = m_script_metadata.find(CScriptID(script));
+ if (it != m_script_metadata.end()) {
+ creation_time = it->second.nCreateTime;
+ }
+
+ std::vector<std::vector<unsigned char>> sols;
+ TxoutType type = Solver(script, sols);
+ if (type == TxoutType::MULTISIG) {
+ CScript sh_spk = GetScriptForDestination(ScriptHash(script));
+ CTxDestination witdest = WitnessV0ScriptHash(script);
+ CScript witprog = GetScriptForDestination(witdest);
+ CScript sh_wsh_spk = GetScriptForDestination(ScriptHash(witprog));
+
+ // We only want the multisigs that we have not already seen, i.e. they are not watchonly and not spendable
+ // For P2SH, a multisig is not ISMINE_NO when:
+ // * All keys are in the wallet
+ // * The multisig itself is watch only
+ // * The P2SH is watch only
+ // For P2SH-P2WSH, if the script is in the wallet, then it will have the same conditions as P2SH.
+ // For P2WSH, a multisig is not ISMINE_NO when, other than the P2SH conditions:
+ // * The P2WSH script is in the wallet and it is being watched
+ std::vector<std::vector<unsigned char>> keys(sols.begin() + 1, sols.begin() + sols.size() - 1);
+ if (HaveWatchOnly(sh_spk) || HaveWatchOnly(script) || HaveKeys(keys, *this) || (HaveCScript(CScriptID(witprog)) && HaveWatchOnly(witprog))) {
+ // The above emulates IsMine for these 3 scriptPubKeys, so double check that by running IsMine
+ assert(IsMine(sh_spk) != ISMINE_NO || IsMine(witprog) != ISMINE_NO || IsMine(sh_wsh_spk) != ISMINE_NO);
+ continue;
+ }
+ assert(IsMine(sh_spk) == ISMINE_NO && IsMine(witprog) == ISMINE_NO && IsMine(sh_wsh_spk) == ISMINE_NO);
+
+ std::unique_ptr<Descriptor> sh_desc = InferDescriptor(sh_spk, *GetSolvingProvider(sh_spk));
+ out.solvable_descs.push_back({sh_desc->ToString(), creation_time});
+
+ const auto desc = InferDescriptor(witprog, *this);
+ if (desc->IsSolvable()) {
+ std::unique_ptr<Descriptor> wsh_desc = InferDescriptor(witprog, *GetSolvingProvider(witprog));
+ out.solvable_descs.push_back({wsh_desc->ToString(), creation_time});
+ std::unique_ptr<Descriptor> sh_wsh_desc = InferDescriptor(sh_wsh_spk, *GetSolvingProvider(sh_wsh_spk));
+ out.solvable_descs.push_back({sh_wsh_desc->ToString(), creation_time});
+ }
+ }
+ }
+
+ // Make sure that we have accounted for all scriptPubKeys
+ assert(spks.size() == 0);
+ return out;
+}
+
+bool LegacyScriptPubKeyMan::DeleteRecords()
+{
+ LOCK(cs_KeyStore);
+ WalletBatch batch(m_storage.GetDatabase());
+ return batch.EraseRecords(DBKeys::LEGACY_TYPES);
+}
+
+util::Result<CTxDestination> DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type)
{
// Returns true if this descriptor supports getting new addresses. Conditions where we may be unable to fetch them (e.g. locked) are caught later
if (!CanGetAddresses()) {
- return _("No addresses available");
+ return util::Error{_("No addresses available")};
}
{
LOCK(cs_desc_man);
@@ -1666,7 +1989,7 @@ BResult<CTxDestination> DescriptorScriptPubKeyMan::GetNewDestination(const Outpu
std::optional<OutputType> desc_addr_type = m_wallet_descriptor.descriptor->GetOutputType();
assert(desc_addr_type);
if (type != *desc_addr_type) {
- throw std::runtime_error(std::string(__func__) + ": Types are inconsistent");
+ throw std::runtime_error(std::string(__func__) + ": Types are inconsistent. Stored type does not match type of newly generated address");
}
TopUp();
@@ -1676,19 +1999,16 @@ BResult<CTxDestination> DescriptorScriptPubKeyMan::GetNewDestination(const Outpu
std::vector<CScript> scripts_temp;
if (m_wallet_descriptor.range_end <= m_max_cached_index && !TopUp(1)) {
// We can't generate anymore keys
- return _("Error: Keypool ran out, please call keypoolrefill first");
+ return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")};
}
if (!m_wallet_descriptor.descriptor->ExpandFromCache(m_wallet_descriptor.next_index, m_wallet_descriptor.cache, scripts_temp, out_keys)) {
// We can't generate anymore keys
- return _("Error: Keypool ran out, please call keypoolrefill first");
+ return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")};
}
CTxDestination dest;
- std::optional<OutputType> out_script_type = m_wallet_descriptor.descriptor->GetOutputType();
- if (out_script_type && out_script_type == type) {
- ExtractDestination(scripts_temp[0], dest);
- } else {
- throw std::runtime_error(std::string(__func__) + ": Types are inconsistent. Stored type does not match type of newly generated address");
+ if (!ExtractDestination(scripts_temp[0], dest)) {
+ return util::Error{_("Error: Cannot extract destination from the generated scriptpubkey")}; // shouldn't happen
}
m_wallet_descriptor.next_index++;
WalletBatch(m_storage.GetDatabase()).WriteDescriptor(GetID(), m_wallet_descriptor);
@@ -1760,17 +2080,12 @@ bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, Walle
return true;
}
-bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error)
+util::Result<CTxDestination> DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool)
{
LOCK(cs_desc_man);
auto op_dest = GetNewDestination(type);
index = m_wallet_descriptor.next_index - 1;
- if (op_dest) {
- address = op_dest.GetObj();
- } else {
- error = op_dest.GetError();
- }
- return op_dest.HasRes();
+ return op_dest;
}
void DescriptorScriptPubKeyMan::ReturnDestination(int64_t index, bool internal, const CTxDestination& addr)
@@ -1789,7 +2104,7 @@ std::map<CKeyID, CKey> DescriptorScriptPubKeyMan::GetKeys() const
AssertLockHeld(cs_desc_man);
if (m_storage.HasEncryptionKeys() && !m_storage.IsLocked()) {
KeyMap keys;
- for (auto key_pair : m_map_crypted_keys) {
+ for (const auto& key_pair : m_map_crypted_keys) {
const CPubKey& pubkey = key_pair.second.first;
const std::vector<unsigned char>& crypted_secret = key_pair.second.second;
CKey key;
@@ -1966,6 +2281,11 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_
desc_prefix = "tr(" + xpub + "/86'";
break;
}
+ case OutputType::UNKNOWN: {
+ // We should never have a DescriptorScriptPubKeyMan for an UNKNOWN OutputType,
+ // so if we get to this point something is wrong
+ assert(false);
+ }
} // no default case, so the compiler can warn about missing cases
assert(!desc_prefix.empty());
@@ -2075,10 +2395,21 @@ std::unique_ptr<FlatSigningProvider> DescriptorScriptPubKeyMan::GetSigningProvid
std::unique_ptr<FlatSigningProvider> DescriptorScriptPubKeyMan::GetSigningProvider(int32_t index, bool include_private) const
{
AssertLockHeld(cs_desc_man);
- // Get the scripts, keys, and key origins for this script
+
std::unique_ptr<FlatSigningProvider> out_keys = std::make_unique<FlatSigningProvider>();
- std::vector<CScript> scripts_temp;
- if (!m_wallet_descriptor.descriptor->ExpandFromCache(index, m_wallet_descriptor.cache, scripts_temp, *out_keys)) return nullptr;
+
+ // Fetch SigningProvider from cache to avoid re-deriving
+ auto it = m_map_signing_providers.find(index);
+ if (it != m_map_signing_providers.end()) {
+ out_keys->Merge(FlatSigningProvider{it->second});
+ } else {
+ // Get the scripts, keys, and key origins for this script
+ std::vector<CScript> scripts_temp;
+ if (!m_wallet_descriptor.descriptor->ExpandFromCache(index, m_wallet_descriptor.cache, scripts_temp, *out_keys)) return nullptr;
+
+ // Cache SigningProvider so we don't need to re-derive if we need this SigningProvider again
+ m_map_signing_providers[index] = *out_keys;
+ }
if (HavePrivateKeys() && include_private) {
FlatSigningProvider master_provider;
@@ -2107,7 +2438,7 @@ bool DescriptorScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const s
if (!coin_keys) {
continue;
}
- *keys = Merge(*keys, *coin_keys);
+ keys->Merge(std::move(*coin_keys));
}
return ::SignTransaction(tx, keys.get(), coins, sighash, input_errors);
@@ -2168,7 +2499,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
std::unique_ptr<FlatSigningProvider> keys = std::make_unique<FlatSigningProvider>();
std::unique_ptr<FlatSigningProvider> script_keys = GetSigningProvider(script, sign);
if (script_keys) {
- *keys = Merge(*keys, *script_keys);
+ keys->Merge(std::move(*script_keys));
} else {
// Maybe there are pubkeys listed that we can sign for
script_keys = std::make_unique<FlatSigningProvider>();
@@ -2176,7 +2507,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
const CPubKey& pubkey = pk_pair.first;
std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(pubkey);
if (pk_keys) {
- *keys = Merge(*keys, *pk_keys);
+ keys->Merge(std::move(*pk_keys));
}
}
for (const auto& pk_pair : input.m_tap_bip32_paths) {
@@ -2188,7 +2519,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
fullpubkey.Set(b, b + 33);
std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(fullpubkey);
if (pk_keys) {
- *keys = Merge(*keys, *pk_keys);
+ keys->Merge(std::move(*pk_keys));
}
}
}
@@ -2312,18 +2643,28 @@ const WalletDescriptor DescriptorScriptPubKeyMan::GetWalletDescriptor() const
return m_wallet_descriptor;
}
-const std::vector<CScript> DescriptorScriptPubKeyMan::GetScriptPubKeys() const
+const std::unordered_set<CScript, SaltedSipHasher> DescriptorScriptPubKeyMan::GetScriptPubKeys() const
+{
+ return GetScriptPubKeys(0);
+}
+
+const std::unordered_set<CScript, SaltedSipHasher> DescriptorScriptPubKeyMan::GetScriptPubKeys(int32_t minimum_index) const
{
LOCK(cs_desc_man);
- std::vector<CScript> script_pub_keys;
+ std::unordered_set<CScript, SaltedSipHasher> script_pub_keys;
script_pub_keys.reserve(m_map_script_pub_keys.size());
- for (auto const& script_pub_key: m_map_script_pub_keys) {
- script_pub_keys.push_back(script_pub_key.first);
+ for (auto const& [script_pub_key, index] : m_map_script_pub_keys) {
+ if (index >= minimum_index) script_pub_keys.insert(script_pub_key);
}
return script_pub_keys;
}
+int32_t DescriptorScriptPubKeyMan::GetEndRange() const
+{
+ return m_max_cached_index + 1;
+}
+
bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, const bool priv) const
{
LOCK(cs_desc_man);
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index eebc05330f..eb77015956 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -172,14 +172,14 @@ protected:
public:
explicit ScriptPubKeyMan(WalletStorage& storage) : m_storage(storage) {}
virtual ~ScriptPubKeyMan() {};
- virtual BResult<CTxDestination> GetNewDestination(const OutputType type) { return Untranslated("Not supported"); }
+ virtual util::Result<CTxDestination> GetNewDestination(const OutputType type) { return util::Error{Untranslated("Not supported")}; }
virtual isminetype IsMine(const CScript& script) const { return ISMINE_NO; }
//! Check that the given decryption key is valid for this ScriptPubKeyMan, i.e. it decrypts all of the keys handled by it.
virtual bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) { return false; }
virtual bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) { return false; }
- virtual bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error) { return false; }
+ virtual util::Result<CTxDestination> GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) { return util::Error{Untranslated("Not supported")}; }
virtual void KeepDestination(int64_t index, const OutputType& type) {}
virtual void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) {}
@@ -242,6 +242,9 @@ public:
virtual uint256 GetID() const { return uint256(); }
+ /** Returns a set of all the scriptPubKeys that this ScriptPubKeyMan watches */
+ virtual const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const { return {}; };
+
/** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */
template<typename... Params>
void WalletLogPrintf(std::string fmt, Params... parameters) const {
@@ -262,6 +265,8 @@ static const std::unordered_set<OutputType> LEGACY_OUTPUT_TYPES {
OutputType::BECH32,
};
+class DescriptorScriptPubKeyMan;
+
class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider
{
private:
@@ -360,13 +365,13 @@ private:
public:
using ScriptPubKeyMan::ScriptPubKeyMan;
- BResult<CTxDestination> GetNewDestination(const OutputType type) override;
+ util::Result<CTxDestination> GetNewDestination(const OutputType type) override;
isminetype IsMine(const CScript& script) const override;
bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override;
bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override;
- bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error) override;
+ util::Result<CTxDestination> GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) override;
void KeepDestination(int64_t index, const OutputType& type) override;
void ReturnDestination(int64_t index, bool internal, const CTxDestination&) override;
@@ -507,6 +512,13 @@ public:
const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; }
std::set<CKeyID> GetKeys() const override;
+ const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override;
+
+ /** Get the DescriptorScriptPubKeyMans (with private keys) that have the same scriptPubKeys as this LegacyScriptPubKeyMan.
+ * Does not modify this ScriptPubKeyMan. */
+ std::optional<MigrationData> MigrateToDescriptor();
+ /** Delete all the records ofthis LegacyScriptPubKeyMan from disk*/
+ bool DeleteRecords();
};
/** Wraps a LegacyScriptPubKeyMan so that it can be returned in a new unique_ptr. Does not provide privkeys */
@@ -547,6 +559,8 @@ private:
KeyMap GetKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
+ // Cached FlatSigningProviders to avoid regenerating them each time they are needed.
+ mutable std::map<int32_t, FlatSigningProvider> m_map_signing_providers;
// Fetch the SigningProvider for the given script and optionally include private keys
std::unique_ptr<FlatSigningProvider> GetSigningProvider(const CScript& script, bool include_private = false) const;
// Fetch the SigningProvider for the given pubkey and always include private keys. This should only be called by signing code.
@@ -568,13 +582,13 @@ public:
mutable RecursiveMutex cs_desc_man;
- BResult<CTxDestination> GetNewDestination(const OutputType type) override;
+ util::Result<CTxDestination> GetNewDestination(const OutputType type) override;
isminetype IsMine(const CScript& script) const override;
bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override;
bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override;
- bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error) override;
+ util::Result<CTxDestination> GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) override;
void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) override;
// Tops up the descriptor cache and m_map_script_pub_keys. The cache is stored in the wallet file
@@ -628,7 +642,9 @@ public:
void WriteDescriptor();
const WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
- const std::vector<CScript> GetScriptPubKeys() const;
+ const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override;
+ const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys(int32_t minimum_index) const;
+ int32_t GetEndRange() const;
bool GetDescriptorString(std::string& out, const bool priv) const;
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index c00a2cd023..644b2b587c 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -79,30 +79,113 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
}
-uint64_t CoinsResult::size() const
+size_t CoinsResult::Size() const
{
- return bech32m.size() + bech32.size() + P2SH_segwit.size() + legacy.size() + other.size();
+ size_t size{0};
+ for (const auto& it : coins) {
+ size += it.second.size();
+ }
+ return size;
}
-std::vector<COutput> CoinsResult::all() const
+std::vector<COutput> CoinsResult::All() const
{
std::vector<COutput> all;
- all.reserve(this->size());
- all.insert(all.end(), bech32m.begin(), bech32m.end());
- all.insert(all.end(), bech32.begin(), bech32.end());
- all.insert(all.end(), P2SH_segwit.begin(), P2SH_segwit.end());
- all.insert(all.end(), legacy.begin(), legacy.end());
- all.insert(all.end(), other.begin(), other.end());
+ all.reserve(coins.size());
+ for (const auto& it : coins) {
+ all.insert(all.end(), it.second.begin(), it.second.end());
+ }
return all;
}
-void CoinsResult::clear()
+void CoinsResult::Clear() {
+ coins.clear();
+}
+
+void CoinsResult::Erase(std::set<COutPoint>& preset_coins)
+{
+ for (auto& it : coins) {
+ auto& vec = it.second;
+ auto i = std::find_if(vec.begin(), vec.end(), [&](const COutput &c) { return preset_coins.count(c.outpoint);});
+ if (i != vec.end()) {
+ vec.erase(i);
+ break;
+ }
+ }
+}
+
+void CoinsResult::Shuffle(FastRandomContext& rng_fast)
+{
+ for (auto& it : coins) {
+ ::Shuffle(it.second.begin(), it.second.end(), rng_fast);
+ }
+}
+
+void CoinsResult::Add(OutputType type, const COutput& out)
+{
+ coins[type].emplace_back(out);
+}
+
+static OutputType GetOutputType(TxoutType type, bool is_from_p2sh)
{
- bech32m.clear();
- bech32.clear();
- P2SH_segwit.clear();
- legacy.clear();
- other.clear();
+ switch (type) {
+ case TxoutType::WITNESS_V1_TAPROOT:
+ return OutputType::BECH32M;
+ case TxoutType::WITNESS_V0_KEYHASH:
+ case TxoutType::WITNESS_V0_SCRIPTHASH:
+ if (is_from_p2sh) return OutputType::P2SH_SEGWIT;
+ else return OutputType::BECH32;
+ case TxoutType::SCRIPTHASH:
+ case TxoutType::PUBKEYHASH:
+ return OutputType::LEGACY;
+ default:
+ return OutputType::UNKNOWN;
+ }
+}
+
+// Fetch and validate the coin control selected inputs.
+// Coins could be internal (from the wallet) or external.
+util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const CCoinControl& coin_control,
+ const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
+{
+ PreSelectedInputs result;
+ std::vector<COutPoint> vPresetInputs;
+ coin_control.ListSelected(vPresetInputs);
+ for (const COutPoint& outpoint : vPresetInputs) {
+ int input_bytes = -1;
+ CTxOut txout;
+ if (auto ptr_wtx = wallet.GetWalletTx(outpoint.hash)) {
+ // Clearly invalid input, fail
+ if (ptr_wtx->tx->vout.size() <= outpoint.n) {
+ return util::Error{strprintf(_("Invalid pre-selected input %s"), outpoint.ToString())};
+ }
+ txout = ptr_wtx->tx->vout.at(outpoint.n);
+ input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control);
+ } else {
+ // The input is external. We did not find the tx in mapWallet.
+ if (!coin_control.GetExternalOutput(outpoint, txout)) {
+ return util::Error{strprintf(_("Not found pre-selected input %s"), outpoint.ToString())};
+ }
+ }
+
+ if (input_bytes == -1) {
+ input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
+ }
+
+ // If available, override calculated size with coin control specified size
+ if (coin_control.HasInputWeight(outpoint)) {
+ input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
+ }
+
+ if (input_bytes == -1) {
+ return util::Error{strprintf(_("Not solvable pre-selected input %s"), outpoint.ToString())}; // Not solvable, can't estimate size for fee
+ }
+
+ /* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */
+ COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate);
+ result.Insert(output, coin_selection_params.m_subtract_fee_outputs);
+ }
+ return result;
}
CoinsResult AvailableCoins(const CWallet& wallet,
@@ -192,7 +275,8 @@ CoinsResult AvailableCoins(const CWallet& wallet,
if (output.nValue < nMinimumAmount || output.nValue > nMaximumAmount)
continue;
- if (coinControl && coinControl->HasSelected() && !coinControl->m_allow_other_inputs && !coinControl->IsSelected(outpoint))
+ // Skip manually selected coins (the caller can fetch them directly)
+ if (coinControl && coinControl->HasSelected() && coinControl->IsSelected(outpoint))
continue;
if (wallet.IsLockedCoin(outpoint))
@@ -213,58 +297,33 @@ CoinsResult AvailableCoins(const CWallet& wallet,
std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(output.scriptPubKey);
- bool solvable = provider ? IsSolvable(*provider, output.scriptPubKey) : false;
+ int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), coinControl);
+ // Because CalculateMaximumSignedInputSize just uses ProduceSignature and makes a dummy signature,
+ // it is safe to assume that this input is solvable if input_bytes is greater -1.
+ bool solvable = input_bytes > -1;
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
// Filter by spendable outputs only
if (!spendable && only_spendable) continue;
- // When parsing a scriptPubKey, Solver returns the parsed pubkeys or hashes (depending on the script)
- // We don't need those here, so we are leaving them in return_values_unused
- std::vector<std::vector<uint8_t>> return_values_unused;
- TxoutType type;
- bool is_from_p2sh{false};
+ // Obtain script type
+ std::vector<std::vector<uint8_t>> script_solutions;
+ TxoutType type = Solver(output.scriptPubKey, script_solutions);
- // If the Output is P2SH and spendable, we want to know if it is
+ // If the output is P2SH and solvable, we want to know if it is
// a P2SH (legacy) or one of P2SH-P2WPKH, P2SH-P2WSH (P2SH-Segwit). We can determine
- // this from the redeemScript. If the Output is not spendable, it will be classified
+ // this from the redeemScript. If the output is not solvable, it will be classified
// as a P2SH (legacy), since we have no way of knowing otherwise without the redeemScript
- if (output.scriptPubKey.IsPayToScriptHash() && solvable) {
- CScript redeemScript;
- CTxDestination destination;
- if (!ExtractDestination(output.scriptPubKey, destination))
- continue;
- const CScriptID& hash = CScriptID(std::get<ScriptHash>(destination));
- if (!provider->GetCScript(hash, redeemScript))
- continue;
- type = Solver(redeemScript, return_values_unused);
+ bool is_from_p2sh{false};
+ if (type == TxoutType::SCRIPTHASH && solvable) {
+ CScript script;
+ if (!provider->GetCScript(CScriptID(uint160(script_solutions[0])), script)) continue;
+ type = Solver(script, script_solutions);
is_from_p2sh = true;
- } else {
- type = Solver(output.scriptPubKey, return_values_unused);
}
- int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), coinControl);
- COutput coin(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
- switch (type) {
- case TxoutType::WITNESS_UNKNOWN:
- case TxoutType::WITNESS_V1_TAPROOT:
- result.bech32m.push_back(coin);
- break;
- case TxoutType::WITNESS_V0_KEYHASH:
- case TxoutType::WITNESS_V0_SCRIPTHASH:
- if (is_from_p2sh) {
- result.P2SH_segwit.push_back(coin);
- break;
- }
- result.bech32.push_back(coin);
- break;
- case TxoutType::SCRIPTHASH:
- case TxoutType::PUBKEYHASH:
- result.legacy.push_back(coin);
- break;
- default:
- result.other.push_back(coin);
- };
+ result.Add(GetOutputType(type, is_from_p2sh),
+ COutput(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate));
// Cache total amount as we go
result.total_amount += output.nValue;
@@ -276,7 +335,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
}
// Checks the maximum number of UTXO's.
- if (nMaximumCount > 0 && result.size() >= nMaximumCount) {
+ if (nMaximumCount > 0 && result.Size() >= nMaximumCount) {
return result;
}
}
@@ -332,7 +391,7 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
std::map<CTxDestination, std::vector<COutput>> result;
- for (const COutput& coin : AvailableCoinsListUnspent(wallet).all()) {
+ for (COutput& coin : AvailableCoinsListUnspent(wallet).All()) {
CTxDestination address;
if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) &&
ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
@@ -455,31 +514,25 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
{
// Run coin selection on each OutputType and compute the Waste Metric
std::vector<SelectionResult> results;
- if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.legacy, coin_selection_params)}) {
- results.push_back(*result);
- }
- if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.P2SH_segwit, coin_selection_params)}) {
- results.push_back(*result);
- }
- if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.bech32, coin_selection_params)}) {
- results.push_back(*result);
- }
- if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.bech32m, coin_selection_params)}) {
- results.push_back(*result);
+ for (const auto& it : available_coins.coins) {
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, it.second, coin_selection_params)}) {
+ results.push_back(*result);
+ }
}
-
- // If we can't fund the transaction from any individual OutputType, run coin selection
- // over all available coins, else pick the best solution from the results
- if (results.size() == 0) {
- if (allow_mixed_output_types) {
- if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.all(), coin_selection_params)}) {
- return result;
- }
+ // If we have at least one solution for funding the transaction without mixing, choose the minimum one according to waste metric
+ // and return the result
+ if (results.size() > 0) return *std::min_element(results.begin(), results.end());
+
+ // If we can't fund the transaction from any individual OutputType, run coin selection one last time
+ // over all available coins, which would allow mixing
+ if (allow_mixed_output_types) {
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.All(), coin_selection_params)}) {
+ return result;
}
- return std::optional<SelectionResult>();
- };
- std::optional<SelectionResult> result{*std::min_element(results.begin(), results.end())};
- return result;
+ }
+ // Either mixing is not allowed and we couldn't find a solution from any single OutputType, or mixing was allowed and we still couldn't
+ // find a solution using all available coins
+ return std::nullopt;
};
std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins, const CoinSelectionParams& coin_selection_params)
@@ -487,32 +540,20 @@ std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, cons
// Vector of results. We will choose the best one based on waste.
std::vector<SelectionResult> results;
- // Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output.
- std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, true /* positive_only */);
+ std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, /*positive_only=*/true);
if (auto bnb_result{SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change)}) {
results.push_back(*bnb_result);
}
// The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
- std::vector<OutputGroup> all_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, false /* positive_only */);
- CAmount target_with_change = nTargetValue;
- // While nTargetValue includes the transaction fees for non-input things, it does not include the fee for creating a change output.
- // So we need to include that for KnapsackSolver and SRD as well, as we are expecting to create a change output.
- if (!coin_selection_params.m_subtract_fee_outputs) {
- target_with_change += coin_selection_params.m_change_fee;
- }
- if (auto knapsack_result{KnapsackSolver(all_groups, target_with_change, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast)}) {
- knapsack_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
+ std::vector<OutputGroup> all_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, /*positive_only=*/false);
+ if (auto knapsack_result{KnapsackSolver(all_groups, nTargetValue, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast)}) {
+ knapsack_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
results.push_back(*knapsack_result);
}
- // Include change for SRD as we want to avoid making really small change if the selection just
- // barely meets the target. Just use the lower bound change target instead of the randomly
- // generated one, since SRD will result in a random change amount anyway; avoid making the
- // target needlessly large.
- const CAmount srd_target = target_with_change + CHANGE_LOWER;
- if (auto srd_result{SelectCoinsSRD(positive_groups, srd_target, coin_selection_params.rng_fast)}) {
- srd_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
+ if (auto srd_result{SelectCoinsSRD(positive_groups, nTargetValue, coin_selection_params.rng_fast)}) {
+ srd_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
results.push_back(*srd_result);
}
@@ -527,76 +568,42 @@ std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, cons
return best_result;
}
-std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params)
+std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const PreSelectedInputs& pre_set_inputs,
+ const CAmount& nTargetValue, const CCoinControl& coin_control,
+ const CoinSelectionParams& coin_selection_params)
{
- CAmount value_to_select = nTargetValue;
-
- OutputGroup preset_inputs(coin_selection_params);
-
- // calculate value from preset inputs and store them
- std::set<COutPoint> preset_coins;
-
- std::vector<COutPoint> vPresetInputs;
- coin_control.ListSelected(vPresetInputs);
- for (const COutPoint& outpoint : vPresetInputs) {
- int input_bytes = -1;
- CTxOut txout;
- auto ptr_wtx = wallet.GetWalletTx(outpoint.hash);
- if (ptr_wtx) {
- // Clearly invalid input, fail
- if (ptr_wtx->tx->vout.size() <= outpoint.n) {
- return std::nullopt;
- }
- txout = ptr_wtx->tx->vout.at(outpoint.n);
- input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control);
- } else {
- // The input is external. We did not find the tx in mapWallet.
- if (!coin_control.GetExternalOutput(outpoint, txout)) {
- return std::nullopt;
- }
- input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
- }
- // If available, override calculated size with coin control specified size
- if (coin_control.HasInputWeight(outpoint)) {
- input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
- }
+ // Deduct preset inputs amount from the search target
+ CAmount selection_target = nTargetValue - pre_set_inputs.total_amount;
- if (input_bytes == -1) {
- return std::nullopt; // Not solvable, can't estimate size for fee
- }
-
- /* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */
- COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate);
- if (coin_selection_params.m_subtract_fee_outputs) {
- value_to_select -= output.txout.nValue;
- } else {
- value_to_select -= output.GetEffectiveValue();
- }
- preset_coins.insert(outpoint);
- /* Set ancestors and descendants to 0 as they don't matter for preset inputs since no actual selection is being done.
- * positive_only is set to false because we want to include all preset inputs, even if they are dust.
- */
- preset_inputs.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
- }
+ // Return if automatic coin selection is disabled, and we don't cover the selection target
+ if (!coin_control.m_allow_other_inputs && selection_target > 0) return std::nullopt;
- // coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
- if (coin_control.HasSelected() && !coin_control.m_allow_other_inputs) {
+ // Return if we can cover the target only with the preset inputs
+ if (selection_target <= 0) {
SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL);
- result.AddInput(preset_inputs);
- if (result.GetSelectedValue() < nTargetValue) return std::nullopt;
- result.ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
+ result.AddInputs(pre_set_inputs.coins, coin_selection_params.m_subtract_fee_outputs);
+ result.ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
return result;
}
- // remove preset inputs from coins so that Coin Selection doesn't pick them.
- if (coin_control.HasSelected()) {
- available_coins.legacy.erase(remove_if(available_coins.legacy.begin(), available_coins.legacy.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.legacy.end());
- available_coins.P2SH_segwit.erase(remove_if(available_coins.P2SH_segwit.begin(), available_coins.P2SH_segwit.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.P2SH_segwit.end());
- available_coins.bech32.erase(remove_if(available_coins.bech32.begin(), available_coins.bech32.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.bech32.end());
- available_coins.bech32m.erase(remove_if(available_coins.bech32m.begin(), available_coins.bech32m.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.bech32m.end());
- available_coins.other.erase(remove_if(available_coins.other.begin(), available_coins.other.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.other.end());
+ // Start wallet Coin Selection procedure
+ auto op_selection_result = AutomaticCoinSelection(wallet, available_coins, selection_target, coin_control, coin_selection_params);
+ if (!op_selection_result) return op_selection_result;
+
+ // If needed, add preset inputs to the automatic coin selection result
+ if (!pre_set_inputs.coins.empty()) {
+ SelectionResult preselected(pre_set_inputs.total_amount, SelectionAlgorithm::MANUAL);
+ preselected.AddInputs(pre_set_inputs.coins, coin_selection_params.m_subtract_fee_outputs);
+ op_selection_result->Merge(preselected);
+ op_selection_result->ComputeAndSetWaste(coin_selection_params.min_viable_change,
+ coin_selection_params.m_cost_of_change,
+ coin_selection_params.m_change_fee);
}
+ return op_selection_result;
+}
+std::optional<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, CoinsResult& available_coins, const CAmount& value_to_select, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params)
+{
unsigned int limit_ancestor_count = 0;
unsigned int limit_descendant_count = 0;
wallet.chain().getPackageLimits(limit_ancestor_count, limit_descendant_count);
@@ -606,24 +613,17 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
// form groups from remaining coins; note that preset coins will not
// automatically have their associated (same address) coins included
- if (coin_control.m_avoid_partial_spends && available_coins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
+ if (coin_control.m_avoid_partial_spends && available_coins.Size() > OUTPUT_GROUP_MAX_ENTRIES) {
// Cases where we have 101+ outputs all pointing to the same destination may result in
// privacy leaks as they will potentially be deterministically sorted. We solve that by
// explicitly shuffling the outputs before processing
- Shuffle(available_coins.legacy.begin(), available_coins.legacy.end(), coin_selection_params.rng_fast);
- Shuffle(available_coins.P2SH_segwit.begin(), available_coins.P2SH_segwit.end(), coin_selection_params.rng_fast);
- Shuffle(available_coins.bech32.begin(), available_coins.bech32.end(), coin_selection_params.rng_fast);
- Shuffle(available_coins.bech32m.begin(), available_coins.bech32m.end(), coin_selection_params.rng_fast);
- Shuffle(available_coins.other.begin(), available_coins.other.end(), coin_selection_params.rng_fast);
+ available_coins.Shuffle(coin_selection_params.rng_fast);
}
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
// transaction at a target feerate. If an attempt fails, more attempts may be made using a more
// permissive CoinEligibilityFilter.
std::optional<SelectionResult> res = [&] {
- // Pre-selected inputs already cover the target amount.
- if (value_to_select <= 0) return std::make_optional(SelectionResult(nTargetValue, SelectionAlgorithm::MANUAL));
-
// If possible, fund the transaction with confirmed UTXOs only. Prefer at least six
// confirmations on outputs received from other wallets and only spend confirmed change.
if (auto r1{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), available_coins, coin_selection_params, /*allow_mixed_output_types=*/false)}) return r1;
@@ -673,14 +673,6 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
return std::optional<SelectionResult>();
}();
- if (!res) return std::nullopt;
-
- // Add preset inputs to result
- res->AddInput(preset_inputs);
- if (res->m_algo == SelectionAlgorithm::MANUAL) {
- res->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
- }
-
return res;
}
@@ -758,7 +750,7 @@ static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng
}
}
-static BResult<CreatedTransactionResult> CreateTransactionInternal(
+static util::Result<CreatedTransactionResult> CreateTransactionInternal(
CWallet& wallet,
const std::vector<CRecipient>& vecSend,
int change_pos,
@@ -792,7 +784,6 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
coin_selection_params.m_subtract_fee_outputs = true;
}
}
- coin_selection_params.m_change_target = GenerateChangeTarget(std::floor(recipients_sum / vecSend.size()), rng_fast);
// Create change script that will be used if we need change
CScript scriptChange;
@@ -812,11 +803,13 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
// Reserve a new key pair from key pool. If it fails, provide a dummy
// destination in case we don't need change.
CTxDestination dest;
- bilingual_str dest_err;
- if (!reservedest.GetReservedDestination(dest, true, dest_err)) {
- error = _("Transaction needs a change address, but we can't generate it.") + Untranslated(" ") + dest_err;
+ auto op_dest = reservedest.GetReservedDestination(true);
+ if (!op_dest) {
+ error = _("Transaction needs a change address, but we can't generate it.") + Untranslated(" ") + util::ErrorString(op_dest);
+ } else {
+ dest = *op_dest;
+ scriptChange = GetScriptForDestination(dest);
}
- scriptChange = GetScriptForDestination(dest);
// A valid destination implies a change script (and
// vice-versa). An empty change script will abort later, if the
// change keypool ran out, but change is required.
@@ -844,11 +837,11 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
// Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
// provided one
if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) {
- return strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB));
+ return util::Error{strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB))};
}
if (feeCalc.reason == FeeReason::FALLBACK && !wallet.m_allow_fallback_fee) {
// eventually allow a fallback fee
- return _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
+ return util::Error{_("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.")};
}
// Calculate the cost of change
@@ -859,6 +852,15 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
coin_selection_params.m_change_fee = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.change_output_size);
coin_selection_params.m_cost_of_change = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size) + coin_selection_params.m_change_fee;
+ coin_selection_params.m_min_change_target = GenerateChangeTarget(std::floor(recipients_sum / vecSend.size()), coin_selection_params.m_change_fee, rng_fast);
+
+ // The smallest change amount should be:
+ // 1. at least equal to dust threshold
+ // 2. at least 1 sat greater than fees to spend it at m_discard_feerate
+ const auto dust = GetDustThreshold(change_prototype_txout, coin_selection_params.m_discard_feerate);
+ const auto change_spend_fee = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size);
+ coin_selection_params.min_viable_change = std::max(change_spend_fee + 1, dust);
+
// vouts to the payees
if (!coin_selection_params.m_subtract_fee_outputs) {
coin_selection_params.tx_noinputs_size = 10; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 witness overhead (dummy, flag, stack size)
@@ -874,7 +876,7 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
}
if (IsDust(txout, wallet.chain().relayDustFee())) {
- return _("Transaction amount too small");
+ return util::Error{_("Transaction amount too small")};
}
txNew.vout.push_back(txout);
}
@@ -883,39 +885,48 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size);
CAmount selection_target = recipients_sum + not_input_fees;
- // Get available coins
- auto available_coins = AvailableCoins(wallet,
- &coin_control,
- coin_selection_params.m_effective_feerate,
- 1, /*nMinimumAmount*/
- MAX_MONEY, /*nMaximumAmount*/
- MAX_MONEY, /*nMinimumSumAmount*/
- 0); /*nMaximumCount*/
-
- // Choose coins to use
- std::optional<SelectionResult> result = SelectCoins(wallet, available_coins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
- if (!result) {
- return _("Insufficient funds");
+ // Fetch manually selected coins
+ PreSelectedInputs preset_inputs;
+ if (coin_control.HasSelected()) {
+ auto res_fetch_inputs = FetchSelectedInputs(wallet, coin_control, coin_selection_params);
+ if (!res_fetch_inputs) return util::Error{util::ErrorString(res_fetch_inputs)};
+ preset_inputs = *res_fetch_inputs;
}
- TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->m_algo).c_str(), result->m_target, result->GetWaste(), result->GetSelectedValue());
- // Always make a change output
- // We will reduce the fee from this change output later, and remove the output if it is too small.
- const CAmount change_and_fee = result->GetSelectedValue() - recipients_sum;
- assert(change_and_fee >= 0);
- CTxOut newTxOut(change_and_fee, scriptChange);
+ // Fetch wallet available coins if "other inputs" are
+ // allowed (coins automatically selected by the wallet)
+ CoinsResult available_coins;
+ if (coin_control.m_allow_other_inputs) {
+ available_coins = AvailableCoins(wallet,
+ &coin_control,
+ coin_selection_params.m_effective_feerate,
+ 1, /*nMinimumAmount*/
+ MAX_MONEY, /*nMaximumAmount*/
+ MAX_MONEY, /*nMinimumSumAmount*/
+ 0); /*nMaximumCount*/
+ }
- if (nChangePosInOut == -1) {
- // Insert change txn at random position:
- nChangePosInOut = rng_fast.randrange(txNew.vout.size() + 1);
+ // Choose coins to use
+ std::optional<SelectionResult> result = SelectCoins(wallet, available_coins, preset_inputs, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
+ if (!result) {
+ return util::Error{_("Insufficient funds")};
}
- else if ((unsigned int)nChangePosInOut > txNew.vout.size()) {
- return _("Transaction change output index out of range");
+ TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->GetAlgo()).c_str(), result->GetTarget(), result->GetWaste(), result->GetSelectedValue());
+
+ const CAmount change_amount = result->GetChange(coin_selection_params.min_viable_change, coin_selection_params.m_change_fee);
+ if (change_amount > 0) {
+ CTxOut newTxOut(change_amount, scriptChange);
+ if (nChangePosInOut == -1) {
+ // Insert change txn at random position:
+ nChangePosInOut = rng_fast.randrange(txNew.vout.size() + 1);
+ } else if ((unsigned int)nChangePosInOut > txNew.vout.size()) {
+ return util::Error{_("Transaction change output index out of range")};
+ }
+ txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
+ } else {
+ nChangePosInOut = -1;
}
- assert(nChangePosInOut != -1);
- auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
-
// Shuffle selected coins and fill in final vin
std::vector<COutput> selected_coins = result->GetShuffledInputVector();
@@ -937,44 +948,25 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control);
int nBytes = tx_sizes.vsize;
if (nBytes == -1) {
- return _("Missing solving data for estimating transaction size");
+ return util::Error{_("Missing solving data for estimating transaction size")};
}
- nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
+ CAmount fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
+ nFeeRet = result->GetSelectedValue() - recipients_sum - change_amount;
- // Subtract fee from the change output if not subtracting it from recipient outputs
- CAmount fee_needed = nFeeRet;
- if (!coin_selection_params.m_subtract_fee_outputs) {
- change_position->nValue -= fee_needed;
- }
-
- // We want to drop the change to fees if:
- // 1. The change output would be dust
- // 2. The change is within the (almost) exact match window, i.e. it is less than or equal to the cost of the change output (cost_of_change)
- CAmount change_amount = change_position->nValue;
- if (IsDust(*change_position, coin_selection_params.m_discard_feerate) || change_amount <= coin_selection_params.m_cost_of_change)
- {
- nChangePosInOut = -1;
- change_amount = 0;
- txNew.vout.erase(change_position);
-
- // Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those
- tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control);
- nBytes = tx_sizes.vsize;
- fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
- }
-
- // The only time that fee_needed should be less than the amount available for fees (in change_and_fee - change_amount) is when
+ // The only time that fee_needed should be less than the amount available for fees is when
// we are subtracting the fee from the outputs. If this occurs at any other time, it is a bug.
- assert(coin_selection_params.m_subtract_fee_outputs || fee_needed <= change_and_fee - change_amount);
+ assert(coin_selection_params.m_subtract_fee_outputs || fee_needed <= nFeeRet);
- // Update nFeeRet in case fee_needed changed due to dropping the change output
- if (fee_needed <= change_and_fee - change_amount) {
- nFeeRet = change_and_fee - change_amount;
+ // If there is a change output and we overpay the fees then increase the change to match the fee needed
+ if (nChangePosInOut != -1 && fee_needed < nFeeRet) {
+ auto& change = txNew.vout.at(nChangePosInOut);
+ change.nValue += nFeeRet - fee_needed;
+ nFeeRet = fee_needed;
}
// Reduce output values for subtractFeeFromAmount
if (coin_selection_params.m_subtract_fee_outputs) {
- CAmount to_reduce = fee_needed + change_amount - change_and_fee;
+ CAmount to_reduce = fee_needed - nFeeRet;
int i = 0;
bool fFirst = true;
for (const auto& recipient : vecSend)
@@ -997,9 +989,9 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
// Error if this output is reduced to be below dust
if (IsDust(txout, wallet.chain().relayDustFee())) {
if (txout.nValue < 0) {
- return _("The transaction amount is too small to pay the fee");
+ return util::Error{_("The transaction amount is too small to pay the fee")};
} else {
- return _("The transaction amount is too small to send after the fee has been deducted");
+ return util::Error{_("The transaction amount is too small to send after the fee has been deducted")};
}
}
}
@@ -1010,11 +1002,11 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
// Give up if change keypool ran out and change is required
if (scriptChange.empty() && nChangePosInOut != -1) {
- return error;
+ return util::Error{error};
}
if (sign && !wallet.SignTransaction(txNew)) {
- return _("Signing transaction failed");
+ return util::Error{_("Signing transaction failed")};
}
// Return the constructed transaction data.
@@ -1024,17 +1016,17 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) ||
(!sign && tx_sizes.weight > MAX_STANDARD_TX_WEIGHT))
{
- return _("Transaction too large");
+ return util::Error{_("Transaction too large")};
}
if (nFeeRet > wallet.m_default_max_tx_fee) {
- return TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
+ return util::Error{TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED)};
}
if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits
if (!wallet.chain().checkChainLimits(tx)) {
- return _("Transaction has too long of a mempool chain");
+ return util::Error{_("Transaction has too long of a mempool chain")};
}
}
@@ -1053,7 +1045,7 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut, feeCalc);
}
-BResult<CreatedTransactionResult> CreateTransaction(
+util::Result<CreatedTransactionResult> CreateTransaction(
CWallet& wallet,
const std::vector<CRecipient>& vecSend,
int change_pos,
@@ -1061,28 +1053,26 @@ BResult<CreatedTransactionResult> CreateTransaction(
bool sign)
{
if (vecSend.empty()) {
- return _("Transaction must have at least one recipient");
+ return util::Error{_("Transaction must have at least one recipient")};
}
if (std::any_of(vecSend.cbegin(), vecSend.cend(), [](const auto& recipient){ return recipient.nAmount < 0; })) {
- return _("Transaction amounts must not be negative");
+ return util::Error{_("Transaction amounts must not be negative")};
}
LOCK(wallet.cs_wallet);
auto res = CreateTransactionInternal(wallet, vecSend, change_pos, coin_control, sign);
- TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), res.HasRes(),
- res ? res.GetObj().fee : 0, res ? res.GetObj().change_pos : 0);
+ TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), bool(res),
+ res ? res->fee : 0, res ? res->change_pos : 0);
if (!res) return res;
- const auto& txr_ungrouped = res.GetObj();
+ const auto& txr_ungrouped = *res;
// try with avoidpartialspends unless it's enabled already
if (txr_ungrouped.fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
TRACE1(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str());
CCoinControl tmp_cc = coin_control;
tmp_cc.m_avoid_partial_spends = true;
- auto res_tx_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, tmp_cc, sign);
- // Helper optional class for now
- std::optional<CreatedTransactionResult> txr_grouped{res_tx_grouped.HasRes() ? std::make_optional(res_tx_grouped.GetObj()) : std::nullopt};
+ auto txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, tmp_cc, sign);
// if fee of this alternative one is within the range of the max fee, we use this one
const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped.fee + wallet.m_max_aps_fee) : false};
TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, txr_grouped.has_value(),
@@ -1090,7 +1080,7 @@ BResult<CreatedTransactionResult> CreateTransaction(
if (txr_grouped) {
wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n",
txr_ungrouped.fee, txr_grouped->fee, use_aps ? "grouped" : "non-grouped");
- if (use_aps) return res_tx_grouped;
+ if (use_aps) return txr_grouped;
}
}
return res;
@@ -1120,21 +1110,25 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet,
wallet.chain().findCoins(coins);
for (const CTxIn& txin : tx.vin) {
- // if it's not in the wallet and corresponding UTXO is found than select as external output
const auto& outPoint = txin.prevout;
- if (wallet.mapWallet.find(outPoint.hash) == wallet.mapWallet.end() && !coins[outPoint].out.IsNull()) {
- coinControl.SelectExternal(outPoint, coins[outPoint].out);
- } else {
+ if (wallet.IsMine(outPoint)) {
+ // The input was found in the wallet, so select as internal
coinControl.Select(outPoint);
+ } else if (coins[outPoint].out.IsNull()) {
+ error = _("Unable to find UTXO for external input");
+ return false;
+ } else {
+ // The input was not in the wallet, but is in the UTXO set, so select as external
+ coinControl.SelectExternal(outPoint, coins[outPoint].out);
}
}
auto res = CreateTransaction(wallet, vecSend, nChangePosInOut, coinControl, false);
if (!res) {
- error = res.GetError();
+ error = util::ErrorString(res);
return false;
}
- const auto& txr = res.GetObj();
+ const auto& txr = *res;
CTransactionRef tx_new = txr.tx;
nFeeRet = txr.fee;
nChangePosInOut = txr.change_pos;
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index 96ecd690be..b66bb3797c 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -34,26 +34,22 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* walle
* This struct is really just a wrapper around OutputType vectors with a convenient
* method for concatenating and returning all COutputs as one vector.
*
- * clear(), size() methods are implemented so that one can interact with
- * the CoinsResult struct as if it was a vector
+ * Size(), Clear(), Erase(), Shuffle(), and Add() methods are implemented to
+ * allow easy interaction with the struct.
*/
struct CoinsResult {
- /** Vectors for each OutputType */
- std::vector<COutput> legacy;
- std::vector<COutput> P2SH_segwit;
- std::vector<COutput> bech32;
- std::vector<COutput> bech32m;
-
- /** Other is a catch-all for anything that doesn't match the known OutputTypes */
- std::vector<COutput> other;
+ std::map<OutputType, std::vector<COutput>> coins;
/** Concatenate and return all COutputs as one vector */
- std::vector<COutput> all() const;
+ std::vector<COutput> All() const;
/** The following methods are provided so that CoinsResult can mimic a vector,
* i.e., methods can work with individual OutputType vectors or on the entire object */
- uint64_t size() const;
- void clear();
+ size_t Size() const;
+ void Clear();
+ void Erase(std::set<COutPoint>& preset_coins);
+ void Shuffle(FastRandomContext& rng_fast);
+ void Add(OutputType type, const COutput& out);
/** Sum of all available coins */
CAmount total_amount{0};
@@ -125,9 +121,35 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins,
const CoinSelectionParams& coin_selection_params);
+// User manually selected inputs that must be part of the transaction
+struct PreSelectedInputs
+{
+ std::set<COutput> coins;
+ // If subtract fee from outputs is disabled, the 'total_amount'
+ // will be the sum of each output effective value
+ // instead of the sum of the outputs amount
+ CAmount total_amount{0};
+
+ void Insert(const COutput& output, bool subtract_fee_outputs)
+ {
+ if (subtract_fee_outputs) {
+ total_amount += output.txout.nValue;
+ } else {
+ total_amount += output.GetEffectiveValue();
+ }
+ coins.insert(output);
+ }
+};
+
/**
- * Select a set of coins such that nTargetValue is met and at least
- * all coins from coin_control are selected; never select unconfirmed coins if they are not ours
+ * Fetch and validate coin control selected inputs.
+ * Coins could be internal (from the wallet) or external.
+*/
+util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const CCoinControl& coin_control,
+ const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
+
+/**
+ * Select a set of coins such that nTargetValue is met; never select unconfirmed coins if they are not ours
* param@[in] wallet The wallet which provides data necessary to spend the selected coins
* param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering
* param@[in] nTargetValue The target value
@@ -136,9 +158,17 @@ std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, cons
* returns If successful, a SelectionResult containing the selected coins
* If failed, a nullopt.
*/
-std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control,
+std::optional<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control,
const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
+/**
+ * Select all coins from coin_control, and if coin_control 'm_allow_other_inputs=true', call 'AutomaticCoinSelection' to
+ * select a set of coins such that nTargetValue - pre_set_inputs.total_amount is met.
+ */
+std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const PreSelectedInputs& pre_set_inputs,
+ const CAmount& nTargetValue, const CCoinControl& coin_control,
+ const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
+
struct CreatedTransactionResult
{
CTransactionRef tx;
@@ -155,7 +185,7 @@ struct CreatedTransactionResult
* selected by SelectCoins(); Also create the change output, when needed
* @note passing change_pos as -1 will result in setting a random position
*/
-BResult<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, const CCoinControl& coin_control, bool sign = true);
+util::Result<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, const CCoinControl& coin_control, bool sign = true);
/**
* Insert additional inputs into the transaction by
diff --git a/src/wallet/test/availablecoins_tests.cpp b/src/wallet/test/availablecoins_tests.cpp
index 01d24da981..2427a343d5 100644
--- a/src/wallet/test/availablecoins_tests.cpp
+++ b/src/wallet/test/availablecoins_tests.cpp
@@ -33,7 +33,7 @@ public:
constexpr int RANDOM_CHANGE_POSITION = -1;
auto res = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, dummy);
BOOST_CHECK(res);
- tx = res.GetObj().tx;
+ tx = res->tx;
}
wallet->CommitTransaction(tx, {}, {});
CMutableTransaction blocktx;
@@ -44,6 +44,7 @@ public:
CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
LOCK(wallet->cs_wallet);
+ LOCK(m_node.chainman->GetMutex());
wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
auto it = wallet->mapWallet.find(tx->GetHash());
BOOST_CHECK(it != wallet->mapWallet.end());
@@ -57,14 +58,14 @@ public:
BOOST_FIXTURE_TEST_CASE(BasicOutputTypesTest, AvailableCoinsTestingSetup)
{
CoinsResult available_coins;
- BResult<CTxDestination> dest;
+ util::Result<CTxDestination> dest{util::Error{}};
LOCK(wallet->cs_wallet);
// Verify our wallet has one usable coinbase UTXO before starting
// This UTXO is a P2PK, so it should show up in the Other bucket
available_coins = AvailableCoins(*wallet);
- BOOST_CHECK_EQUAL(available_coins.size(), 1U);
- BOOST_CHECK_EQUAL(available_coins.other.size(), 1U);
+ BOOST_CHECK_EQUAL(available_coins.Size(), 1U);
+ BOOST_CHECK_EQUAL(available_coins.coins[OutputType::UNKNOWN].size(), 1U);
// We will create a self transfer for each of the OutputTypes and
// verify it is put in the correct bucket after running GetAvailablecoins
@@ -75,30 +76,31 @@ BOOST_FIXTURE_TEST_CASE(BasicOutputTypesTest, AvailableCoinsTestingSetup)
// Bech32m
dest = wallet->GetNewDestination(OutputType::BECH32M, "");
- BOOST_ASSERT(dest.HasRes());
- AddTx(CRecipient{{GetScriptForDestination(dest.GetObj())}, 1 * COIN, /*fSubtractFeeFromAmount=*/true});
+ BOOST_ASSERT(dest);
+ AddTx(CRecipient{{GetScriptForDestination(*dest)}, 1 * COIN, /*fSubtractFeeFromAmount=*/true});
available_coins = AvailableCoins(*wallet);
- BOOST_CHECK_EQUAL(available_coins.bech32m.size(), 2U);
+ BOOST_CHECK_EQUAL(available_coins.coins[OutputType::BECH32M].size(), 2U);
// Bech32
dest = wallet->GetNewDestination(OutputType::BECH32, "");
- BOOST_ASSERT(dest.HasRes());
- AddTx(CRecipient{{GetScriptForDestination(dest.GetObj())}, 2 * COIN, /*fSubtractFeeFromAmount=*/true});
+ BOOST_ASSERT(dest);
+ AddTx(CRecipient{{GetScriptForDestination(*dest)}, 2 * COIN, /*fSubtractFeeFromAmount=*/true});
available_coins = AvailableCoins(*wallet);
- BOOST_CHECK_EQUAL(available_coins.bech32.size(), 2U);
+ BOOST_CHECK_EQUAL(available_coins.coins[OutputType::BECH32].size(), 2U);
// P2SH-SEGWIT
dest = wallet->GetNewDestination(OutputType::P2SH_SEGWIT, "");
- AddTx(CRecipient{{GetScriptForDestination(dest.GetObj())}, 3 * COIN, /*fSubtractFeeFromAmount=*/true});
+ BOOST_ASSERT(dest);
+ AddTx(CRecipient{{GetScriptForDestination(*dest)}, 3 * COIN, /*fSubtractFeeFromAmount=*/true});
available_coins = AvailableCoins(*wallet);
- BOOST_CHECK_EQUAL(available_coins.P2SH_segwit.size(), 2U);
+ BOOST_CHECK_EQUAL(available_coins.coins[OutputType::P2SH_SEGWIT].size(), 2U);
// Legacy (P2PKH)
dest = wallet->GetNewDestination(OutputType::LEGACY, "");
- BOOST_ASSERT(dest.HasRes());
- AddTx(CRecipient{{GetScriptForDestination(dest.GetObj())}, 4 * COIN, /*fSubtractFeeFromAmount=*/true});
+ BOOST_ASSERT(dest);
+ AddTx(CRecipient{{GetScriptForDestination(*dest)}, 4 * COIN, /*fSubtractFeeFromAmount=*/true});
available_coins = AvailableCoins(*wallet);
- BOOST_CHECK_EQUAL(available_coins.legacy.size(), 2U);
+ BOOST_CHECK_EQUAL(available_coins.coins[OutputType::LEGACY].size(), 2U);
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index cd7fd3f4dd..f9c8c8ee9d 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -74,9 +74,7 @@ static void add_coin(CoinsResult& available_coins, CWallet& wallet, const CAmoun
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
if (spendable) {
- auto op_dest = wallet.GetNewDestination(OutputType::BECH32, "");
- assert(op_dest.HasRes());
- tx.vout[nInput].scriptPubKey = GetScriptForDestination(op_dest.GetObj());
+ tx.vout[nInput].scriptPubKey = GetScriptForDestination(*Assert(wallet.GetNewDestination(OutputType::BECH32, "")));
}
uint256 txid = tx.GetHash();
@@ -85,7 +83,7 @@ static void add_coin(CoinsResult& available_coins, CWallet& wallet, const CAmoun
assert(ret.second);
CWalletTx& wtx = (*ret.first).second;
const auto& txout = wtx.tx->vout.at(nInput);
- available_coins.bech32.emplace_back(COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
+ available_coins.coins[OutputType::BECH32].emplace_back(COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
}
/** Check if SelectionResult a is equivalent to SelectionResult b.
@@ -250,9 +248,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
// Iteration exhaustion test
CAmount target = make_hard_case(17, utxo_pool);
- BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, 0)); // Should exhaust
+ BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, 1)); // Should exhaust
target = make_hard_case(14, utxo_pool);
- const auto result7 = SelectCoinsBnB(GroupCoins(utxo_pool), target, 0); // Should not exhaust
+ const auto result7 = SelectCoinsBnB(GroupCoins(utxo_pool), target, 1); // Should not exhaust
BOOST_CHECK(result7);
// Test same value early bailout optimization
@@ -291,8 +289,8 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
// Make sure that effective value is working in AttemptSelection when BnB is used
CoinSelectionParams coin_selection_params_bnb{
rand,
- /*change_output_size=*/ 0,
- /*change_spend_size=*/ 0,
+ /*change_output_size=*/ 31,
+ /*change_spend_size=*/ 68,
/*min_change_target=*/ 0,
/*effective_feerate=*/ CFeeRate(3000),
/*long_term_feerate=*/ CFeeRate(1000),
@@ -300,6 +298,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
/*tx_noinputs_size=*/ 0,
/*avoid_partial=*/ false,
};
+ coin_selection_params_bnb.m_change_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_output_size);
+ coin_selection_params_bnb.m_cost_of_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size) + coin_selection_params_bnb.m_change_fee;
+ coin_selection_params_bnb.min_viable_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size);
{
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
wallet->LoadWallet();
@@ -310,15 +311,15 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
CoinsResult available_coins;
add_coin(available_coins, *wallet, 1, coin_selection_params_bnb.m_effective_feerate);
- available_coins.all().at(0).input_bytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
- BOOST_CHECK(!SelectCoinsBnB(GroupCoins(available_coins.all()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
+ available_coins.All().at(0).input_bytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
+ BOOST_CHECK(!SelectCoinsBnB(GroupCoins(available_coins.All()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
// Test fees subtracted from output:
- available_coins.clear();
+ available_coins.Clear();
add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate);
- available_coins.all().at(0).input_bytes = 40;
+ available_coins.All().at(0).input_bytes = 40;
coin_selection_params_bnb.m_subtract_fee_outputs = true;
- const auto result9 = SelectCoinsBnB(GroupCoins(available_coins.all()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
+ const auto result9 = SelectCoinsBnB(GroupCoins(available_coins.All()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
BOOST_CHECK(result9);
BOOST_CHECK_EQUAL(result9->GetSelectedValue(), 1 * CENT);
}
@@ -337,9 +338,13 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
add_coin(available_coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
CCoinControl coin_control;
coin_control.m_allow_other_inputs = true;
- coin_control.Select(available_coins.all().at(0).outpoint);
+ COutput select_coin = available_coins.All().at(0);
+ coin_control.Select(select_coin.outpoint);
+ PreSelectedInputs selected_input;
+ selected_input.Insert(select_coin, coin_selection_params_bnb.m_subtract_fee_outputs);
+ available_coins.coins[OutputType::BECH32].erase(available_coins.coins[OutputType::BECH32].begin());
coin_selection_params_bnb.m_effective_feerate = CFeeRate(0);
- const auto result10 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
+ const auto result10 = SelectCoins(*wallet, available_coins, selected_input, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(result10);
}
{
@@ -362,9 +367,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
expected_result.Clear();
add_coin(10 * CENT, 2, expected_result);
CCoinControl coin_control;
- const auto result11 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
+ const auto result11 = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/{}, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(EquivalentResult(expected_result, *result11));
- available_coins.clear();
+ available_coins.Clear();
// more coins should be selected when effective fee < long term fee
coin_selection_params_bnb.m_effective_feerate = CFeeRate(3000);
@@ -377,9 +382,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
expected_result.Clear();
add_coin(9 * CENT, 2, expected_result);
add_coin(1 * CENT, 2, expected_result);
- const auto result12 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
+ const auto result12 = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/{}, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(EquivalentResult(expected_result, *result12));
- available_coins.clear();
+ available_coins.Clear();
// pre selected coin should be selected even if disadvantageous
coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000);
@@ -393,8 +398,12 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
add_coin(9 * CENT, 2, expected_result);
add_coin(1 * CENT, 2, expected_result);
coin_control.m_allow_other_inputs = true;
- coin_control.Select(available_coins.all().at(1).outpoint); // pre select 9 coin
- const auto result13 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb);
+ COutput select_coin = available_coins.All().at(1); // pre select 9 coin
+ coin_control.Select(select_coin.outpoint);
+ PreSelectedInputs selected_input;
+ selected_input.Insert(select_coin, coin_selection_params_bnb.m_subtract_fee_outputs);
+ available_coins.coins[OutputType::BECH32].erase(++available_coins.coins[OutputType::BECH32].begin());
+ const auto result13 = SelectCoins(*wallet, available_coins, selected_input, 10 * CENT, coin_control, coin_selection_params_bnb);
BOOST_CHECK(EquivalentResult(expected_result, *result13));
}
}
@@ -415,28 +424,28 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// test multiple times to allow for differences in the shuffle order
for (int i = 0; i < RUN_TESTS; i++)
{
- available_coins.clear();
+ available_coins.Clear();
// with an empty wallet we can't even pay one cent
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 1 * CENT, CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard), 1 * CENT, CENT));
add_coin(available_coins, *wallet, 1*CENT, CFeeRate(0), 4); // add a new 1 cent coin
// with a new 1 cent coin, we still can't find a mature 1 cent
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 1 * CENT, CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard), 1 * CENT, CENT));
// but we can find a new 1 cent
- const auto result1 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
+ const auto result1 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 1 * CENT, CENT);
BOOST_CHECK(result1);
BOOST_CHECK_EQUAL(result1->GetSelectedValue(), 1 * CENT);
add_coin(available_coins, *wallet, 2*CENT); // add a mature 2 cent coin
// we can't make 3 cents of mature coins
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 3 * CENT, CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard), 3 * CENT, CENT));
// we can make 3 cents of new coins
- const auto result2 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 3 * CENT, CENT);
+ const auto result2 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 3 * CENT, CENT);
BOOST_CHECK(result2);
BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 3 * CENT);
@@ -447,44 +456,44 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
// we can't make 38 cents only if we disallow new coins:
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 38 * CENT, CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard), 38 * CENT, CENT));
// we can't even make 37 cents if we don't allow new coins even if they're from us
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard_extra), 38 * CENT, CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard_extra), 38 * CENT, CENT));
// but we can make 37 cents if we accept new coins from ourself
- const auto result3 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 37 * CENT, CENT);
+ const auto result3 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard), 37 * CENT, CENT);
BOOST_CHECK(result3);
BOOST_CHECK_EQUAL(result3->GetSelectedValue(), 37 * CENT);
// and we can make 38 cents if we accept all new coins
- const auto result4 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 38 * CENT, CENT);
+ const auto result4 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 38 * CENT, CENT);
BOOST_CHECK(result4);
BOOST_CHECK_EQUAL(result4->GetSelectedValue(), 38 * CENT);
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
- const auto result5 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 34 * CENT, CENT);
+ const auto result5 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 34 * CENT, CENT);
BOOST_CHECK(result5);
BOOST_CHECK_EQUAL(result5->GetSelectedValue(), 35 * CENT); // but 35 cents is closest
BOOST_CHECK_EQUAL(result5->GetInputSet().size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
- const auto result6 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 7 * CENT, CENT);
+ const auto result6 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 7 * CENT, CENT);
BOOST_CHECK(result6);
BOOST_CHECK_EQUAL(result6->GetSelectedValue(), 7 * CENT);
BOOST_CHECK_EQUAL(result6->GetInputSet().size(), 2U);
// when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
- const auto result7 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 8 * CENT, CENT);
+ const auto result7 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 8 * CENT, CENT);
BOOST_CHECK(result7);
BOOST_CHECK(result7->GetSelectedValue() == 8 * CENT);
BOOST_CHECK_EQUAL(result7->GetInputSet().size(), 3U);
// when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
- const auto result8 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 9 * CENT, CENT);
+ const auto result8 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 9 * CENT, CENT);
BOOST_CHECK(result8);
BOOST_CHECK_EQUAL(result8->GetSelectedValue(), 10 * CENT);
BOOST_CHECK_EQUAL(result8->GetInputSet().size(), 1U);
// now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin
- available_coins.clear();
+ available_coins.Clear();
add_coin(available_coins, *wallet, 6*CENT);
add_coin(available_coins, *wallet, 7*CENT);
@@ -493,12 +502,12 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(available_coins, *wallet, 30*CENT); // now we have 6+7+8+20+30 = 71 cents total
// check that we have 71 and not 72
- const auto result9 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 71 * CENT, CENT);
+ const auto result9 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 71 * CENT, CENT);
BOOST_CHECK(result9);
- BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 72 * CENT, CENT));
+ BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 72 * CENT, CENT));
// now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20
- const auto result10 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 16 * CENT, CENT);
+ const auto result10 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 16 * CENT, CENT);
BOOST_CHECK(result10);
BOOST_CHECK_EQUAL(result10->GetSelectedValue(), 20 * CENT); // we should get 20 in one coin
BOOST_CHECK_EQUAL(result10->GetInputSet().size(), 1U);
@@ -506,7 +515,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(available_coins, *wallet, 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
// now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20
- const auto result11 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 16 * CENT, CENT);
+ const auto result11 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 16 * CENT, CENT);
BOOST_CHECK(result11);
BOOST_CHECK_EQUAL(result11->GetSelectedValue(), 18 * CENT); // we should get 18 in 3 coins
BOOST_CHECK_EQUAL(result11->GetInputSet().size(), 3U);
@@ -514,13 +523,13 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(available_coins, *wallet, 18*CENT); // now we have 5+6+7+8+18+20+30
// and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18
- const auto result12 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 16 * CENT, CENT);
+ const auto result12 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 16 * CENT, CENT);
BOOST_CHECK(result12);
BOOST_CHECK_EQUAL(result12->GetSelectedValue(), 18 * CENT); // we should get 18 in 1 coin
BOOST_CHECK_EQUAL(result12->GetInputSet().size(), 1U); // because in the event of a tie, the biggest coin wins
// now try making 11 cents. we should get 5+6
- const auto result13 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 11 * CENT, CENT);
+ const auto result13 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 11 * CENT, CENT);
BOOST_CHECK(result13);
BOOST_CHECK_EQUAL(result13->GetSelectedValue(), 11 * CENT);
BOOST_CHECK_EQUAL(result13->GetInputSet().size(), 2U);
@@ -530,19 +539,19 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(available_coins, *wallet, 2*COIN);
add_coin(available_coins, *wallet, 3*COIN);
add_coin(available_coins, *wallet, 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
- const auto result14 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 95 * CENT, CENT);
+ const auto result14 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 95 * CENT, CENT);
BOOST_CHECK(result14);
BOOST_CHECK_EQUAL(result14->GetSelectedValue(), 1 * COIN); // we should get 1 BTC in 1 coin
BOOST_CHECK_EQUAL(result14->GetInputSet().size(), 1U);
- const auto result15 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 195 * CENT, CENT);
+ const auto result15 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 195 * CENT, CENT);
BOOST_CHECK(result15);
BOOST_CHECK_EQUAL(result15->GetSelectedValue(), 2 * COIN); // we should get 2 BTC in 1 coin
BOOST_CHECK_EQUAL(result15->GetInputSet().size(), 1U);
// empty the wallet and start again, now with fractions of a cent, to test small change avoidance
- available_coins.clear();
+ available_coins.Clear();
add_coin(available_coins, *wallet, CENT * 1 / 10);
add_coin(available_coins, *wallet, CENT * 2 / 10);
add_coin(available_coins, *wallet, CENT * 3 / 10);
@@ -551,7 +560,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// try making 1 * CENT from the 1.5 * CENT
// we'll get change smaller than CENT whatever happens, so can expect CENT exactly
- const auto result16 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT, CENT);
+ const auto result16 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), CENT, CENT);
BOOST_CHECK(result16);
BOOST_CHECK_EQUAL(result16->GetSelectedValue(), CENT);
@@ -559,7 +568,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(available_coins, *wallet, 1111*CENT);
// try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
- const auto result17 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
+ const auto result17 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 1 * CENT, CENT);
BOOST_CHECK(result17);
BOOST_CHECK_EQUAL(result17->GetSelectedValue(), 1 * CENT); // we should get the exact amount
@@ -568,17 +577,17 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(available_coins, *wallet, CENT * 7 / 10);
// and try again to make 1.0 * CENT
- const auto result18 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
+ const auto result18 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 1 * CENT, CENT);
BOOST_CHECK(result18);
BOOST_CHECK_EQUAL(result18->GetSelectedValue(), 1 * CENT); // we should get the exact amount
// run the 'mtgox' test (see https://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
// they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
- available_coins.clear();
+ available_coins.Clear();
for (int j = 0; j < 20; j++)
add_coin(available_coins, *wallet, 50000 * COIN);
- const auto result19 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 500000 * COIN, CENT);
+ const auto result19 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 500000 * COIN, CENT);
BOOST_CHECK(result19);
BOOST_CHECK_EQUAL(result19->GetSelectedValue(), 500000 * COIN); // we should get the exact amount
BOOST_CHECK_EQUAL(result19->GetInputSet().size(), 10U); // in ten coins
@@ -587,41 +596,41 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// we need to try finding an exact subset anyway
// sometimes it will fail, and so we use the next biggest coin:
- available_coins.clear();
+ available_coins.Clear();
add_coin(available_coins, *wallet, CENT * 5 / 10);
add_coin(available_coins, *wallet, CENT * 6 / 10);
add_coin(available_coins, *wallet, CENT * 7 / 10);
add_coin(available_coins, *wallet, 1111 * CENT);
- const auto result20 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 1 * CENT, CENT);
+ const auto result20 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 1 * CENT, CENT);
BOOST_CHECK(result20);
BOOST_CHECK_EQUAL(result20->GetSelectedValue(), 1111 * CENT); // we get the bigger coin
BOOST_CHECK_EQUAL(result20->GetInputSet().size(), 1U);
// but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
- available_coins.clear();
+ available_coins.Clear();
add_coin(available_coins, *wallet, CENT * 4 / 10);
add_coin(available_coins, *wallet, CENT * 6 / 10);
add_coin(available_coins, *wallet, CENT * 8 / 10);
add_coin(available_coins, *wallet, 1111 * CENT);
- const auto result21 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT, CENT);
+ const auto result21 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), CENT, CENT);
BOOST_CHECK(result21);
BOOST_CHECK_EQUAL(result21->GetSelectedValue(), CENT); // we should get the exact amount
BOOST_CHECK_EQUAL(result21->GetInputSet().size(), 2U); // in two coins 0.4+0.6
// test avoiding small change
- available_coins.clear();
+ available_coins.Clear();
add_coin(available_coins, *wallet, CENT * 5 / 100);
add_coin(available_coins, *wallet, CENT * 1);
add_coin(available_coins, *wallet, CENT * 100);
// trying to make 100.01 from these three coins
- const auto result22 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT * 10001 / 100, CENT);
+ const auto result22 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), CENT * 10001 / 100, CENT);
BOOST_CHECK(result22);
BOOST_CHECK_EQUAL(result22->GetSelectedValue(), CENT * 10105 / 100); // we should get all coins
BOOST_CHECK_EQUAL(result22->GetInputSet().size(), 3U);
// but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
- const auto result23 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), CENT * 9990 / 100, CENT);
+ const auto result23 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), CENT * 9990 / 100, CENT);
BOOST_CHECK(result23);
BOOST_CHECK_EQUAL(result23->GetSelectedValue(), 101 * CENT);
BOOST_CHECK_EQUAL(result23->GetInputSet().size(), 2U);
@@ -629,14 +638,14 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// test with many inputs
for (CAmount amt=1500; amt < COIN; amt*=10) {
- available_coins.clear();
+ available_coins.Clear();
// Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148 bytes per input)
for (uint16_t j = 0; j < 676; j++)
add_coin(available_coins, *wallet, amt);
// We only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
for (int i = 0; i < RUN_TESTS; i++) {
- const auto result24 = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_confirmed), 2000, CENT);
+ const auto result24 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 2000, CENT);
BOOST_CHECK(result24);
if (amt - 2000 < CENT) {
@@ -655,7 +664,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// test randomness
{
- available_coins.clear();
+ available_coins.Clear();
for (int i2 = 0; i2 < 100; i2++)
add_coin(available_coins, *wallet, COIN);
@@ -663,9 +672,9 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
for (int i = 0; i < RUN_TESTS; i++) {
// picking 50 from 100 coins doesn't depend on the shuffle,
// but does depend on randomness in the stochastic approximation code
- const auto result25 = KnapsackSolver(GroupCoins(available_coins.all()), 50 * COIN, CENT);
+ const auto result25 = KnapsackSolver(GroupCoins(available_coins.All()), 50 * COIN, CENT);
BOOST_CHECK(result25);
- const auto result26 = KnapsackSolver(GroupCoins(available_coins.all()), 50 * COIN, CENT);
+ const auto result26 = KnapsackSolver(GroupCoins(available_coins.All()), 50 * COIN, CENT);
BOOST_CHECK(result26);
BOOST_CHECK(!EqualResult(*result25, *result26));
@@ -676,9 +685,9 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// When choosing 1 from 100 identical coins, 1% of the time, this test will choose the same coin twice
// which will cause it to fail.
// To avoid that issue, run the test RANDOM_REPEATS times and only complain if all of them fail
- const auto result27 = KnapsackSolver(GroupCoins(available_coins.all()), COIN, CENT);
+ const auto result27 = KnapsackSolver(GroupCoins(available_coins.All()), COIN, CENT);
BOOST_CHECK(result27);
- const auto result28 = KnapsackSolver(GroupCoins(available_coins.all()), COIN, CENT);
+ const auto result28 = KnapsackSolver(GroupCoins(available_coins.All()), COIN, CENT);
BOOST_CHECK(result28);
if (EqualResult(*result27, *result28))
fails++;
@@ -699,9 +708,9 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
int fails = 0;
for (int j = 0; j < RANDOM_REPEATS; j++)
{
- const auto result29 = KnapsackSolver(GroupCoins(available_coins.all()), 90 * CENT, CENT);
+ const auto result29 = KnapsackSolver(GroupCoins(available_coins.All()), 90 * CENT, CENT);
BOOST_CHECK(result29);
- const auto result30 = KnapsackSolver(GroupCoins(available_coins.all()), 90 * CENT, CENT);
+ const auto result30 = KnapsackSolver(GroupCoins(available_coins.All()), 90 * CENT, CENT);
BOOST_CHECK(result30);
if (EqualResult(*result29, *result30))
fails++;
@@ -727,7 +736,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
add_coin(available_coins, *wallet, 1000 * COIN);
add_coin(available_coins, *wallet, 3 * COIN);
- const auto result = KnapsackSolver(KnapsackGroupOutputs(available_coins.all(), *wallet, filter_standard), 1003 * COIN, CENT, rand);
+ const auto result = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard), 1003 * COIN, CENT, rand);
BOOST_CHECK(result);
BOOST_CHECK_EQUAL(result->GetSelectedValue(), 1003 * COIN);
BOOST_CHECK_EQUAL(result->GetInputSet().size(), 2U);
@@ -779,8 +788,10 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
/*tx_noinputs_size=*/ 0,
/*avoid_partial=*/ false,
};
+ cs_params.m_cost_of_change = 1;
+ cs_params.min_viable_change = 1;
CCoinControl cc;
- const auto result = SelectCoins(*wallet, available_coins, target, cc, cs_params);
+ const auto result = SelectCoins(*wallet, available_coins, /*pre_set_inputs=*/{}, target, cc, cs_params);
BOOST_CHECK(result);
BOOST_CHECK_GE(result->GetSelectedValue(), target);
}
@@ -919,5 +930,55 @@ BOOST_AUTO_TEST_CASE(effective_value_test)
BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1
}
+BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test)
+{
+ // Test that the effective value is used to check whether preset inputs provide sufficient funds when subtract_fee_outputs is not used.
+ // This test creates a coin whose value is higher than the target but whose effective value is lower than the target.
+ // The coin is selected using coin control, with m_allow_other_inputs = false. SelectCoins should fail due to insufficient funds.
+
+ std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
+ wallet->LoadWallet();
+ LOCK(wallet->cs_wallet);
+ wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
+ wallet->SetupDescriptorScriptPubKeyMans();
+
+ CoinsResult available_coins;
+ {
+ std::unique_ptr<CWallet> dummyWallet = std::make_unique<CWallet>(m_node.chain.get(), "dummy", m_args, CreateMockWalletDatabase());
+ dummyWallet->LoadWallet();
+ LOCK(dummyWallet->cs_wallet);
+ dummyWallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
+ dummyWallet->SetupDescriptorScriptPubKeyMans();
+
+ add_coin(available_coins, *dummyWallet, 100000); // 0.001 BTC
+ }
+
+ CAmount target{99900}; // 0.000999 BTC
+
+ FastRandomContext rand;
+ CoinSelectionParams cs_params{
+ rand,
+ /*change_output_size=*/34,
+ /*change_spend_size=*/148,
+ /*min_change_target=*/1000,
+ /*effective_feerate=*/CFeeRate(3000),
+ /*long_term_feerate=*/CFeeRate(1000),
+ /*discard_feerate=*/CFeeRate(1000),
+ /*tx_noinputs_size=*/0,
+ /*avoid_partial=*/false,
+ };
+ CCoinControl cc;
+ cc.m_allow_other_inputs = false;
+ COutput output = available_coins.All().at(0);
+ cc.SetInputWeight(output.outpoint, 148);
+ cc.SelectExternal(output.outpoint, output.txout);
+
+ const auto preset_inputs = *Assert(FetchSelectedInputs(*wallet, cc, cs_params));
+ available_coins.coins[OutputType::BECH32].erase(available_coins.coins[OutputType::BECH32].begin());
+
+ const auto result = SelectCoins(*wallet, available_coins, preset_inputs, target, cc, cs_params);
+ BOOST_CHECK(!result);
+}
+
BOOST_AUTO_TEST_SUITE_END()
} // namespace wallet
diff --git a/src/wallet/test/feebumper_tests.cpp b/src/wallet/test/feebumper_tests.cpp
new file mode 100644
index 0000000000..2480a9b4e1
--- /dev/null
+++ b/src/wallet/test/feebumper_tests.cpp
@@ -0,0 +1,56 @@
+// Copyright (c) 2022 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or https://www.opensource.org/licenses/mit-license.php.
+
+#include <consensus/validation.h>
+#include <policy/policy.h>
+#include <primitives/transaction.h>
+#include <script/script.h>
+#include <util/strencodings.h>
+#include <wallet/feebumper.h>
+#include <wallet/test/util.h>
+#include <wallet/test/wallet_test_fixture.h>
+
+#include <boost/test/unit_test.hpp>
+
+namespace wallet {
+namespace feebumper {
+BOOST_FIXTURE_TEST_SUITE(feebumper_tests, WalletTestingSetup)
+
+static void CheckMaxWeightComputation(const std::string& script_str, const std::vector<std::string>& witness_str_stack, const std::string& prevout_script_str, int64_t expected_max_weight)
+{
+ std::vector script_data(ParseHex(script_str));
+ CScript script(script_data.begin(), script_data.end());
+ CTxIn input(uint256(), 0, script);
+
+ for (const auto& s : witness_str_stack) {
+ input.scriptWitness.stack.push_back(ParseHex(s));
+ }
+
+ std::vector prevout_script_data(ParseHex(prevout_script_str));
+ CScript prevout_script(prevout_script_data.begin(), prevout_script_data.end());
+
+ int64_t weight = GetTransactionInputWeight(input);
+ SignatureWeights weights;
+ SignatureWeightChecker size_checker(weights, DUMMY_CHECKER);
+ bool script_ok = VerifyScript(input.scriptSig, prevout_script, &input.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, size_checker);
+ BOOST_CHECK(script_ok);
+ weight += weights.GetWeightDiffToMax();
+ BOOST_CHECK_EQUAL(weight, expected_max_weight);
+}
+
+BOOST_AUTO_TEST_CASE(external_max_weight_test)
+{
+ // P2PKH
+ CheckMaxWeightComputation("453042021f03c8957c5ce12940ee6e3333ecc3f633d9a1ac53a55b3ce0351c617fa96abe021f0dccdcce3ef45a63998be9ec748b561baf077b8e862941d0cd5ec08f5afe68012102fccfeb395f0ecd3a77e7bc31c3bc61dc987418b18e395d441057b42ca043f22c", {}, "76a914f60dcfd3392b28adc7662669603641f578eed72d88ac", 593);
+ // P2SH-P2WPKH
+ CheckMaxWeightComputation("160014001dca1b22c599b5a56a87c78417ad2ff39552f1", {"3042021f5443c58eaf45f3e5ef46f8516f966b334a7d497cedda4edb2b9fad57c90c3b021f63a77cb56cde848e2e2dd20b487eec2f53101f634193786083f60b4d23a82301", "026cfe86116f161057deb240201d6b82ebd4f161e0200d63dc9aca65a1d6b38bb7"}, "a9147c8ab5ad7708b97ccb6b483d57aba48ee85214df87", 364);
+ // P2WPKH
+ CheckMaxWeightComputation("", {"3042021f0f8906f0394979d5b737134773e5b88bf036c7d63542301d600ab677ba5a59021f0e9fe07e62c113045fa1c1532e2914720e8854d189c4f5b8c88f57956b704401", "0359edba11ed1a0568094a6296a16c4d5ee4c8cfe2f5e2e6826871b5ecf8188f79"}, "00149961a78658030cc824af4c54fbf5294bec0cabdd", 272);
+ // P2WSH HTLC
+ CheckMaxWeightComputation("", {"3042021f5c4c29e6b686aae5b6d0751e90208592ea96d26bc81d78b0d3871a94a21fa8021f74dc2f971e438ccece8699c8fd15704c41df219ab37b63264f2147d15c34d801", "01", "6321024cf55e52ec8af7866617dc4e7ff8433758e98799906d80e066c6f32033f685f967029000b275210214827893e2dcbe4ad6c20bd743288edad21100404eb7f52ccd6062fd0e7808f268ac"}, "002089e84892873c679b1129edea246e484fd914c2601f776d4f2f4a001eb8059703", 318);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+} // namespace feebumper
+} // namespace wallet
diff --git a/src/wallet/test/fuzz/coinselection.cpp b/src/wallet/test/fuzz/coinselection.cpp
index 3465f2f331..1a0708c43a 100644
--- a/src/wallet/test/fuzz/coinselection.cpp
+++ b/src/wallet/test/fuzz/coinselection.cpp
@@ -30,7 +30,7 @@ static void GroupCoins(FuzzedDataProvider& fuzzed_data_provider, const std::vect
bool valid_outputgroup{false};
for (auto& coin : coins) {
output_group.Insert(coin, /*ancestors=*/0, /*descendants=*/0, positive_only);
- // If positive_only was specified, nothing may have been inserted, leading to an empty outpout group
+ // If positive_only was specified, nothing may have been inserted, leading to an empty output group
// that would be invalid for the BnB algorithm
valid_outputgroup = !positive_only || output_group.GetSelectionAmount() > 0;
if (valid_outputgroup && fuzzed_data_provider.ConsumeBool()) {
@@ -58,6 +58,8 @@ FUZZ_TARGET(coinselection)
coin_params.m_subtract_fee_outputs = subtract_fee_outputs;
coin_params.m_long_term_feerate = long_term_fee_rate;
coin_params.m_effective_feerate = effective_fee_rate;
+ coin_params.change_output_size = fuzzed_data_provider.ConsumeIntegralInRange<int>(10, 1000);
+ coin_params.m_change_fee = effective_fee_rate.GetFee(coin_params.change_output_size);
// Create some coins
CAmount total_balance{0};
@@ -83,11 +85,11 @@ FUZZ_TARGET(coinselection)
const auto result_bnb = SelectCoinsBnB(group_pos, target, cost_of_change);
auto result_srd = SelectCoinsSRD(group_pos, target, fast_random_context);
- if (result_srd) result_srd->ComputeAndSetWaste(cost_of_change);
+ if (result_srd) result_srd->ComputeAndSetWaste(cost_of_change, cost_of_change, 0);
- CAmount change_target{GenerateChangeTarget(target, fast_random_context)};
+ CAmount change_target{GenerateChangeTarget(target, coin_params.m_change_fee, fast_random_context)};
auto result_knapsack = KnapsackSolver(group_all, target, change_target, fast_random_context);
- if (result_knapsack) result_knapsack->ComputeAndSetWaste(cost_of_change);
+ if (result_knapsack) result_knapsack->ComputeAndSetWaste(cost_of_change, cost_of_change, 0);
// If the total balance is sufficient for the target and we are not using
// effective values, Knapsack should always find a solution.
diff --git a/src/wallet/test/fuzz/notifications.cpp b/src/wallet/test/fuzz/notifications.cpp
index 5c173773e4..5e9cd4001b 100644
--- a/src/wallet/test/fuzz/notifications.cpp
+++ b/src/wallet/test/fuzz/notifications.cpp
@@ -69,14 +69,13 @@ struct FuzzedWallet {
CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider)
{
auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)};
- BResult<CTxDestination> op_dest;
+ util::Result<CTxDestination> op_dest{util::Error{}};
if (fuzzed_data_provider.ConsumeBool()) {
op_dest = wallet->GetNewDestination(type, "");
} else {
op_dest = wallet->GetNewChangeDestination(type);
}
- assert(op_dest.HasRes());
- return GetScriptForDestination(op_dest.GetObj());
+ return GetScriptForDestination(*Assert(op_dest));
}
};
diff --git a/src/test/fuzz/parse_iso8601.cpp b/src/wallet/test/fuzz/parse_iso8601.cpp
index 0fef9a9a1d..5be248c2fb 100644
--- a/src/test/fuzz/parse_iso8601.cpp
+++ b/src/wallet/test/fuzz/parse_iso8601.cpp
@@ -5,6 +5,7 @@
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <util/time.h>
+#include <wallet/rpc/util.h>
#include <cassert>
#include <cstdint>
@@ -20,7 +21,7 @@ FUZZ_TARGET(parse_iso8601)
const std::string iso8601_datetime = FormatISO8601DateTime(random_time);
(void)FormatISO8601Date(random_time);
- const int64_t parsed_time_1 = ParseISO8601DateTime(iso8601_datetime);
+ const int64_t parsed_time_1 = wallet::ParseISO8601DateTime(iso8601_datetime);
if (random_time >= 0) {
assert(parsed_time_1 >= 0);
if (iso8601_datetime.length() == 20) {
@@ -28,6 +29,6 @@ FUZZ_TARGET(parse_iso8601)
}
}
- const int64_t parsed_time_2 = ParseISO8601DateTime(random_string);
+ const int64_t parsed_time_2 = wallet::ParseISO8601DateTime(random_string);
assert(parsed_time_2 >= 0);
}
diff --git a/src/wallet/test/ismine_tests.cpp b/src/wallet/test/ismine_tests.cpp
index dd5cd0af46..68146eb079 100644
--- a/src/wallet/test/ismine_tests.cpp
+++ b/src/wallet/test/ismine_tests.cpp
@@ -43,11 +43,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Keystore does not have key
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has key
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1);
}
// P2PK uncompressed
@@ -60,11 +62,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Keystore does not have key
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has key
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1);
}
// P2PKH compressed
@@ -77,11 +81,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Keystore does not have key
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has key
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1);
}
// P2PKH uncompressed
@@ -94,11 +100,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Keystore does not have key
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has key
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1);
}
// P2SH
@@ -113,16 +121,19 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Keystore does not have redeemScript or key
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has redeemScript but no key
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has redeemScript and key
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1);
}
// (P2PKH inside) P2SH inside P2SH (invalid)
@@ -141,6 +152,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
}
// (P2PKH inside) P2SH inside P2WSH (invalid)
@@ -159,6 +171,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
}
// P2WPKH inside P2WSH (invalid)
@@ -175,6 +188,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
}
// (P2PKH inside) P2WSH inside P2WSH (invalid)
@@ -193,6 +207,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
}
// P2WPKH compressed
@@ -208,6 +223,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1);
}
// P2WPKH uncompressed
@@ -222,11 +238,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Keystore has key, but no P2SH redeemScript
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has key and P2SH redeemScript
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
}
// scriptPubKey multisig
@@ -240,24 +258,28 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Keystore does not have any keys
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has 1/2 keys
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has 2/2 keys
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1]));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has 2/2 keys and the script
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
}
// P2SH multisig
@@ -274,11 +296,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Keystore has no redeemScript
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has redeemScript
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1);
}
// P2WSH multisig with compressed keys
@@ -295,16 +319,19 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Keystore has keys, but no witnessScript or P2SH redeemScript
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has keys and witnessScript, but no P2SH redeemScript
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has keys, witnessScript, P2SH redeemScript
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1);
}
// P2WSH multisig with uncompressed key
@@ -321,16 +348,19 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Keystore has keys, but no witnessScript or P2SH redeemScript
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has keys and witnessScript, but no P2SH redeemScript
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has keys, witnessScript, P2SH redeemScript
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
}
// P2WSH multisig wrapped in P2SH
@@ -346,18 +376,21 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Keystore has no witnessScript, P2SH redeemScript, or keys
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has witnessScript and P2SH redeemScript, but no keys
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript));
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
// Keystore has keys, witnessScript, P2SH redeemScript
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1]));
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1);
}
// OP_RETURN
@@ -372,6 +405,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
}
// witness unspendable
@@ -386,6 +420,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
}
// witness unknown
@@ -400,6 +435,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
}
// Nonstandard
@@ -414,6 +450,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0);
}
}
diff --git a/src/wallet/test/rpc_util_tests.cpp b/src/wallet/test/rpc_util_tests.cpp
new file mode 100644
index 0000000000..32f6f5ab46
--- /dev/null
+++ b/src/wallet/test/rpc_util_tests.cpp
@@ -0,0 +1,24 @@
+// Copyright (c) 2022 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 <wallet/rpc/util.h>
+
+#include <boost/test/unit_test.hpp>
+
+namespace wallet {
+
+BOOST_AUTO_TEST_SUITE(wallet_util_tests)
+
+BOOST_AUTO_TEST_CASE(util_ParseISO8601DateTime)
+{
+ BOOST_CHECK_EQUAL(ParseISO8601DateTime("1970-01-01T00:00:00Z"), 0);
+ BOOST_CHECK_EQUAL(ParseISO8601DateTime("1960-01-01T00:00:00Z"), 0);
+ BOOST_CHECK_EQUAL(ParseISO8601DateTime("2000-01-01T00:00:01Z"), 946684801);
+ BOOST_CHECK_EQUAL(ParseISO8601DateTime("2011-09-30T23:36:17Z"), 1317425777);
+ BOOST_CHECK_EQUAL(ParseISO8601DateTime("2100-12-31T23:59:59Z"), 4133980799);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace wallet
diff --git a/src/wallet/test/spend_tests.cpp b/src/wallet/test/spend_tests.cpp
index 53c3b5d2ae..a75b014870 100644
--- a/src/wallet/test/spend_tests.cpp
+++ b/src/wallet/test/spend_tests.cpp
@@ -18,7 +18,7 @@ BOOST_FIXTURE_TEST_SUITE(spend_tests, WalletTestingSetup)
BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
- auto wallet = CreateSyncedWallet(*m_node.chain, m_node.chainman->ActiveChain(), m_args, coinbaseKey);
+ auto wallet = CreateSyncedWallet(*m_node.chain, WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain()), m_args, coinbaseKey);
// Check that a subtract-from-recipient transaction slightly less than the
// coinbase input amount does not create a change output (because it would
@@ -35,7 +35,7 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
coin_control.m_change_type = OutputType::LEGACY;
auto res = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, coin_control);
BOOST_CHECK(res);
- const auto& txr = res.GetObj();
+ const auto& txr = *res;
BOOST_CHECK_EQUAL(txr.tx->vout.size(), 1);
BOOST_CHECK_EQUAL(txr.tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - txr.fee);
BOOST_CHECK_GT(txr.fee, 0);
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 81183aa738..60fdbde71b 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -91,16 +91,17 @@ static void AddKey(CWallet& wallet, const CKey& key)
BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
{
// Cap last block file size, and mine new block in a new block file.
- CBlockIndex* oldTip = m_node.chainman->ActiveChain().Tip();
+ CBlockIndex* oldTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
WITH_LOCK(::cs_main, m_node.chainman->m_blockman.GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE);
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
- CBlockIndex* newTip = m_node.chainman->ActiveChain().Tip();
+ CBlockIndex* newTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
// Verify ScanForWalletTransactions fails to read an unknown start block.
{
CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
+ LOCK(Assert(m_node.chainman)->GetMutex());
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
}
@@ -121,6 +122,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
CWallet wallet(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
{
LOCK(wallet.cs_wallet);
+ LOCK(Assert(m_node.chainman)->GetMutex());
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
}
@@ -165,6 +167,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
+ LOCK(Assert(m_node.chainman)->GetMutex());
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
}
@@ -192,6 +195,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
+ LOCK(Assert(m_node.chainman)->GetMutex());
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
}
@@ -210,10 +214,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
{
// Cap last block file size, and mine new block in a new block file.
- CBlockIndex* oldTip = m_node.chainman->ActiveChain().Tip();
+ CBlockIndex* oldTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
WITH_LOCK(::cs_main, m_node.chainman->m_blockman.GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE);
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
- CBlockIndex* newTip = m_node.chainman->ActiveChain().Tip();
+ CBlockIndex* newTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
// Prune the older block file.
int file_number;
@@ -277,7 +281,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
{
// Create two blocks with same timestamp to verify that importwallet rescan
// will pick up both blocks, not just the first.
- const int64_t BLOCK_TIME = m_node.chainman->ActiveChain().Tip()->GetBlockTimeMax() + 5;
+ const int64_t BLOCK_TIME = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip()->GetBlockTimeMax() + 5);
SetMockTime(BLOCK_TIME);
m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
@@ -302,6 +306,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
AddWallet(context, wallet);
+ LOCK(Assert(m_node.chainman)->GetMutex());
wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
}
JSONRPCRequest request;
@@ -327,6 +332,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
request.params.setArray();
request.params.push_back(backup_file);
AddWallet(context, wallet);
+ LOCK(Assert(m_node.chainman)->GetMutex());
wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
wallet::importwallet().HandleRequest(request);
RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt);
@@ -350,9 +356,10 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
{
CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase());
- CWalletTx wtx{m_coinbase_txns.back(), TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*index=*/0}};
LOCK(wallet.cs_wallet);
+ LOCK(Assert(m_node.chainman)->GetMutex());
+ CWalletTx wtx{m_coinbase_txns.back(), TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*index=*/0}};
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet.SetupDescriptorScriptPubKeyMans();
@@ -520,7 +527,7 @@ public:
ListCoinsTestingSetup()
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
- wallet = CreateSyncedWallet(*m_node.chain, m_node.chainman->ActiveChain(), m_args, coinbaseKey);
+ wallet = CreateSyncedWallet(*m_node.chain, WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain()), m_args, coinbaseKey);
}
~ListCoinsTestingSetup()
@@ -536,7 +543,7 @@ public:
constexpr int RANDOM_CHANGE_POSITION = -1;
auto res = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, dummy);
BOOST_CHECK(res);
- tx = res.GetObj().tx;
+ tx = res->tx;
}
wallet->CommitTransaction(tx, {}, {});
CMutableTransaction blocktx;
@@ -547,6 +554,7 @@ public:
CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
LOCK(wallet->cs_wallet);
+ LOCK(Assert(m_node.chainman)->GetMutex());
wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
auto it = wallet->mapWallet.find(tx->GetHash());
BOOST_CHECK(it != wallet->mapWallet.end());
@@ -591,7 +599,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
// Lock both coins. Confirm number of available coins drops to 0.
{
LOCK(wallet->cs_wallet);
- BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).size(), 2U);
+ BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).Size(), 2U);
}
for (const auto& group : list) {
for (const auto& coin : group.second) {
@@ -601,7 +609,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
}
{
LOCK(wallet->cs_wallet);
- BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).size(), 0U);
+ BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).Size(), 0U);
}
// Confirm ListCoins still returns same result as before, despite coins
// being locked.
@@ -843,21 +851,126 @@ BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
{
auto block_hash = block_tx.GetHash();
- auto prev_hash = m_coinbase_txns[0]->GetHash();
+ auto prev_tx = m_coinbase_txns[0];
LOCK(wallet->cs_wallet);
- BOOST_CHECK(wallet->HasWalletSpend(prev_hash));
+ BOOST_CHECK(wallet->HasWalletSpend(prev_tx));
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 1u);
std::vector<uint256> vHashIn{ block_hash }, vHashOut;
BOOST_CHECK_EQUAL(wallet->ZapSelectTx(vHashIn, vHashOut), DBErrors::LOAD_OK);
- BOOST_CHECK(!wallet->HasWalletSpend(prev_hash));
+ BOOST_CHECK(!wallet->HasWalletSpend(prev_tx));
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 0u);
}
TestUnloadWallet(std::move(wallet));
}
+/** RAII class that provides access to a FailDatabase. Which fails if needed. */
+class FailBatch : public DatabaseBatch
+{
+private:
+ bool m_pass{true};
+ bool ReadKey(CDataStream&& key, CDataStream& value) override { return m_pass; }
+ bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite=true) override { return m_pass; }
+ bool EraseKey(CDataStream&& key) override { return m_pass; }
+ bool HasKey(CDataStream&& key) override { return m_pass; }
+
+public:
+ explicit FailBatch(bool pass) : m_pass(pass) {}
+ void Flush() override {}
+ void Close() override {}
+
+ bool StartCursor() override { return true; }
+ bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) override { return false; }
+ void CloseCursor() override {}
+ bool TxnBegin() override { return false; }
+ bool TxnCommit() override { return false; }
+ bool TxnAbort() override { return false; }
+};
+
+/** A dummy WalletDatabase that does nothing, only fails if needed.**/
+class FailDatabase : public WalletDatabase
+{
+public:
+ bool m_pass{true}; // false when this db should fail
+
+ void Open() override {};
+ void AddRef() override {}
+ void RemoveRef() override {}
+ bool Rewrite(const char* pszSkip=nullptr) override { return true; }
+ bool Backup(const std::string& strDest) const override { return true; }
+ void Close() override {}
+ void Flush() override {}
+ bool PeriodicFlush() override { return true; }
+ void IncrementUpdateCounter() override { ++nUpdateCounter; }
+ void ReloadDbEnv() override {}
+ std::string Filename() override { return "faildb"; }
+ std::string Format() override { return "faildb"; }
+ std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override { return std::make_unique<FailBatch>(m_pass); }
+};
+
+/**
+ * Checks a wallet invalid state where the inputs (prev-txs) of a new arriving transaction are not marked dirty,
+ * while the transaction that spends them exist inside the in-memory wallet tx map (not stored on db due a db write failure).
+ */
+BOOST_FIXTURE_TEST_CASE(wallet_sync_tx_invalid_state_test, TestingSetup)
+{
+ CWallet wallet(m_node.chain.get(), "", m_args, std::make_unique<FailDatabase>());
+ {
+ LOCK(wallet.cs_wallet);
+ wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
+ wallet.SetupDescriptorScriptPubKeyMans();
+ }
+
+ // Add tx to wallet
+ const auto& op_dest = wallet.GetNewDestination(OutputType::BECH32M, "");
+ BOOST_ASSERT(op_dest);
+
+ CMutableTransaction mtx;
+ mtx.vout.push_back({COIN, GetScriptForDestination(*op_dest)});
+ mtx.vin.push_back(CTxIn(g_insecure_rand_ctx.rand256(), 0));
+ const auto& tx_id_to_spend = wallet.AddToWallet(MakeTransactionRef(mtx), TxStateInMempool{})->GetHash();
+
+ {
+ // Cache and verify available balance for the wtx
+ LOCK(wallet.cs_wallet);
+ const CWalletTx* wtx_to_spend = wallet.GetWalletTx(tx_id_to_spend);
+ BOOST_CHECK_EQUAL(CachedTxGetAvailableCredit(wallet, *wtx_to_spend), 1 * COIN);
+ }
+
+ // Now the good case:
+ // 1) Add a transaction that spends the previously created transaction
+ // 2) Verify that the available balance of this new tx and the old one is updated (prev tx is marked dirty)
+
+ mtx.vin.clear();
+ mtx.vin.push_back(CTxIn(tx_id_to_spend, 0));
+ wallet.transactionAddedToMempool(MakeTransactionRef(mtx), 0);
+ const uint256& good_tx_id = mtx.GetHash();
+
+ {
+ // Verify balance update for the new tx and the old one
+ LOCK(wallet.cs_wallet);
+ const CWalletTx* new_wtx = wallet.GetWalletTx(good_tx_id);
+ BOOST_CHECK_EQUAL(CachedTxGetAvailableCredit(wallet, *new_wtx), 1 * COIN);
+
+ // Now the old wtx
+ const CWalletTx* wtx_to_spend = wallet.GetWalletTx(tx_id_to_spend);
+ BOOST_CHECK_EQUAL(CachedTxGetAvailableCredit(wallet, *wtx_to_spend), 0 * COIN);
+ }
+
+ // Now the bad case:
+ // 1) Make db always fail
+ // 2) Try to add a transaction that spends the previously created transaction and
+ // verify that we are not moving forward if the wallet cannot store it
+ static_cast<FailDatabase&>(wallet.GetDatabase()).m_pass = false;
+ mtx.vin.clear();
+ mtx.vin.push_back(CTxIn(good_tx_id, 0));
+ BOOST_CHECK_EXCEPTION(wallet.transactionAddedToMempool(MakeTransactionRef(mtx), 0),
+ std::runtime_error,
+ HasReason("DB error adding transaction to wallet, write failed"));
+}
+
BOOST_AUTO_TEST_SUITE_END()
} // namespace wallet
diff --git a/src/wallet/test/walletload_tests.cpp b/src/wallet/test/walletload_tests.cpp
new file mode 100644
index 0000000000..f45b69a418
--- /dev/null
+++ b/src/wallet/test/walletload_tests.cpp
@@ -0,0 +1,54 @@
+// Copyright (c) 2022 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or https://www.opensource.org/licenses/mit-license.php.
+
+#include <wallet/wallet.h>
+#include <test/util/setup_common.h>
+
+#include <boost/test/unit_test.hpp>
+
+namespace wallet {
+
+BOOST_AUTO_TEST_SUITE(walletload_tests)
+
+class DummyDescriptor final : public Descriptor {
+private:
+ std::string desc;
+public:
+ explicit DummyDescriptor(const std::string& descriptor) : desc(descriptor) {};
+ ~DummyDescriptor() = default;
+
+ std::string ToString() const override { return desc; }
+ std::optional<OutputType> GetOutputType() const override { return OutputType::UNKNOWN; }
+
+ bool IsRange() const override { return false; }
+ bool IsSolvable() const override { return false; }
+ bool IsSingleType() const override { return true; }
+ bool ToPrivateString(const SigningProvider& provider, std::string& out) const override { return false; }
+ bool ToNormalizedString(const SigningProvider& provider, std::string& out, const DescriptorCache* cache = nullptr) const override { return false; }
+ bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, DescriptorCache* write_cache = nullptr) const override { return false; };
+ bool ExpandFromCache(int pos, const DescriptorCache& read_cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override { return false; }
+ void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const override {}
+};
+
+BOOST_FIXTURE_TEST_CASE(wallet_load_unknown_descriptor, TestingSetup)
+{
+ std::unique_ptr<WalletDatabase> database = CreateMockWalletDatabase();
+ {
+ // Write unknown active descriptor
+ WalletBatch batch(*database, false);
+ std::string unknown_desc = "trx(tpubD6NzVbkrYhZ4Y4S7m6Y5s9GD8FqEMBy56AGphZXuagajudVZEnYyBahZMgHNCTJc2at82YX6s8JiL1Lohu5A3v1Ur76qguNH4QVQ7qYrBQx/86'/1'/0'/0/*)#8pn8tzdt";
+ WalletDescriptor wallet_descriptor(std::make_shared<DummyDescriptor>(unknown_desc), 0, 0, 0, 0);
+ BOOST_CHECK(batch.WriteDescriptor(uint256(), wallet_descriptor));
+ BOOST_CHECK(batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(OutputType::UNKNOWN), uint256(), false));
+ }
+
+ {
+ // Now try to load the wallet and verify the error.
+ const std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", m_args, std::move(database)));
+ BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::UNKNOWN_DESCRIPTOR);
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+} // namespace wallet
diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h
index 271d698e56..27983e356d 100644
--- a/src/wallet/transaction.h
+++ b/src/wallet/transaction.h
@@ -305,6 +305,13 @@ public:
CWalletTx(CWalletTx const &) = delete;
void operator=(CWalletTx const &x) = delete;
};
+
+struct WalletTxOrderComparator {
+ bool operator()(const CWalletTx* a, const CWalletTx* b) const
+ {
+ return a->nOrderPos < b->nOrderPos;
+ }
+};
} // namespace wallet
#endif // BITCOIN_WALLET_TRANSACTION_H
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 54a3221e2d..431e970edc 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -5,6 +5,7 @@
#include <wallet/wallet.h>
+#include <blockfilter.h>
#include <chain.h>
#include <consensus/amount.h>
#include <consensus/consensus.h>
@@ -21,6 +22,7 @@
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <psbt.h>
+#include <random.h>
#include <script/descriptor.h>
#include <script/script.h>
#include <script/signingprovider.h>
@@ -124,7 +126,7 @@ bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet
interfaces::Chain& chain = wallet->chain();
std::string name = wallet->GetName();
- // Unregister with the validation interface which also drops shared ponters.
+ // Unregister with the validation interface which also drops shared pointers.
wallet->m_chain_notifications_handler.reset();
LOCK(context.wallets_mutex);
std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(context.wallets.begin(), context.wallets.end(), wallet);
@@ -149,6 +151,13 @@ std::vector<std::shared_ptr<CWallet>> GetWallets(WalletContext& context)
return context.wallets;
}
+std::shared_ptr<CWallet> GetDefaultWallet(WalletContext& context, size_t& count)
+{
+ LOCK(context.wallets_mutex);
+ count = context.wallets.size();
+ return count == 1 ? context.wallets[0] : nullptr;
+}
+
std::shared_ptr<CWallet> GetWallet(WalletContext& context, const std::string& name)
{
LOCK(context.wallets_mutex);
@@ -253,6 +262,64 @@ std::shared_ptr<CWallet> LoadWalletInternal(WalletContext& context, const std::s
return nullptr;
}
}
+
+class FastWalletRescanFilter
+{
+public:
+ FastWalletRescanFilter(const CWallet& wallet) : m_wallet(wallet)
+ {
+ // fast rescanning via block filters is only supported by descriptor wallets right now
+ assert(!m_wallet.IsLegacy());
+
+ // create initial filter with scripts from all ScriptPubKeyMans
+ for (auto spkm : m_wallet.GetAllScriptPubKeyMans()) {
+ auto desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(spkm)};
+ assert(desc_spkm != nullptr);
+ AddScriptPubKeys(desc_spkm);
+ // save each range descriptor's end for possible future filter updates
+ if (desc_spkm->IsHDEnabled()) {
+ m_last_range_ends.emplace(desc_spkm->GetID(), desc_spkm->GetEndRange());
+ }
+ }
+ }
+
+ void UpdateIfNeeded()
+ {
+ // repopulate filter with new scripts if top-up has happened since last iteration
+ for (const auto& [desc_spkm_id, last_range_end] : m_last_range_ends) {
+ auto desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(m_wallet.GetScriptPubKeyMan(desc_spkm_id))};
+ assert(desc_spkm != nullptr);
+ int32_t current_range_end{desc_spkm->GetEndRange()};
+ if (current_range_end > last_range_end) {
+ AddScriptPubKeys(desc_spkm, last_range_end);
+ m_last_range_ends.at(desc_spkm->GetID()) = current_range_end;
+ }
+ }
+ }
+
+ std::optional<bool> MatchesBlock(const uint256& block_hash) const
+ {
+ return m_wallet.chain().blockFilterMatchesAny(BlockFilterType::BASIC, block_hash, m_filter_set);
+ }
+
+private:
+ const CWallet& m_wallet;
+ /** Map for keeping track of each range descriptor's last seen end range.
+ * This information is used to detect whether new addresses were derived
+ * (that is, if the current end range is larger than the saved end range)
+ * after processing a block and hence a filter set update is needed to
+ * take possible keypool top-ups into account.
+ */
+ std::map<uint256, int32_t> m_last_range_ends;
+ GCSFilter::ElementSet m_filter_set;
+
+ void AddScriptPubKeys(const DescriptorScriptPubKeyMan* desc_spkm, int32_t last_range_end = 0)
+ {
+ for (const auto& script_pub_key : desc_spkm->GetScriptPubKeys(last_range_end)) {
+ m_filter_set.emplace(script_pub_key.begin(), script_pub_key.end());
+ }
+ }
+};
} // namespace
std::shared_ptr<CWallet> LoadWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
@@ -379,25 +446,31 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& b
ReadDatabaseArgs(*context.args, options);
options.require_existing = true;
- if (!fs::exists(backup_file)) {
- error = Untranslated("Backup file does not exist");
- status = DatabaseStatus::FAILED_INVALID_BACKUP_FILE;
- return nullptr;
- }
-
const fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), fs::u8path(wallet_name));
+ auto wallet_file = wallet_path / "wallet.dat";
+ std::shared_ptr<CWallet> wallet;
- if (fs::exists(wallet_path) || !TryCreateDirectories(wallet_path)) {
- error = Untranslated(strprintf("Failed to create database path '%s'. Database already exists.", fs::PathToString(wallet_path)));
- status = DatabaseStatus::FAILED_ALREADY_EXISTS;
- return nullptr;
- }
+ try {
+ if (!fs::exists(backup_file)) {
+ error = Untranslated("Backup file does not exist");
+ status = DatabaseStatus::FAILED_INVALID_BACKUP_FILE;
+ return nullptr;
+ }
- auto wallet_file = wallet_path / "wallet.dat";
- fs::copy_file(backup_file, wallet_file, fs::copy_options::none);
+ if (fs::exists(wallet_path) || !TryCreateDirectories(wallet_path)) {
+ error = Untranslated(strprintf("Failed to create database path '%s'. Database already exists.", fs::PathToString(wallet_path)));
+ status = DatabaseStatus::FAILED_ALREADY_EXISTS;
+ return nullptr;
+ }
- auto wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
+ fs::copy_file(backup_file, wallet_file, fs::copy_options::none);
+ wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
+ } catch (const std::exception& e) {
+ assert(!wallet);
+ if (!error.empty()) error += Untranslated("\n");
+ error += strprintf(Untranslated("Unexpected exception: %s"), e.what());
+ }
if (!wallet) {
fs::remove(wallet_file);
fs::remove(wallet_path);
@@ -414,7 +487,7 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& b
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
{
AssertLockHeld(cs_wallet);
- std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(hash);
+ const auto it = mapWallet.find(hash);
if (it == mapWallet.end())
return nullptr;
return &(it->second);
@@ -535,6 +608,7 @@ void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in)
LOCK(cs_wallet);
if (nWalletVersion >= nVersion)
return;
+ WalletLogPrintf("Setting minversion to %d\n", nVersion);
nWalletVersion = nVersion;
{
@@ -551,7 +625,7 @@ std::set<uint256> CWallet::GetConflicts(const uint256& txid) const
std::set<uint256> result;
AssertLockHeld(cs_wallet);
- std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(txid);
+ const auto it = mapWallet.find(txid);
if (it == mapWallet.end())
return result;
const CWalletTx& wtx = it->second;
@@ -569,11 +643,17 @@ std::set<uint256> CWallet::GetConflicts(const uint256& txid) const
return result;
}
-bool CWallet::HasWalletSpend(const uint256& txid) const
+bool CWallet::HasWalletSpend(const CTransactionRef& tx) const
{
AssertLockHeld(cs_wallet);
- auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0));
- return (iter != mapTxSpends.end() && iter->first.hash == txid);
+ const uint256& txid = tx->GetHash();
+ for (unsigned int i = 0; i < tx->vout.size(); ++i) {
+ auto iter = mapTxSpends.find(COutPoint(txid, i));
+ if (iter != mapTxSpends.end()) {
+ return true;
+ }
+ }
+ return false;
}
void CWallet::Flush()
@@ -636,7 +716,7 @@ bool CWallet::IsSpent(const COutPoint& outpoint) const
for (TxSpends::const_iterator it = range.first; it != range.second; ++it) {
const uint256& wtxid = it->second;
- std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
+ const auto mit = mapWallet.find(wtxid);
if (mit != mapWallet.end()) {
int depth = GetTxDepthInMainChain(mit->second);
if (depth > 0 || (depth == 0 && !mit->second.isAbandoned()))
@@ -868,7 +948,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
wtx.mapValue["replaced_by_txid"] = newHash.ToString();
- // Refresh mempool status without waiting for transactionRemovedFromMempool
+ // Refresh mempool status without waiting for transactionRemovedFromMempool or transactionAddedToMempool
RefreshMempoolStatus(wtx, chain());
WalletBatch batch(GetDatabase());
@@ -1130,7 +1210,13 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const SyncTxS
// Block disconnection override an abandoned tx as unconfirmed
// which means user may have to call abandontransaction again
TxState tx_state = std::visit([](auto&& s) -> TxState { return s; }, state);
- return AddToWallet(MakeTransactionRef(tx), tx_state, /*update_wtx=*/nullptr, /*fFlushOnClose=*/false, rescanning_old_block);
+ CWalletTx* wtx = AddToWallet(MakeTransactionRef(tx), tx_state, /*update_wtx=*/nullptr, /*fFlushOnClose=*/false, rescanning_old_block);
+ if (!wtx) {
+ // Can only be nullptr if there was a db write error (missing db, read-only db or a db engine internal writing error).
+ // As we only store arriving transaction in this process, and we don't want an inconsistent state, let's throw an error.
+ throw std::runtime_error("DB error adding transaction to wallet, write failed");
+ }
+ return true;
}
}
return false;
@@ -1191,12 +1277,13 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
batch.WriteTx(wtx);
NotifyTransactionChanged(wtx.GetHash(), CT_UPDATED);
// Iterate over all its outputs, and mark transactions in the wallet that spend them abandoned too
- TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0));
- while (iter != mapTxSpends.end() && iter->first.hash == now) {
- if (!done.count(iter->second)) {
- todo.insert(iter->second);
+ for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
+ std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(COutPoint(now, i));
+ for (TxSpends::const_iterator iter = range.first; iter != range.second; ++iter) {
+ if (!done.count(iter->second)) {
+ todo.insert(iter->second);
+ }
}
- iter++;
}
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be recomputed
@@ -1242,12 +1329,13 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c
wtx.MarkDirty();
batch.WriteTx(wtx);
// Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too
- TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0));
- while (iter != mapTxSpends.end() && iter->first.hash == now) {
- if (!done.count(iter->second)) {
- todo.insert(iter->second);
- }
- iter++;
+ for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
+ std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(COutPoint(now, i));
+ for (TxSpends::const_iterator iter = range.first; iter != range.second; ++iter) {
+ if (!done.count(iter->second)) {
+ todo.insert(iter->second);
+ }
+ }
}
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be recomputed
@@ -1364,7 +1452,7 @@ CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const
{
{
LOCK(cs_wallet);
- std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
+ const auto mi = mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end())
{
const CWalletTx& prev = (*mi).second;
@@ -1407,6 +1495,19 @@ bool CWallet::IsMine(const CTransaction& tx) const
return false;
}
+isminetype CWallet::IsMine(const COutPoint& outpoint) const
+{
+ AssertLockHeld(cs_wallet);
+ auto wtx = GetWalletTx(outpoint.hash);
+ if (!wtx) {
+ return ISMINE_NO;
+ }
+ if (outpoint.n >= wtx->tx->vout.size()) {
+ return ISMINE_NO;
+ }
+ return IsMine(wtx->tx->vout[outpoint.n]);
+}
+
bool CWallet::IsFromMe(const CTransaction& tx) const
{
return (GetDebit(tx, ISMINE_ALL) > 0);
@@ -1492,16 +1593,20 @@ bool CWallet::LoadWalletFlags(uint64_t flags)
return true;
}
-bool CWallet::AddWalletFlags(uint64_t flags)
+void CWallet::InitWalletFlags(uint64_t flags)
{
LOCK(cs_wallet);
+
// We should never be writing unknown non-tolerable wallet flags
assert(((flags & KNOWN_WALLET_FLAGS) >> 32) == (flags >> 32));
+ // This should only be used once, when creating a new wallet - so current flags are expected to be blank
+ assert(m_wallet_flags == 0);
+
if (!WalletBatch(GetDatabase()).WriteWalletFlags(flags)) {
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
}
- return LoadWalletFlags(flags);
+ if (!LoadWalletFlags(flags)) assert(false);
}
// Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
@@ -1709,7 +1814,11 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
uint256 block_hash = start_block;
ScanResult result;
- WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString());
+ std::unique_ptr<FastWalletRescanFilter> fast_rescan_filter;
+ if (!IsLegacy() && chain().hasBlockFilterIndex(BlockFilterType::BASIC)) fast_rescan_filter = std::make_unique<FastWalletRescanFilter>(*this);
+
+ WalletLogPrintf("Rescan started from block %s... (%s)\n", start_block.ToString(),
+ fast_rescan_filter ? "fast variant using block filters" : "slow variant inspecting all blocks");
fAbortRescan = false;
ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if rescan required on startup (e.g. due to corruption)
@@ -1736,9 +1845,22 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current);
}
- // Read block data
- CBlock block;
- chain().findBlock(block_hash, FoundBlock().data(block));
+ bool fetch_block{true};
+ if (fast_rescan_filter) {
+ fast_rescan_filter->UpdateIfNeeded();
+ auto matches_block{fast_rescan_filter->MatchesBlock(block_hash)};
+ if (matches_block.has_value()) {
+ if (*matches_block) {
+ LogPrint(BCLog::SCAN, "Fast rescan: inspect block %d [%s] (filter matched)\n", block_height, block_hash.ToString());
+ } else {
+ result.last_scanned_block = block_hash;
+ result.last_scanned_height = block_height;
+ fetch_block = false;
+ }
+ } else {
+ LogPrint(BCLog::SCAN, "Fast rescan: inspect block %d [%s] (WARNING: block filter not found!)\n", block_height, block_hash.ToString());
+ }
+ }
// Find next block separately from reading data above, because reading
// is slow and there might be a reorg while it is read.
@@ -1747,35 +1869,41 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
uint256 next_block_hash;
chain().findBlock(block_hash, FoundBlock().inActiveChain(block_still_active).nextBlock(FoundBlock().inActiveChain(next_block).hash(next_block_hash)));
- if (!block.IsNull()) {
- LOCK(cs_wallet);
- if (!block_still_active) {
- // Abort scan if current block is no longer active, to prevent
- // marking transactions as coming from the wrong block.
- result.last_failed_block = block_hash;
- result.status = ScanResult::FAILURE;
- break;
- }
- for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
- SyncTransaction(block.vtx[posInBlock], TxStateConfirmed{block_hash, block_height, static_cast<int>(posInBlock)}, fUpdate, /*rescanning_old_block=*/true);
- }
- // scan succeeded, record block as most recent successfully scanned
- result.last_scanned_block = block_hash;
- result.last_scanned_height = block_height;
+ if (fetch_block) {
+ // Read block data
+ CBlock block;
+ chain().findBlock(block_hash, FoundBlock().data(block));
+
+ if (!block.IsNull()) {
+ LOCK(cs_wallet);
+ if (!block_still_active) {
+ // Abort scan if current block is no longer active, to prevent
+ // marking transactions as coming from the wrong block.
+ result.last_failed_block = block_hash;
+ result.status = ScanResult::FAILURE;
+ break;
+ }
+ for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
+ SyncTransaction(block.vtx[posInBlock], TxStateConfirmed{block_hash, block_height, static_cast<int>(posInBlock)}, fUpdate, /*rescanning_old_block=*/true);
+ }
+ // scan succeeded, record block as most recent successfully scanned
+ result.last_scanned_block = block_hash;
+ result.last_scanned_height = block_height;
- if (save_progress && next_interval) {
- CBlockLocator loc = m_chain->getActiveChainLocator(block_hash);
+ if (save_progress && next_interval) {
+ CBlockLocator loc = m_chain->getActiveChainLocator(block_hash);
- if (!loc.IsNull()) {
- WalletLogPrintf("Saving scan progress %d.\n", block_height);
- WalletBatch batch(GetDatabase());
- batch.WriteBestBlock(loc);
+ if (!loc.IsNull()) {
+ WalletLogPrintf("Saving scan progress %d.\n", block_height);
+ WalletBatch batch(GetDatabase());
+ batch.WriteBestBlock(loc);
+ }
}
+ } else {
+ // could not scan block, keep scanning but record this block as the most recent failure
+ result.last_failed_block = block_hash;
+ result.status = ScanResult::FAILURE;
}
- } else {
- // could not scan block, keep scanning but record this block as the most recent failure
- result.last_failed_block = block_hash;
- result.status = ScanResult::FAILURE;
}
if (max_height && block_height >= *max_height) {
break;
@@ -1818,34 +1946,6 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
return result;
}
-void CWallet::ReacceptWalletTransactions()
-{
- // If transactions aren't being broadcasted, don't let them into local mempool either
- if (!fBroadcastTransactions)
- return;
- 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) {
- const uint256& wtxid = item.first;
- CWalletTx& wtx = item.second;
- assert(wtx.GetHash() == wtxid);
-
- int nDepth = GetTxDepthInMainChain(wtx);
-
- if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) {
- mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
- }
- }
-
- // Try to add wallet transactions to memory pool
- for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) {
- CWalletTx& wtx = *(item.second);
- std::string unused_err_string;
- SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, false);
- }
-}
-
bool CWallet::SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const
{
AssertLockHeld(cs_wallet);
@@ -1886,43 +1986,76 @@ std::set<uint256> CWallet::GetTxConflicts(const CWalletTx& wtx) const
return result;
}
-// Rebroadcast transactions from the wallet. We do this on a random timer
-// to slightly obfuscate which transactions come from our wallet.
+bool CWallet::ShouldResend() const
+{
+ // Don't attempt to resubmit if the wallet is configured to not broadcast
+ if (!fBroadcastTransactions) return false;
+
+ // During reindex, importing and IBD, old wallet transactions become
+ // unconfirmed. Don't resend them as that would spam other nodes.
+ // We only allow forcing mempool submission when not relaying to avoid this spam.
+ if (!chain().isReadyToBroadcast()) return false;
+
+ // Do this infrequently and randomly to avoid giving away
+ // that these are our transactions.
+ if (NodeClock::now() < m_next_resend) return false;
+
+ return true;
+}
+
+NodeClock::time_point CWallet::GetDefaultNextResend() { return FastRandomContext{}.rand_uniform_delay(NodeClock::now() + 12h, 24h); }
+
+// Resubmit transactions from the wallet to the mempool, optionally asking the
+// mempool to relay them. On startup, we will do this for all unconfirmed
+// transactions but will not ask the mempool to relay them. We do this on startup
+// to ensure that our own mempool is aware of our transactions. There
+// is a privacy side effect here as not broadcasting on startup also means that we won't
+// inform the world of our wallet's state, particularly if the wallet (or node) is not
+// yet synced.
+//
+// Otherwise this function is called periodically in order to relay our unconfirmed txs.
+// We do this on a random timer to slightly obfuscate which transactions
+// come from our wallet.
//
-// Ideally, we'd only resend transactions that we think should have been
+// TODO: Ideally, we'd only resend transactions that we think should have been
// mined in the most recent block. Any transaction that wasn't in the top
// blockweight of transactions in the mempool shouldn't have been mined,
// and so is probably just sitting in the mempool waiting to be confirmed.
// Rebroadcasting does nothing to speed up confirmation and only damages
// privacy.
-void CWallet::ResendWalletTransactions()
+//
+// The `force` option results in all unconfirmed transactions being submitted to
+// the mempool. This does not necessarily result in those transactions being relayed,
+// that depends on the `relay` option. Periodic rebroadcast uses the pattern
+// relay=true force=false, while loading into the mempool
+// (on start, or after import) uses relay=false force=true.
+void CWallet::ResubmitWalletTransactions(bool relay, bool force)
{
- // During reindex, importing and IBD, old wallet transactions become
- // unconfirmed. Don't resend them as that would spam other nodes.
- if (!chain().isReadyToBroadcast()) return;
-
- // Do this infrequently and randomly to avoid giving away
- // that these are our transactions.
- if (GetTime() < nNextResend || !fBroadcastTransactions) return;
- bool fFirst = (nNextResend == 0);
- // resend 12-36 hours from now, ~1 day on average.
- nNextResend = GetTime() + (12 * 60 * 60) + GetRand(24 * 60 * 60);
- if (fFirst) return;
+ // Don't attempt to resubmit if the wallet is configured to not broadcast,
+ // even if forcing.
+ if (!fBroadcastTransactions) return;
int submitted_tx_count = 0;
{ // cs_wallet scope
LOCK(cs_wallet);
- // Relay transactions
- for (std::pair<const uint256, CWalletTx>& item : mapWallet) {
- CWalletTx& wtx = item.second;
+ // First filter for the transactions we want to rebroadcast.
+ // We use a set with WalletTxOrderComparator so that rebroadcasting occurs in insertion order
+ std::set<CWalletTx*, WalletTxOrderComparator> to_submit;
+ for (auto& [txid, wtx] : mapWallet) {
+ // Only rebroadcast unconfirmed txs
+ if (!wtx.isUnconfirmed()) continue;
+
// Attempt to rebroadcast all txes more than 5 minutes older than
- // the last block. SubmitTxMemoryPoolAndRelay() will not rebroadcast
- // any confirmed or conflicting txs.
- if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
+ // the last block, or all txs if forcing.
+ if (!force && wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
+ to_submit.insert(&wtx);
+ }
+ // Now try submitting the transactions to the memory pool and (optionally) relay them.
+ for (auto wtx : to_submit) {
std::string unused_err_string;
- if (SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, true)) ++submitted_tx_count;
+ if (SubmitTxMemoryPoolAndRelay(*wtx, unused_err_string, relay)) ++submitted_tx_count;
}
} // cs_wallet
@@ -1936,7 +2069,9 @@ void CWallet::ResendWalletTransactions()
void MaybeResendWalletTxs(WalletContext& context)
{
for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
- pwallet->ResendWalletTransactions();
+ if (!pwallet->ShouldResend()) continue;
+ pwallet->ResubmitWalletTransactions(/*relay=*/true, /*force=*/false);
+ pwallet->SetNextResend();
}
}
@@ -1953,7 +2088,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const
// Build coins map
std::map<COutPoint, Coin> coins;
for (auto& input : tx.vin) {
- std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(input.prevout.hash);
+ const auto mi = mapWallet.find(input.prevout.hash);
if(mi == mapWallet.end() || input.prevout.n >= mi->second.tx->vout.size()) {
return false;
}
@@ -2067,6 +2202,7 @@ SigningResult CWallet::SignMessage(const std::string& message, const PKHash& pkh
CScript script_pub_key = GetScriptForDestination(pkhash);
for (const auto& spk_man_pair : m_spk_managers) {
if (spk_man_pair.second->CanProvide(script_pub_key, sigdata)) {
+ LOCK(cs_wallet); // DescriptorScriptPubKeyMan calls IsLocked which can lock cs_wallet in a deadlocking order
return spk_man_pair.second->SignMessage(message, pkhash, str_sig);
}
}
@@ -2324,36 +2460,31 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
return res;
}
-BResult<CTxDestination> CWallet::GetNewDestination(const OutputType type, const std::string label)
+util::Result<CTxDestination> CWallet::GetNewDestination(const OutputType type, const std::string label)
{
LOCK(cs_wallet);
auto spk_man = GetScriptPubKeyMan(type, false /* internal */);
if (!spk_man) {
- return strprintf(_("Error: No %s addresses available."), FormatOutputType(type));
+ return util::Error{strprintf(_("Error: No %s addresses available."), FormatOutputType(type))};
}
- spk_man->TopUp();
auto op_dest = spk_man->GetNewDestination(type);
if (op_dest) {
- SetAddressBook(op_dest.GetObj(), label, "receive");
+ SetAddressBook(*op_dest, label, "receive");
}
return op_dest;
}
-BResult<CTxDestination> CWallet::GetNewChangeDestination(const OutputType type)
+util::Result<CTxDestination> CWallet::GetNewChangeDestination(const OutputType type)
{
LOCK(cs_wallet);
- CTxDestination dest;
- bilingual_str error;
ReserveDestination reservedest(this, type);
- if (!reservedest.GetReservedDestination(dest, true, error)) {
- return error;
- }
+ auto op_dest = reservedest.GetReservedDestination(true);
+ if (op_dest) reservedest.KeepDestination();
- reservedest.KeepDestination();
- return dest;
+ return op_dest;
}
std::optional<int64_t> CWallet::GetOldestKeyPoolTime() const
@@ -2423,27 +2554,21 @@ std::set<std::string> CWallet::ListAddrBookLabels(const std::string& purpose) co
return label_set;
}
-bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal, bilingual_str& error)
+util::Result<CTxDestination> ReserveDestination::GetReservedDestination(bool internal)
{
m_spk_man = pwallet->GetScriptPubKeyMan(type, internal);
if (!m_spk_man) {
- error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type));
- return false;
+ return util::Error{strprintf(_("Error: No %s addresses available."), FormatOutputType(type))};
}
-
- if (nIndex == -1)
- {
- m_spk_man->TopUp();
-
+ if (nIndex == -1) {
CKeyPool keypool;
- if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool, error)) {
- return false;
- }
+ auto op_address = m_spk_man->GetReservedDestination(type, internal, nIndex, keypool);
+ if (!op_address) return op_address;
+ address = *op_address;
fInternal = keypool.fInternal;
}
- dest = address;
- return true;
+ return address;
}
void ReserveDestination::KeepDestination()
@@ -2756,7 +2881,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
ArgsManager& args = *Assert(context.args);
const std::string& walletFile = database->Filename();
- int64_t nStart = GetTimeMillis();
+ const auto start{SteadyClock::now()};
// TODO: Can't use std::make_shared because we need a custom deleter but
// should be possible to use std::allocate_shared.
const std::shared_ptr<CWallet> walletInstance(new CWallet(chain, name, args, std::move(database)), ReleaseWallet);
@@ -2789,8 +2914,12 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
warnings.push_back(strprintf(_("Error reading %s! Transaction data may be missing or incorrect."
" Rescanning wallet."), walletFile));
rescan_required = true;
- }
- else {
+ } else if (nLoadWalletRet == DBErrors::UNKNOWN_DESCRIPTOR) {
+ error = strprintf(_("Unrecognized descriptor found. Loading wallet %s\n\n"
+ "The wallet might had been created on a newer version.\n"
+ "Please try running the latest software version.\n"), walletFile);
+ return nullptr;
+ } else {
error = strprintf(_("Error loading %s"), walletFile);
return nullptr;
}
@@ -2805,7 +2934,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
// ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key
walletInstance->SetMinVersion(FEATURE_LATEST);
- walletInstance->AddWalletFlags(wallet_creation_flags);
+ walletInstance->InitWalletFlags(wallet_creation_flags);
// Only create LegacyScriptPubKeyMan when not descriptor wallet
if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
@@ -2973,7 +3102,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
walletInstance->m_spend_zero_conf_change = args.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
walletInstance->m_signal_rbf = args.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF);
- walletInstance->WalletLogPrintf("Wallet completed loading in %15dms\n", GetTimeMillis() - nStart);
+ walletInstance->WalletLogPrintf("Wallet completed loading in %15dms\n", Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
// Try to top up keypool. No-op if the wallet is locked.
walletInstance->TopUpKeyPool();
@@ -3070,12 +3199,14 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
// If a block is pruned after this check, we will load the wallet,
// but fail the rescan with a generic error.
- error = chain.hasAssumedValidChain() ?
- _(
- "Assumed-valid: last wallet synchronisation goes beyond "
- "available block data. You need to wait for the background "
- "validation chain to download more blocks.") :
- _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)");
+ error = chain.havePruned() ?
+ _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)") :
+ strprintf(_(
+ "Error loading wallet. Wallet requires blocks to be downloaded, "
+ "and software does not currently support loading wallets while "
+ "blocks are being downloaded out of order when using assumeutxo "
+ "snapshots. Wallet should be able to load successfully after "
+ "node sync reaches height %s"), block_height);
return false;
}
}
@@ -3155,14 +3286,12 @@ bool CWallet::UpgradeWallet(int version, bilingual_str& error)
void CWallet::postInitProcess()
{
- LOCK(cs_wallet);
-
// Add wallet transactions that aren't already in a block to mempool
// Do this here as mempool requires genesis block to be loaded
- ReacceptWalletTransactions();
+ ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
// Update wallet transactions with current mempool transactions.
- chain().requestMempoolTransactions(*this);
+ WITH_LOCK(cs_wallet, chain().requestMempoolTransactions(*this));
}
bool CWallet::BackupWallet(const std::string& strDest) const
@@ -3329,6 +3458,18 @@ std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript& scri
return nullptr;
}
+std::vector<WalletDescriptor> CWallet::GetWalletDescriptors(const CScript& script) const
+{
+ std::vector<WalletDescriptor> descs;
+ for (const auto spk_man: GetScriptPubKeyMans(script)) {
+ if (const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man)) {
+ LOCK(desc_spk_man->cs_desc_man);
+ descs.push_back(desc_spk_man->GetWalletDescriptor());
+ }
+ }
+ return descs;
+}
+
LegacyScriptPubKeyMan* CWallet::GetLegacyScriptPubKeyMan() const
{
if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
@@ -3390,6 +3531,29 @@ void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc)
}
}
+void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key)
+{
+ AssertLockHeld(cs_wallet);
+
+ for (bool internal : {false, true}) {
+ for (OutputType t : OUTPUT_TYPES) {
+ auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this));
+ if (IsCrypted()) {
+ if (IsLocked()) {
+ throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
+ }
+ if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) {
+ throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
+ }
+ }
+ spk_manager->SetupDescriptorGeneration(master_key, t, internal);
+ uint256 id = spk_manager->GetID();
+ m_spk_managers[id] = std::move(spk_manager);
+ AddActiveScriptPubKeyMan(id, t, internal);
+ }
+ }
+}
+
void CWallet::SetupDescriptorScriptPubKeyMans()
{
AssertLockHeld(cs_wallet);
@@ -3405,23 +3569,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
CExtKey master_key;
master_key.SetSeed(seed_key);
- for (bool internal : {false, true}) {
- for (OutputType t : OUTPUT_TYPES) {
- auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this));
- if (IsCrypted()) {
- if (IsLocked()) {
- throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
- }
- if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) {
- throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
- }
- }
- spk_manager->SetupDescriptorGeneration(master_key, t, internal);
- uint256 id = spk_manager->GetID();
- m_spk_managers[id] = std::move(spk_manager);
- AddActiveScriptPubKeyMan(id, t, internal);
- }
- }
+ SetupDescriptorScriptPubKeyMans(master_key);
} else {
ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
@@ -3434,7 +3582,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
const UniValue& descriptor_vals = find_value(signer_res, internal ? "internal" : "receive");
if (!descriptor_vals.isArray()) throw std::runtime_error(std::string(__func__) + ": Unexpected result");
for (const UniValue& desc_val : descriptor_vals.get_array().getValues()) {
- std::string desc_str = desc_val.getValStr();
+ const std::string& desc_str = desc_val.getValStr();
FlatSigningProvider keys;
std::string desc_error;
std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, desc_error, false);
@@ -3470,7 +3618,7 @@ void CWallet::LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool intern
// Legacy wallets have only one ScriptPubKeyManager and it's active for all output and change types.
Assert(IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
- WalletLogPrintf("Setting spkMan to active: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
+ WalletLogPrintf("Setting spkMan to active: id = %s, type = %s, internal = %s\n", id.ToString(), FormatOutputType(type), internal ? "true" : "false");
auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
auto& spk_mans_other = internal ? m_external_spk_managers : m_internal_spk_managers;
auto spk_man = m_spk_managers.at(id).get();
@@ -3488,7 +3636,7 @@ void CWallet::DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool intern
{
auto spk_man = GetScriptPubKeyMan(type, internal);
if (spk_man != nullptr && spk_man->GetID() == id) {
- WalletLogPrintf("Deactivate spkMan: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
+ WalletLogPrintf("Deactivate spkMan: id = %s, type = %s, internal = %s\n", id.ToString(), FormatOutputType(type), internal ? "true" : "false");
WalletBatch batch(GetDatabase());
if (!batch.EraseActiveScriptPubKeyMan(static_cast<uint8_t>(type), internal)) {
throw std::runtime_error(std::string(__func__) + ": erasing active ScriptPubKeyMan id failed");
@@ -3589,9 +3737,13 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
return nullptr;
}
- CTxDestination dest;
- if (!internal && ExtractDestination(script_pub_keys.at(0), dest)) {
- SetAddressBook(dest, label, "receive");
+ if (!internal) {
+ for (const auto& script : script_pub_keys) {
+ CTxDestination dest;
+ if (ExtractDestination(script, dest)) {
+ SetAddressBook(dest, label, "receive");
+ }
+ }
}
}
@@ -3600,4 +3752,472 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
return spk_man;
}
+
+bool CWallet::MigrateToSQLite(bilingual_str& error)
+{
+ AssertLockHeld(cs_wallet);
+
+ WalletLogPrintf("Migrating wallet storage database from BerkeleyDB to SQLite.\n");
+
+ if (m_database->Format() == "sqlite") {
+ error = _("Error: This wallet already uses SQLite");
+ return false;
+ }
+
+ // Get all of the records for DB type migration
+ std::unique_ptr<DatabaseBatch> batch = m_database->MakeBatch();
+ std::vector<std::pair<SerializeData, SerializeData>> records;
+ if (!batch->StartCursor()) {
+ error = _("Error: Unable to begin reading all records in the database");
+ return false;
+ }
+ bool complete = false;
+ while (true) {
+ CDataStream ss_key(SER_DISK, CLIENT_VERSION);
+ CDataStream ss_value(SER_DISK, CLIENT_VERSION);
+ bool ret = batch->ReadAtCursor(ss_key, ss_value, complete);
+ if (!ret) {
+ break;
+ }
+ SerializeData key(ss_key.begin(), ss_key.end());
+ SerializeData value(ss_value.begin(), ss_value.end());
+ records.emplace_back(key, value);
+ }
+ batch->CloseCursor();
+ batch.reset();
+ if (!complete) {
+ error = _("Error: Unable to read all records in the database");
+ return false;
+ }
+
+ // Close this database and delete the file
+ fs::path db_path = fs::PathFromString(m_database->Filename());
+ fs::path db_dir = db_path.parent_path();
+ m_database->Close();
+ fs::remove(db_path);
+
+ // Make new DB
+ DatabaseOptions opts;
+ opts.require_create = true;
+ opts.require_format = DatabaseFormat::SQLITE;
+ DatabaseStatus db_status;
+ std::unique_ptr<WalletDatabase> new_db = MakeDatabase(db_dir, opts, db_status, error);
+ assert(new_db); // This is to prevent doing anything further with this wallet. The original file was deleted, but a backup exists.
+ m_database.reset();
+ m_database = std::move(new_db);
+
+ // Write existing records into the new DB
+ batch = m_database->MakeBatch();
+ bool began = batch->TxnBegin();
+ assert(began); // This is a critical error, the new db could not be written to. The original db exists as a backup, but we should not continue execution.
+ for (const auto& [key, value] : records) {
+ CDataStream ss_key(key, SER_DISK, CLIENT_VERSION);
+ CDataStream ss_value(value, SER_DISK, CLIENT_VERSION);
+ if (!batch->Write(ss_key, ss_value)) {
+ batch->TxnAbort();
+ m_database->Close();
+ fs::remove(m_database->Filename());
+ assert(false); // This is a critical error, the new db could not be written to. The original db exists as a backup, but we should not continue execution.
+ }
+ }
+ bool committed = batch->TxnCommit();
+ assert(committed); // This is a critical error, the new db could not be written to. The original db exists as a backup, but we should not continue execution.
+ return true;
+}
+
+std::optional<MigrationData> CWallet::GetDescriptorsForLegacy(bilingual_str& error) const
+{
+ AssertLockHeld(cs_wallet);
+
+ LegacyScriptPubKeyMan* legacy_spkm = GetLegacyScriptPubKeyMan();
+ if (!legacy_spkm) {
+ error = _("Error: This wallet is already a descriptor wallet");
+ return std::nullopt;
+ }
+
+ std::optional<MigrationData> res = legacy_spkm->MigrateToDescriptor();
+ if (res == std::nullopt) {
+ error = _("Error: Unable to produce descriptors for this legacy wallet. Make sure the wallet is unlocked first");
+ return std::nullopt;
+ }
+ return res;
+}
+
+bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
+{
+ AssertLockHeld(cs_wallet);
+
+ LegacyScriptPubKeyMan* legacy_spkm = GetLegacyScriptPubKeyMan();
+ if (!legacy_spkm) {
+ error = _("Error: This wallet is already a descriptor wallet");
+ return false;
+ }
+
+ for (auto& desc_spkm : data.desc_spkms) {
+ if (m_spk_managers.count(desc_spkm->GetID()) > 0) {
+ error = _("Error: Duplicate descriptors created during migration. Your wallet may be corrupted.");
+ return false;
+ }
+ m_spk_managers[desc_spkm->GetID()] = std::move(desc_spkm);
+ }
+
+ // Remove the LegacyScriptPubKeyMan from disk
+ if (!legacy_spkm->DeleteRecords()) {
+ return false;
+ }
+
+ // Remove the LegacyScriptPubKeyMan from memory
+ m_spk_managers.erase(legacy_spkm->GetID());
+ m_external_spk_managers.clear();
+ m_internal_spk_managers.clear();
+
+ // Setup new descriptors
+ SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
+ if (!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ // Use the existing master key if we have it
+ if (data.master_key.key.IsValid()) {
+ SetupDescriptorScriptPubKeyMans(data.master_key);
+ } else {
+ // Setup with a new seed if we don't.
+ SetupDescriptorScriptPubKeyMans();
+ }
+ }
+
+ // Check if the transactions in the wallet are still ours. Either they belong here, or they belong in the watchonly wallet.
+ // We need to go through these in the tx insertion order so that lookups to spends works.
+ std::vector<uint256> txids_to_delete;
+ for (const auto& [_pos, wtx] : wtxOrdered) {
+ if (!IsMine(*wtx->tx) && !IsFromMe(*wtx->tx)) {
+ // Check it is the watchonly wallet's
+ // solvable_wallet doesn't need to be checked because transactions for those scripts weren't being watched for
+ if (data.watchonly_wallet) {
+ LOCK(data.watchonly_wallet->cs_wallet);
+ if (data.watchonly_wallet->IsMine(*wtx->tx) || data.watchonly_wallet->IsFromMe(*wtx->tx)) {
+ // Add to watchonly wallet
+ if (!data.watchonly_wallet->AddToWallet(wtx->tx, wtx->m_state)) {
+ error = _("Error: Could not add watchonly tx to watchonly wallet");
+ return false;
+ }
+ // Mark as to remove from this wallet
+ txids_to_delete.push_back(wtx->GetHash());
+ continue;
+ }
+ }
+ // Both not ours and not in the watchonly wallet
+ error = strprintf(_("Error: Transaction %s in wallet cannot be identified to belong to migrated wallets"), wtx->GetHash().GetHex());
+ return false;
+ }
+ }
+ // Do the removes
+ if (txids_to_delete.size() > 0) {
+ std::vector<uint256> deleted_txids;
+ if (ZapSelectTx(txids_to_delete, deleted_txids) != DBErrors::LOAD_OK) {
+ error = _("Error: Could not delete watchonly transactions");
+ return false;
+ }
+ if (deleted_txids != txids_to_delete) {
+ error = _("Error: Not all watchonly txs could be deleted");
+ return false;
+ }
+ // Tell the GUI of each tx
+ for (const uint256& txid : deleted_txids) {
+ NotifyTransactionChanged(txid, CT_UPDATED);
+ }
+ }
+
+ // Check the address book data in the same way we did for transactions
+ std::vector<CTxDestination> dests_to_delete;
+ for (const auto& addr_pair : m_address_book) {
+ // Labels applied to receiving addresses should go based on IsMine
+ if (addr_pair.second.purpose == "receive") {
+ if (!IsMine(addr_pair.first)) {
+ // Check the address book data is the watchonly wallet's
+ if (data.watchonly_wallet) {
+ LOCK(data.watchonly_wallet->cs_wallet);
+ if (data.watchonly_wallet->IsMine(addr_pair.first)) {
+ // Add to the watchonly. Preserve the labels, purpose, and change-ness
+ std::string label = addr_pair.second.GetLabel();
+ std::string purpose = addr_pair.second.purpose;
+ if (!purpose.empty()) {
+ data.watchonly_wallet->m_address_book[addr_pair.first].purpose = purpose;
+ }
+ if (!addr_pair.second.IsChange()) {
+ data.watchonly_wallet->m_address_book[addr_pair.first].SetLabel(label);
+ }
+ dests_to_delete.push_back(addr_pair.first);
+ continue;
+ }
+ }
+ if (data.solvable_wallet) {
+ LOCK(data.solvable_wallet->cs_wallet);
+ if (data.solvable_wallet->IsMine(addr_pair.first)) {
+ // Add to the solvable. Preserve the labels, purpose, and change-ness
+ std::string label = addr_pair.second.GetLabel();
+ std::string purpose = addr_pair.second.purpose;
+ if (!purpose.empty()) {
+ data.solvable_wallet->m_address_book[addr_pair.first].purpose = purpose;
+ }
+ if (!addr_pair.second.IsChange()) {
+ data.solvable_wallet->m_address_book[addr_pair.first].SetLabel(label);
+ }
+ dests_to_delete.push_back(addr_pair.first);
+ continue;
+ }
+ }
+ // Not ours, not in watchonly wallet, and not in solvable
+ error = _("Error: Address book data in wallet cannot be identified to belong to migrated wallets");
+ return false;
+ }
+ } else {
+ // Labels for everything else (send) should be cloned to all
+ if (data.watchonly_wallet) {
+ LOCK(data.watchonly_wallet->cs_wallet);
+ // Add to the watchonly. Preserve the labels, purpose, and change-ness
+ std::string label = addr_pair.second.GetLabel();
+ std::string purpose = addr_pair.second.purpose;
+ if (!purpose.empty()) {
+ data.watchonly_wallet->m_address_book[addr_pair.first].purpose = purpose;
+ }
+ if (!addr_pair.second.IsChange()) {
+ data.watchonly_wallet->m_address_book[addr_pair.first].SetLabel(label);
+ }
+ continue;
+ }
+ if (data.solvable_wallet) {
+ LOCK(data.solvable_wallet->cs_wallet);
+ // Add to the solvable. Preserve the labels, purpose, and change-ness
+ std::string label = addr_pair.second.GetLabel();
+ std::string purpose = addr_pair.second.purpose;
+ if (!purpose.empty()) {
+ data.solvable_wallet->m_address_book[addr_pair.first].purpose = purpose;
+ }
+ if (!addr_pair.second.IsChange()) {
+ data.solvable_wallet->m_address_book[addr_pair.first].SetLabel(label);
+ }
+ continue;
+ }
+ }
+ }
+ // Remove the things to delete
+ if (dests_to_delete.size() > 0) {
+ for (const auto& dest : dests_to_delete) {
+ if (!DelAddressBook(dest)) {
+ error = _("Error: Unable to remove watchonly address book data");
+ return false;
+ }
+ }
+ }
+
+ // Connect the SPKM signals
+ ConnectScriptPubKeyManNotifiers();
+ NotifyCanGetAddressesChanged();
+
+ WalletLogPrintf("Wallet migration complete.\n");
+
+ return true;
+}
+
+bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error, MigrationResult& res) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
+{
+ AssertLockHeld(wallet.cs_wallet);
+
+ // Get all of the descriptors from the legacy wallet
+ std::optional<MigrationData> data = wallet.GetDescriptorsForLegacy(error);
+ if (data == std::nullopt) return false;
+
+ // Create the watchonly and solvable wallets if necessary
+ if (data->watch_descs.size() > 0 || data->solvable_descs.size() > 0) {
+ DatabaseOptions options;
+ options.require_existing = false;
+ options.require_create = true;
+
+ // Make the wallets
+ options.create_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET | WALLET_FLAG_DESCRIPTORS;
+ if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
+ options.create_flags |= WALLET_FLAG_AVOID_REUSE;
+ }
+ if (wallet.IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) {
+ options.create_flags |= WALLET_FLAG_KEY_ORIGIN_METADATA;
+ }
+ if (data->watch_descs.size() > 0) {
+ wallet.WalletLogPrintf("Making a new watchonly wallet containing the watched scripts\n");
+
+ DatabaseStatus status;
+ std::vector<bilingual_str> warnings;
+ std::string wallet_name = wallet.GetName() + "_watchonly";
+ data->watchonly_wallet = CreateWallet(context, wallet_name, std::nullopt, options, status, error, warnings);
+ if (status != DatabaseStatus::SUCCESS) {
+ error = _("Error: Failed to create new watchonly wallet");
+ return false;
+ }
+ res.watchonly_wallet = data->watchonly_wallet;
+ LOCK(data->watchonly_wallet->cs_wallet);
+
+ // Parse the descriptors and add them to the new wallet
+ for (const auto& [desc_str, creation_time] : data->watch_descs) {
+ // Parse the descriptor
+ FlatSigningProvider keys;
+ std::string parse_err;
+ std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, parse_err, /* require_checksum */ true);
+ assert(desc); // It shouldn't be possible to have the LegacyScriptPubKeyMan make an invalid descriptor
+ assert(!desc->IsRange()); // It shouldn't be possible to have LegacyScriptPubKeyMan make a ranged watchonly descriptor
+
+ // Add to the wallet
+ WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0);
+ data->watchonly_wallet->AddWalletDescriptor(w_desc, keys, "", false);
+ }
+
+ // Add the wallet to settings
+ UpdateWalletSetting(*context.chain, wallet_name, /*load_on_startup=*/true, warnings);
+ }
+ if (data->solvable_descs.size() > 0) {
+ wallet.WalletLogPrintf("Making a new watchonly wallet containing the unwatched solvable scripts\n");
+
+ DatabaseStatus status;
+ std::vector<bilingual_str> warnings;
+ std::string wallet_name = wallet.GetName() + "_solvables";
+ data->solvable_wallet = CreateWallet(context, wallet_name, std::nullopt, options, status, error, warnings);
+ if (status != DatabaseStatus::SUCCESS) {
+ error = _("Error: Failed to create new watchonly wallet");
+ return false;
+ }
+ res.solvables_wallet = data->solvable_wallet;
+ LOCK(data->solvable_wallet->cs_wallet);
+
+ // Parse the descriptors and add them to the new wallet
+ for (const auto& [desc_str, creation_time] : data->solvable_descs) {
+ // Parse the descriptor
+ FlatSigningProvider keys;
+ std::string parse_err;
+ std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, parse_err, /* require_checksum */ true);
+ assert(desc); // It shouldn't be possible to have the LegacyScriptPubKeyMan make an invalid descriptor
+ assert(!desc->IsRange()); // It shouldn't be possible to have LegacyScriptPubKeyMan make a ranged watchonly descriptor
+
+ // Add to the wallet
+ WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0);
+ data->solvable_wallet->AddWalletDescriptor(w_desc, keys, "", false);
+ }
+
+ // Add the wallet to settings
+ UpdateWalletSetting(*context.chain, wallet_name, /*load_on_startup=*/true, warnings);
+ }
+ }
+
+ // Add the descriptors to wallet, remove LegacyScriptPubKeyMan, and cleanup txs and address book data
+ if (!wallet.ApplyMigrationData(*data, error)) {
+ return false;
+ }
+ return true;
+}
+
+util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>&& wallet, WalletContext& context)
+{
+ MigrationResult res;
+ bilingual_str error;
+ std::vector<bilingual_str> warnings;
+
+ // Make a backup of the DB
+ std::string wallet_name = wallet->GetName();
+ fs::path this_wallet_dir = fs::absolute(fs::PathFromString(wallet->GetDatabase().Filename())).parent_path();
+ fs::path backup_filename = fs::PathFromString(strprintf("%s-%d.legacy.bak", wallet_name, GetTime()));
+ fs::path backup_path = this_wallet_dir / backup_filename;
+ if (!wallet->BackupWallet(fs::PathToString(backup_path))) {
+ return util::Error{_("Error: Unable to make a backup of your wallet")};
+ }
+ res.backup_path = backup_path;
+
+ // Unload the wallet so that nothing else tries to use it while we're changing it
+ if (!RemoveWallet(context, wallet, /*load_on_start=*/std::nullopt, warnings)) {
+ return util::Error{_("Unable to unload the wallet before migrating")};
+ }
+ UnloadWallet(std::move(wallet));
+
+ // Load the wallet but only in the context of this function.
+ // No signals should be connected nor should anything else be aware of this wallet
+ WalletContext empty_context;
+ empty_context.args = context.args;
+ DatabaseOptions options;
+ options.require_existing = true;
+ DatabaseStatus status;
+ std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(wallet_name, options, status, error);
+ if (!database) {
+ return util::Error{Untranslated("Wallet file verification failed.") + Untranslated(" ") + error};
+ }
+
+ std::shared_ptr<CWallet> local_wallet = CWallet::Create(empty_context, wallet_name, std::move(database), options.create_flags, error, warnings);
+ if (!local_wallet) {
+ return util::Error{Untranslated("Wallet loading failed.") + Untranslated(" ") + error};
+ }
+
+ bool success = false;
+ {
+ LOCK(local_wallet->cs_wallet);
+
+ // First change to using SQLite
+ if (!local_wallet->MigrateToSQLite(error)) return util::Error{error};
+
+ // Do the migration, and cleanup if it fails
+ success = DoMigration(*local_wallet, context, error, res);
+ }
+
+ if (success) {
+ // Migration successful, unload the wallet locally, then reload it.
+ assert(local_wallet.use_count() == 1);
+ local_wallet.reset();
+ LoadWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error, warnings);
+ res.wallet_name = wallet_name;
+ } else {
+ // Migration failed, cleanup
+ // Copy the backup to the actual wallet dir
+ fs::path temp_backup_location = fsbridge::AbsPathJoin(GetWalletDir(), backup_filename);
+ fs::copy_file(backup_path, temp_backup_location, fs::copy_options::none);
+
+ // Remember this wallet's walletdir to remove after unloading
+ std::vector<fs::path> wallet_dirs;
+ wallet_dirs.push_back(fs::PathFromString(local_wallet->GetDatabase().Filename()).parent_path());
+
+ // Unload the wallet locally
+ assert(local_wallet.use_count() == 1);
+ local_wallet.reset();
+
+ // Make list of wallets to cleanup
+ std::vector<std::shared_ptr<CWallet>> created_wallets;
+ created_wallets.push_back(std::move(res.watchonly_wallet));
+ created_wallets.push_back(std::move(res.solvables_wallet));
+
+ // Get the directories to remove after unloading
+ for (std::shared_ptr<CWallet>& w : created_wallets) {
+ wallet_dirs.push_back(fs::PathFromString(w->GetDatabase().Filename()).parent_path());
+ }
+
+ // Unload the wallets
+ for (std::shared_ptr<CWallet>& w : created_wallets) {
+ if (!RemoveWallet(context, w, /*load_on_start=*/false)) {
+ error += _("\nUnable to cleanup failed migration");
+ return util::Error{error};
+ }
+ UnloadWallet(std::move(w));
+ }
+
+ // Delete the wallet directories
+ for (fs::path& dir : wallet_dirs) {
+ fs::remove_all(dir);
+ }
+
+ // Restore the backup
+ DatabaseStatus status;
+ std::vector<bilingual_str> warnings;
+ if (!RestoreWallet(context, temp_backup_location, wallet_name, /*load_on_start=*/std::nullopt, status, error, warnings)) {
+ error += _("\nUnable to restore backup of wallet.");
+ return util::Error{error};
+ }
+
+ // Move the backup to the wallet dir
+ fs::copy_file(temp_backup_location, backup_path, fs::copy_options::none);
+ fs::remove(temp_backup_location);
+
+ return util::Error{error};
+ }
+ return res;
+}
} // namespace wallet
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index abab4a2a9b..137320acda 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -14,11 +14,13 @@
#include <policy/feerate.h>
#include <psbt.h>
#include <tinyformat.h>
+#include <util/hasher.h>
#include <util/message.h>
#include <util/result.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/system.h>
+#include <util/time.h>
#include <util/ui_change_type.h>
#include <validationinterface.h>
#include <wallet/crypter.h>
@@ -37,6 +39,7 @@
#include <stdint.h>
#include <string>
#include <utility>
+#include <unordered_map>
#include <vector>
#include <boost/signals2/signal.hpp>
@@ -62,6 +65,7 @@ bool AddWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet);
bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start, std::vector<bilingual_str>& warnings);
bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start);
std::vector<std::shared_ptr<CWallet>> GetWallets(WalletContext& context);
+std::shared_ptr<CWallet> GetDefaultWallet(WalletContext& context, size_t& count);
std::shared_ptr<CWallet> GetWallet(WalletContext& context, const std::string& name);
std::shared_ptr<CWallet> LoadWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
@@ -90,7 +94,7 @@ static const CAmount DEFAULT_CONSOLIDATE_FEERATE{10000}; // 10 sat/vbyte
static const CAmount DEFAULT_MAX_AVOIDPARTIALSPEND_FEE = 0;
//! discourage APS fee higher than this amount
constexpr CAmount HIGH_APS_FEE{COIN / 10000};
-//! minimum recommended increment for BIP 125 replacement txs
+//! minimum recommended increment for replacement txs
static const CAmount WALLET_INCREMENTAL_RELAY_FEE = 5000;
//! Default for -spendzeroconfchange
static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true;
@@ -189,10 +193,10 @@ public:
}
//! Reserve an address
- bool GetReservedDestination(CTxDestination& pubkey, bool internal, bilingual_str& error);
+ util::Result<CTxDestination> GetReservedDestination(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
+ //! Keep the address. Do not return its key to the keypool when this object goes out of scope
void KeepDestination();
};
@@ -247,7 +251,7 @@ private:
int nWalletVersion GUARDED_BY(cs_wallet){FEATURE_BASE};
/** The next scheduled rebroadcast of wallet transactions. */
- int64_t nNextResend = 0;
+ NodeClock::time_point m_next_resend{GetDefaultNextResend()};
/** Whether this wallet will submit newly created transactions to the node's mempool and
* prompt rebroadcasts (see ResendWalletTransactions()). */
bool fBroadcastTransactions = false;
@@ -259,7 +263,7 @@ private:
* detect and report conflicts (double-spends or
* mutated transactions where the mutant gets mined).
*/
- typedef std::multimap<COutPoint, uint256> TxSpends;
+ typedef std::unordered_multimap<COutPoint, uint256, SaltedOutpointHasher> TxSpends;
TxSpends mapTxSpends GUARDED_BY(cs_wallet);
void AddToSpends(const COutPoint& outpoint, const uint256& wtxid, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void AddToSpends(const CWalletTx& wtx, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -313,7 +317,7 @@ private:
std::string m_name;
/** Internal database handle. */
- std::unique_ptr<WalletDatabase> const m_database;
+ std::unique_ptr<WalletDatabase> m_database;
/**
* The following is used to keep track of how far behind the wallet is
@@ -345,6 +349,8 @@ private:
*/
static bool AttachChain(const std::shared_ptr<CWallet>& wallet, interfaces::Chain& chain, const bool rescan_required, bilingual_str& error, std::vector<bilingual_str>& warnings);
+ static NodeClock::time_point GetDefaultNextResend();
+
public:
/**
* Main wallet lock.
@@ -390,13 +396,12 @@ public:
/** Map from txid to CWalletTx for all transactions this wallet is
* interested in, including received and sent transactions. */
- std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet);
+ std::unordered_map<uint256, CWalletTx, SaltedTxidHasher> mapWallet GUARDED_BY(cs_wallet);
typedef std::multimap<int64_t, CWalletTx*> TxItems;
TxItems wtxOrdered;
int64_t nOrderPosNext GUARDED_BY(cs_wallet) = 0;
- uint64_t nAccountingEntryNumber = 0;
std::map<CTxDestination, CAddressBookData> m_address_book GUARDED_BY(cs_wallet);
const CAddressBookData* FindAddressBookEntry(const CTxDestination&, bool allow_change = false) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -505,6 +510,10 @@ public:
//! @return true if wtx is changed and needs to be saved to disk, otherwise false
using UpdateWalletTxFn = std::function<bool(CWalletTx& wtx, bool new_tx)>;
+ /**
+ * Add the transaction to the wallet, wrapping it up inside a CWalletTx
+ * @return the recently added wtx pointer or nullptr if there was a db write error.
+ */
CWalletTx* AddToWallet(CTransactionRef tx, const TxState& state, const UpdateWalletTxFn& update_wtx=nullptr, bool fFlushOnClose=true, bool rescanning_old_block = false);
bool LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void transactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) override;
@@ -530,8 +539,11 @@ public:
};
ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate, const bool save_progress);
void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override;
- void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- void ResendWalletTransactions();
+ /** Set the next time this wallet should resend transactions to 12-36 hours from now, ~1 day on average. */
+ void SetNextResend() { m_next_resend = GetDefaultNextResend(); }
+ /** Return true if all conditions for periodically resending transactions are met. */
+ bool ShouldResend() const;
+ void ResubmitWalletTransactions(bool relay, bool force);
OutputType TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const;
@@ -666,8 +678,8 @@ public:
*/
void MarkDestinationsDirty(const std::set<CTxDestination>& destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- BResult<CTxDestination> GetNewDestination(const OutputType type, const std::string label);
- BResult<CTxDestination> GetNewChangeDestination(const OutputType type);
+ util::Result<CTxDestination> GetNewDestination(const OutputType type, const std::string label);
+ util::Result<CTxDestination> GetNewChangeDestination(const OutputType type);
isminetype IsMine(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
isminetype IsMine(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -678,6 +690,7 @@ public:
CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const;
isminetype IsMine(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool IsMine(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ isminetype IsMine(const COutPoint& outpoint) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** should probably be renamed to IsRelevantToMe */
bool IsFromMe(const CTransaction& tx) const;
CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const;
@@ -708,7 +721,7 @@ public:
std::set<uint256> GetConflicts(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Check if a given transaction has any of its outputs spent by another transaction in the wallet
- bool HasWalletSpend(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool HasWalletSpend(const CTransactionRef& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Flush wallet (bitdb flush)
void Flush();
@@ -760,7 +773,7 @@ public:
/* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */
bool AbandonTransaction(const uint256& hashTx);
- /** Mark a transaction as replaced by another transaction (e.g., BIP 125). */
+ /** Mark a transaction as replaced by another transaction. */
bool MarkReplaced(const uint256& originalHash, const uint256& newHash);
/* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */
@@ -798,8 +811,9 @@ public:
bool IsWalletFlagSet(uint64_t flag) const override;
/** overwrite all flags by the given uint64_t
- returns false if unknown, non-tolerable flags are present */
- bool AddWalletFlags(uint64_t flags);
+ flags must be uninitialised (or 0)
+ only known flags may be present */
+ void InitWalletFlags(uint64_t flags);
/** Loads the flags into the wallet. (used by LoadWallet) */
bool LoadWalletFlags(uint64_t flags);
@@ -839,6 +853,9 @@ public:
std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script) const;
std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script, SignatureData& sigdata) const;
+ //! Get the wallet descriptors for a script.
+ std::vector<WalletDescriptor> GetWalletDescriptors(const CScript& script) const;
+
//! Get the LegacyScriptPubKeyMan which is used for all types, internal, and external.
LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const;
LegacyScriptPubKeyMan* GetOrCreateLegacyScriptPubKeyMan();
@@ -895,6 +912,7 @@ public:
void DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool internal);
//! Create new DescriptorScriptPubKeyMans and add them to the wallet
+ void SetupDescriptorScriptPubKeyMans(const CExtKey& master_key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Return the DescriptorScriptPubKeyMan for a WalletDescriptor if it is already in the wallet
@@ -907,6 +925,20 @@ public:
//! Add a descriptor to the wallet, return a ScriptPubKeyMan & associated output type
ScriptPubKeyMan* AddWalletDescriptor(WalletDescriptor& desc, const FlatSigningProvider& signing_provider, const std::string& label, bool internal) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ /** Move all records from the BDB database to a new SQLite database for storage.
+ * The original BDB file will be deleted and replaced with a new SQLite file.
+ * A backup is not created.
+ * May crash if something unexpected happens in the filesystem.
+ */
+ bool MigrateToSQLite(bilingual_str& error) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ //! Get all of the descriptors from a legacy wallet
+ std::optional<MigrationData> GetDescriptorsForLegacy(bilingual_str& error) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ //! Adds the ScriptPubKeyMans given in MigrationData to this wallet, removes LegacyScriptPubKeyMan,
+ //! and where needed, moves tx and address book entries to watchonly_wallet or solvable_wallet
+ bool ApplyMigrationData(MigrationData& data, bilingual_str& error) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
};
/**
@@ -965,6 +997,16 @@ bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_nam
bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, const CCoinControl* coin_control = nullptr);
bool FillInputToWeight(CTxIn& txin, int64_t target_weight);
+
+struct MigrationResult {
+ std::string wallet_name;
+ std::shared_ptr<CWallet> watchonly_wallet;
+ std::shared_ptr<CWallet> solvables_wallet;
+ fs::path backup_path;
+};
+
+//! Do all steps to migrate a legacy wallet to a descriptor wallet
+util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>&& wallet, WalletContext& context);
} // namespace wallet
#endif // BITCOIN_WALLET_WALLET_H
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 8afd3f416d..6a8f0d2481 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -59,6 +59,7 @@ const std::string WALLETDESCRIPTORCKEY{"walletdescriptorckey"};
const std::string WALLETDESCRIPTORKEY{"walletdescriptorkey"};
const std::string WATCHMETA{"watchmeta"};
const std::string WATCHS{"watchs"};
+const std::unordered_set<std::string> LEGACY_TYPES{CRYPTED_KEY, CSCRIPT, DEFAULTKEY, HDCHAIN, KEYMETA, KEY, OLD_KEY, POOL, WATCHMETA, WATCHS};
} // namespace DBKeys
//
@@ -313,6 +314,7 @@ public:
std::map<std::pair<uint256, CKeyID>, std::pair<CPubKey, std::vector<unsigned char>>> m_descriptor_crypt_keys;
std::map<uint160, CHDChain> m_hd_chains;
bool tx_corrupt{false};
+ bool descriptor_unknown{false};
CWalletScanState() = default;
};
@@ -626,7 +628,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
uint256 id;
ssKey >> id;
WalletDescriptor desc;
- ssValue >> desc;
+ try {
+ ssValue >> desc;
+ } catch (const std::ios_base::failure& e) {
+ strErr = e.what();
+ wss.descriptor_unknown = true;
+ return false;
+ }
if (wss.m_descriptor_caches.count(id) == 0) {
wss.m_descriptor_caches[id] = DescriptorCache();
}
@@ -766,6 +774,12 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
DBErrors result = DBErrors::LOAD_OK;
LOCK(pwallet->cs_wallet);
+
+ // Last client version to open this wallet
+ int last_client = CLIENT_VERSION;
+ bool has_last_client = m_batch->Read(DBKeys::VERSION, last_client);
+ pwallet->WalletLogPrintf("Wallet file version = %d, last client version = %d\n", pwallet->GetVersion(), last_client);
+
try {
int nMinVersion = 0;
if (m_batch->Read(DBKeys::MINVERSION, nMinVersion)) {
@@ -831,6 +845,13 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
// Set tx_corrupt back to false so that the error is only printed once (per corrupt tx)
wss.tx_corrupt = false;
result = DBErrors::CORRUPT;
+ } else if (wss.descriptor_unknown) {
+ strErr = strprintf("Error: Unrecognized descriptor found in wallet %s. ", pwallet->GetName());
+ strErr += (last_client > CLIENT_VERSION) ? "The wallet might had been created on a newer version. " :
+ "The database might be corrupted or the software version is not compatible with one of your wallet descriptors. ";
+ strErr += "Please try running the latest software version";
+ pwallet->WalletLogPrintf("%s\n", strErr);
+ return DBErrors::UNKNOWN_DESCRIPTOR;
} 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.
@@ -856,18 +877,18 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
}
// Set the descriptor caches
- for (auto desc_cache_pair : wss.m_descriptor_caches) {
+ for (const auto& desc_cache_pair : wss.m_descriptor_caches) {
auto spk_man = pwallet->GetScriptPubKeyMan(desc_cache_pair.first);
assert(spk_man);
((DescriptorScriptPubKeyMan*)spk_man)->SetCache(desc_cache_pair.second);
}
// Set the descriptor keys
- for (auto desc_key_pair : wss.m_descriptor_keys) {
+ for (const auto& desc_key_pair : wss.m_descriptor_keys) {
auto spk_man = pwallet->GetScriptPubKeyMan(desc_key_pair.first.first);
((DescriptorScriptPubKeyMan*)spk_man)->AddKey(desc_key_pair.first.second, desc_key_pair.second);
}
- for (auto desc_key_pair : wss.m_descriptor_crypt_keys) {
+ for (const auto& desc_key_pair : wss.m_descriptor_crypt_keys) {
auto spk_man = pwallet->GetScriptPubKeyMan(desc_key_pair.first.first);
((DescriptorScriptPubKeyMan*)spk_man)->AddCryptedKey(desc_key_pair.first.second, desc_key_pair.second.first, desc_key_pair.second.second);
}
@@ -883,11 +904,6 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
if (result != DBErrors::LOAD_OK)
return result;
- // Last client version to open this wallet
- int last_client = CLIENT_VERSION;
- bool has_last_client = m_batch->Read(DBKeys::VERSION, last_client);
- pwallet->WalletLogPrintf("Wallet file version = %d, last client version = %d\n", pwallet->GetVersion(), 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);
@@ -1083,6 +1099,45 @@ bool WalletBatch::WriteWalletFlags(const uint64_t flags)
return WriteIC(DBKeys::FLAGS, flags);
}
+bool WalletBatch::EraseRecords(const std::unordered_set<std::string>& types)
+{
+ // Get cursor
+ if (!m_batch->StartCursor())
+ {
+ return false;
+ }
+
+ // Iterate the DB and look for any records that have the type prefixes
+ while (true)
+ {
+ // Read next record
+ CDataStream key(SER_DISK, CLIENT_VERSION);
+ CDataStream value(SER_DISK, CLIENT_VERSION);
+ bool complete;
+ bool ret = m_batch->ReadAtCursor(key, value, complete);
+ if (complete) {
+ break;
+ }
+ else if (!ret)
+ {
+ m_batch->CloseCursor();
+ return false;
+ }
+
+ // Make a copy of key to avoid data being deleted by the following read of the type
+ Span<const unsigned char> key_data = MakeUCharSpan(key);
+
+ std::string type;
+ key >> type;
+
+ if (types.count(type) > 0) {
+ m_batch->Erase(key_data);
+ }
+ }
+ m_batch->CloseCursor();
+ return true;
+}
+
bool WalletBatch::TxnBegin()
{
return m_batch->TxnBegin();
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index a04ea598b6..da6efe534b 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -51,7 +51,8 @@ enum class DBErrors
EXTERNAL_SIGNER_SUPPORT_REQUIRED,
LOAD_FAIL,
NEED_REWRITE,
- NEED_RESCAN
+ NEED_RESCAN,
+ UNKNOWN_DESCRIPTOR
};
namespace DBKeys {
@@ -84,6 +85,9 @@ extern const std::string WALLETDESCRIPTORCKEY;
extern const std::string WALLETDESCRIPTORKEY;
extern const std::string WATCHMETA;
extern const std::string WATCHS;
+
+// Keys in this set pertain only to the legacy wallet (LegacyScriptPubKeyMan) and are removed during migration from legacy to descriptors.
+extern const std::unordered_set<std::string> LEGACY_TYPES;
} // namespace DBKeys
/* simple HD chain data model */
@@ -276,6 +280,9 @@ public:
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain);
+ //! Delete records of the given types
+ bool EraseRecords(const std::unordered_set<std::string>& types);
+
bool WriteWalletFlags(const uint64_t flags);
//! Begin a new transaction
bool TxnBegin();
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index 769175b5a8..e991bc0814 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -34,7 +34,7 @@ static void WalletCreate(CWallet* wallet_instance, uint64_t wallet_creation_flag
LOCK(wallet_instance->cs_wallet);
wallet_instance->SetMinVersion(FEATURE_LATEST);
- wallet_instance->AddWalletFlags(wallet_creation_flags);
+ wallet_instance->InitWalletFlags(wallet_creation_flags);
if (!wallet_instance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
auto spk_man = wallet_instance->GetOrCreateLegacyScriptPubKeyMan();
diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h
index 788d41ceb7..8434d64fb5 100644
--- a/src/wallet/walletutil.h
+++ b/src/wallet/walletutil.h
@@ -104,6 +104,20 @@ public:
WalletDescriptor() {}
WalletDescriptor(std::shared_ptr<Descriptor> descriptor, uint64_t creation_time, int32_t range_start, int32_t range_end, int32_t next_index) : descriptor(descriptor), creation_time(creation_time), range_start(range_start), range_end(range_end), next_index(next_index) {}
};
+
+class CWallet;
+class DescriptorScriptPubKeyMan;
+
+/** struct containing information needed for migrating legacy wallets to descriptor wallets */
+struct MigrationData
+{
+ CExtKey master_key;
+ std::vector<std::pair<std::string, int64_t>> watch_descs;
+ std::vector<std::pair<std::string, int64_t>> solvable_descs;
+ std::vector<std::unique_ptr<DescriptorScriptPubKeyMan>> desc_spkms;
+ std::shared_ptr<CWallet> watchonly_wallet{nullptr};
+ std::shared_ptr<CWallet> solvable_wallet{nullptr};
+};
} // namespace wallet
#endif // BITCOIN_WALLET_WALLETUTIL_H
diff --git a/src/zmq/zmqabstractnotifier.h b/src/zmq/zmqabstractnotifier.h
index fa3944e32b..97c2599366 100644
--- a/src/zmq/zmqabstractnotifier.h
+++ b/src/zmq/zmqabstractnotifier.h
@@ -5,7 +5,7 @@
#ifndef BITCOIN_ZMQ_ZMQABSTRACTNOTIFIER_H
#define BITCOIN_ZMQ_ZMQABSTRACTNOTIFIER_H
-
+#include <cstdint>
#include <memory>
#include <string>
diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp
index 26618735f6..6ee134f392 100644
--- a/src/zmq/zmqnotificationinterface.cpp
+++ b/src/zmq/zmqnotificationinterface.cpp
@@ -3,13 +3,23 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <zmq/zmqnotificationinterface.h>
+
+#include <logging.h>
+#include <primitives/block.h>
+#include <primitives/transaction.h>
+#include <util/system.h>
+#include <validationinterface.h>
+#include <zmq/zmqabstractnotifier.h>
#include <zmq/zmqpublishnotifier.h>
#include <zmq/zmqutil.h>
#include <zmq.h>
-#include <validation.h>
-#include <util/system.h>
+#include <cassert>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
CZMQNotificationInterface::CZMQNotificationInterface() : pcontext(nullptr)
{
diff --git a/src/zmq/zmqnotificationinterface.h b/src/zmq/zmqnotificationinterface.h
index 8f81bfd63f..585e900ca6 100644
--- a/src/zmq/zmqnotificationinterface.h
+++ b/src/zmq/zmqnotificationinterface.h
@@ -5,10 +5,14 @@
#ifndef BITCOIN_ZMQ_ZMQNOTIFICATIONINTERFACE_H
#define BITCOIN_ZMQ_ZMQNOTIFICATIONINTERFACE_H
+#include <primitives/transaction.h>
#include <validationinterface.h>
+
+#include <cstdint>
#include <list>
#include <memory>
+class CBlock;
class CBlockIndex;
class CZMQAbstractNotifier;
diff --git a/src/zmq/zmqpublishnotifier.cpp b/src/zmq/zmqpublishnotifier.cpp
index 011336bf72..eaf3455296 100644
--- a/src/zmq/zmqpublishnotifier.cpp
+++ b/src/zmq/zmqpublishnotifier.cpp
@@ -6,22 +6,37 @@
#include <chain.h>
#include <chainparams.h>
+#include <crypto/common.h>
+#include <logging.h>
+#include <netaddress.h>
#include <netbase.h>
#include <node/blockstorage.h>
+#include <primitives/block.h>
+#include <primitives/transaction.h>
#include <rpc/server.h>
+#include <serialize.h>
#include <streams.h>
-#include <util/system.h>
-#include <validation.h> // For cs_main
+#include <sync.h>
+#include <uint256.h>
+#include <version.h>
#include <zmq/zmqutil.h>
#include <zmq.h>
+#include <cassert>
#include <cstdarg>
#include <cstddef>
+#include <cstdint>
+#include <cstring>
#include <map>
#include <optional>
#include <string>
#include <utility>
+#include <vector>
+
+namespace Consensus {
+struct Params;
+}
using node::ReadBlockFromDisk;
diff --git a/src/zmq/zmqpublishnotifier.h b/src/zmq/zmqpublishnotifier.h
index c1d66bddb1..fcedd1aabe 100644
--- a/src/zmq/zmqpublishnotifier.h
+++ b/src/zmq/zmqpublishnotifier.h
@@ -7,7 +7,11 @@
#include <zmq/zmqabstractnotifier.h>
+#include <cstddef>
+#include <cstdint>
+
class CBlockIndex;
+class CTransaction;
class CZMQAbstractPublishNotifier : public CZMQAbstractNotifier
{
diff --git a/src/zmq/zmqrpc.cpp b/src/zmq/zmqrpc.cpp
index ec6d1cbba3..047e6bf9b7 100644
--- a/src/zmq/zmqrpc.cpp
+++ b/src/zmq/zmqrpc.cpp
@@ -11,6 +11,11 @@
#include <univalue.h>
+#include <list>
+#include <string>
+
+class JSONRPCRequest;
+
namespace {
static RPCHelpMan getzmqnotifications()